diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ba7220 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# https://github.com/github/gitignore/blob/master/Swift.gitignore + +## User settings +xcuserdata/ + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +.idea/ diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..41002b5 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,46 @@ +# https://github.com/airbnb/swift + +whitelist_rules: + - closure_spacing + - colon + - empty_enum_arguments + - fatal_error_message + - force_try + # - force_cast + # - force_unwrapping + # - implicitly_unwrapped_optional + - legacy_cggeometry_functions + - legacy_constant + - legacy_constructor + - legacy_nsgeometry_functions + - operator_usage_whitespace + - redundant_string_enum_value + - redundant_void_return + - return_arrow_whitespace + - trailing_newline + - trailing_whitespace + - trailing_semicolon + - type_name + - generic_type_name + - unused_closure_parameter + - unused_optional_binding + - vertical_whitespace + - void_return + - custom_rules + - file_header + - trailing_comma + +excluded: + - "PlaygroundBook/Modules/SnapKit.playgroundmodule" + - "PlaygroundBook/Modules/DifferenceKit.playgroundmodule" + - "PlaygroundBook/Modules/ASCollectionView.playgroundmodule" + # - Pods + # - AHDownloadButton + +colon: + apply_to_dictionaries: true + +indentation: 2 + +file_header: + required_pattern: "(\\/\\*\\n \\* Copyright © 2021 Ethan Wong\\. Licensed under MIT\\.\\n \\*)|(\\/\\/#-hidden-code\\n\\/\\/\\n\\/\\/ Copyright © 2021 Ethan Wong\\. Licensed under MIT\\.\\n\\/\\/\\n)" diff --git a/ConfigFiles/Base/BaseBuildSettings.xcconfig b/ConfigFiles/Base/BaseBuildSettings.xcconfig new file mode 100644 index 0000000..82c924e --- /dev/null +++ b/ConfigFiles/Base/BaseBuildSettings.xcconfig @@ -0,0 +1,58 @@ +// +// See LICENSE folder for this template’s licensing information. +// +// Abstract: +// Provides build settings which should be used by all targets. +// + +// +// Most of these build settings should not be edited. +// SUPPORTING_CONTENT_DIR may be edited if you move the SupportingContent directory out of the project directory. +// + +// The path to the directory containing all of the supporting content for this project. +// By default, the supporting content is stored in the SupportingContent directory next to the xcodeproj. +SUPPORTING_CONTENT_DIR = $(PROJECT_DIR)/SupportingContent/ + +// The supporting content for this project is split by platform name. +// This value is generally the same as PLATFORM_NAME +PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME = $(__PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME_$(__PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME_IMPL)) + // Use the platform's regular name if there isn't any override. + __PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME_ = $(PLATFORM_NAME) + + // macOS and Mac Catalyst are handled specially (see below). + __PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME_macosx = macosx + __PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME_maccatalyst = maccatalyst + + // Only set __PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME_IMPL if the value needs to be customized (instead of using the default PLATFORM_NAME). + // If PLATFORM_NAME can be used as-is, don't set __PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME_IMPL. + __PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME_IMPL = $(__PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME_IMPL_$(PLATFORM_NAME)) + // Customize macosx based on the value of IS_MACCATALYST. + __PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME_IMPL_macosx = $(__PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME_IMPL_macosx_$(IS_MACCATALYST)) + // If IS_MACCATALYST is YES, then use the value maccatalyst. + __PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME_IMPL_macosx_YES = maccatalyst + // If IS_MACCATALYST is NO (or unset), then use the value macosx. + __PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME_IMPL_macosx_NO = macosx + __PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME_IMPL_macosx_ = $(__PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME_IMPL_macosx_NO) + +// The path to the directory containing the Swift Playgrounds frameworks (i.e. PlaygroundSupport and PlaygroundBluetooth). +// By default, these are stored in the PlaygroundsFrameworks directory in the supporting content directory. +PLAYGROUNDS_FRAMEWORKS_DIR = $(SUPPORTING_CONTENT_DIR)/PlaygroundsFrameworks/$(PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME)/ + +// The path to the directory containing other supporting frameworks. +// These are not usable in the Playground Book sources, but are usable in (and used by) LiveViewTestApp. +// By default, these are stored in the OtherFrameworks directory in the supporting content directory. +OTHER_FRAMEWORKS_DIR = $(SUPPORTING_CONTENT_DIR)/OtherFrameworks/$(PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME)/ + +// Ensure we include PLAYGROUNDS_FRAMEWORKS_DIR in the system frameworks search path. +// All targets should be able to import the Swift Playgrounds frameworks. +SYSTEM_FRAMEWORK_SEARCH_PATHS = "$(PLAYGROUNDS_FRAMEWORKS_DIR)" $(inherited) + +// Since Swift Playgrounds only supports 64-bit devices, this project can only be built for 64-bit devices as well. +ARCHS = $(ARCHS_STANDARD_64_BIT) + +// Since Swift Playgrounds only supports iPad, this project should only be built targeting iPad as well. +TARGETED_DEVICE_FAMILY = 2 + +// The supporting frameworks do not contain bitcode, so disable bitcode generation for this project. +ENABLE_BITCODE = NO diff --git a/ConfigFiles/BuildSettings.xcconfig b/ConfigFiles/BuildSettings.xcconfig new file mode 100644 index 0000000..5549b7a --- /dev/null +++ b/ConfigFiles/BuildSettings.xcconfig @@ -0,0 +1,25 @@ +// +// See LICENSE folder for this template’s licensing information. +// +// Abstract: +// A set of build settings which should be edited by the author of this project. +// Other build settings which need to be modified should generally be set in the Project Editor. +// + +// This is the prefix for all bundle identifiers in this project, including the playground book's ContentIdentifier by default. +// This should be updated to something specific to your team. +BUNDLE_IDENTIFIER_PREFIX = cn.gettoset + +// This is the base of the file name for the playground book produced by this project (e.g. "PlaygroundBook" in "PlaygroundBook.playgroundbook"). +PLAYGROUND_BOOK_FILE_NAME = PlaygroundBook + +// This is the ContentIdentifier of the playground book. +// This should be updated to something specific to your team and your specific content. +PLAYGROUND_BOOK_CONTENT_IDENTIFIER = $(BUNDLE_IDENTIFIER_PREFIX).$(PLAYGROUND_BOOK_FILE_NAME:rfc1034identifier) + +// This is the ContentVersion of the playground book. +PLAYGROUND_BOOK_CONTENT_VERSION = 1.0 + +// Additionally, include some base build settings which this project requires but which shouldn't generally be edited. +// Do *not* remove this #include; if you do, the project will fail to build. +#include "Base/BaseBuildSettings.xcconfig" diff --git a/ConfigFiles/Overrides/BookOverridingBuildSettings.xcconfig b/ConfigFiles/Overrides/BookOverridingBuildSettings.xcconfig new file mode 100644 index 0000000..1ca5f7e --- /dev/null +++ b/ConfigFiles/Overrides/BookOverridingBuildSettings.xcconfig @@ -0,0 +1,78 @@ +// +// See LICENSE folder for this template’s licensing information. +// +// Abstract: +// Provide build settings which make the PlaygroundBook "bundle" target build a .playgroundbook. +// + +// +// The build settings in this file typically should *not* be edited or overridden in the PlaygroundBook target. +// + +// Override the SHALLOW_BUNDLE build setting to NO, since the PlaygroundBook target does not produce a shallow bundle. +SHALLOW_BUNDLE = NO + +// The PlaygroundBook target produces a wrapper with the extension ".playgroundbook". +WRAPPER_EXTENSION = playgroundbook + +// Playground books store their contents in the Contents directory, so override the CONTENTS_FOLDER_PATH to point at that path. +CONTENTS_FOLDER_PATH = $(WRAPPER_NAME)/Contents + +// Likewise, the resources for a playground book are stored in PrivateResources directory in the Contents directory. +UNLOCALIZED_RESOURCES_FOLDER_PATH = $(CONTENTS_FOLDER_PATH)/PrivateResources + +// Playground books do not have an Info.plist file. (They do have a Manifest.plist file, but it's handled separately.) +INFOPLIST_FILE = + +// Playground books are not code signed, so disable everything related to signing. +CODE_SIGN_IDENTITY = +CODE_SIGNING_ALLOWED = NO +CODE_SIGNING_REQUIRED = NO +CODE_SIGN_STYLE = Manual + +// The PlaygroundBook target requires that build rules be applied to files in Copy Files build phases, as that's how the Manifest.plist for the book is copied. +APPLY_RULES_IN_COPY_FILES = YES + +// The PlaygroundBook target requires that it build resources for all target devices, not just the active target device. +ENABLE_ONLY_ACTIVE_RESOURCES = NO + +// Ensure that the strings files are copied consistently whether targeting a Mac or an iPad. +STRINGS_FILE_OUTPUT_ENCODING = binary + +// Ensure that xibs, storyboards, asset catalogs, etc. are compiled consistently whether targeting a Mac or an iPad. +PLAYGROUND_BOOK_COMPILE_MACCATALYST_RESOURCES_LIKE_IOS = YES + +RESOURCES_PLATFORM_NAME[sdk=macosx*] = $(__RESOURCES_PLATFORM_NAME_macosx_$(IS_MACCATALYST)) + __RESOURCES_PLATFORM_NAME_macosx_NO = macosx + __RESOURCES_PLATFORM_NAME_macosx_ = macosx + __RESOURCES_PLATFORM_NAME_macosx_YES = $(__RESOURCES_PLATFORM_NAME_macosx_YES_$(PLAYGROUND_BOOK_COMPILE_MACCATALYST_RESOURCES_LIKE_IOS)) + __RESOURCES_PLATFORM_NAME_macosx_YES_YES = iphoneos + __RESOURCES_PLATFORM_NAME_macosx_YES_NO = macosx + +RESOURCES_UI_FRAMEWORK_FAMILY[sdk=macosx*] = $(__RESOURCES_UI_FRAMEWORK_FAMILY_macosx_$(IS_MACCATALYST)) + __RESOURCES_UI_FRAMEWORK_FAMILY_macosx_NO = + __RESOURCES_UI_FRAMEWORK_FAMILY_macosx_ = + __RESOURCES_UI_FRAMEWORK_FAMILY_macosx_YES = $(__RESOURCES_UI_FRAMEWORK_FAMILY_macosx_YES_$(PLAYGROUND_BOOK_COMPILE_MACCATALYST_RESOURCES_LIKE_IOS)) + __RESOURCES_UI_FRAMEWORK_FAMILY_macosx_YES_YES = + __RESOURCES_UI_FRAMEWORK_FAMILY_macosx_YES_NO = uikit + +// Wrap DEPLOYMENT_TARGET_SETTING_NAME in a way that allows customization for platforms which can't use the default value. +__PLAYGROUND_BOOK_DEPLOYMENT_TARGET_SETTING_NAME = $(__PLAYGROUND_BOOK_DEPLOYMENT_TARGET_SETTING_NAME_$(__PLAYGROUND_BOOK_DEPLOYMENT_TARGET_SETTING_NAME_IMPL)) + // For platforms which don't need custom behavior, use DEPLOYMENT_TARGET_SETTING_NAME. + __PLAYGROUND_BOOK_DEPLOYMENT_TARGET_SETTING_NAME_ = $(DEPLOYMENT_TARGET_SETTING_NAME) + + // For Mac Catalyst, use IPHONEOS_DEPLOYMENT_TARGET instead of MACOSX_DEPLOYMENT_TARGET. + __PLAYGROUND_BOOK_DEPLOYMENT_TARGET_SETTING_NAME_maccatalyst = IPHONEOS_DEPLOYMENT_TARGET + + // __PLAYGROUND_BOOK_DEPLOYMENT_TARGET_SETTING_NAME_IMPL is set to the platform name for platforms which need custom behavior. + __PLAYGROUND_BOOK_DEPLOYMENT_TARGET_SETTING_NAME_IMPL = $(__PLAYGROUND_BOOK_DEPLOYMENT_TARGET_SETTING_NAME_IMPL_$(PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME)) + __PLAYGROUND_BOOK_DEPLOYMENT_TARGET_SETTING_NAME_IMPL_maccatalyst = maccatalyst + +// The value for DeploymentTarget in the playground book's Manifest.plist, derived from the current platform and the current platform's deployment target for the PlaygroundBook target in the Xcode project. +PLAYGROUND_BOOK_DEPLOYMENT_TARGET_FOR_MANIFEST = $(__PLAYGROUND_BOOK_DEPLOYMENT_TARGET_PLATFORM_IDENTIFIER)$($(__PLAYGROUND_BOOK_DEPLOYMENT_TARGET_SETTING_NAME)) + +__PLAYGROUND_BOOK_DEPLOYMENT_TARGET_PLATFORM_IDENTIFIER = $(__PLAYGROUND_BOOK_DEPLOYMENT_TARGET_PLATFORM_IDENTIFIER_$(PLAYGROUNDBOOKTEMPLATE_PLATFORM_NAME)) +__PLAYGROUND_BOOK_DEPLOYMENT_TARGET_PLATFORM_IDENTIFIER_iphoneos = ios +__PLAYGROUND_BOOK_DEPLOYMENT_TARGET_PLATFORM_IDENTIFIER_iphonesimulator = ios +// The playground book format treats iOS and Mac Catalyst the same, so use ios for this value. +__PLAYGROUND_BOOK_DEPLOYMENT_TARGET_PLATFORM_IDENTIFIER_maccatalyst = ios diff --git a/ConfigFiles/Overrides/ModuleOverridingBuildSettings.xcconfig b/ConfigFiles/Overrides/ModuleOverridingBuildSettings.xcconfig new file mode 100644 index 0000000..c507972 --- /dev/null +++ b/ConfigFiles/Overrides/ModuleOverridingBuildSettings.xcconfig @@ -0,0 +1,37 @@ +// +// See LICENSE folder for this template’s licensing information. +// +// Abstract: +// Provide build settings which make a static library target build a .playgroundmodule. +// + +// +// The build settings in this file typically should *not* be edited or overridden in the module target. +// +// To set the build settings up correctly for a new module target, do the following: +// +// 1. Create a new static library target. +// 2. Open the Build Settings tab of the Project Editor and select the new static library target. +// 3. Select the "Customized" option. +// 3. Click on a row in the table of build settings, then select all build settings using Edit > Select All (⌘A). +// 4. Delete all build setting overrides by pressing the Delete key. +// 5. In the Project Editor, select the project itself, and then open the Info tab. +// 6. In the Configurations area, select "ModuleOverridingBuildSettings" for your new static library target for both the Debug and Release configurations. +// +// More details for creating new module targets is available in the README. +// + +// Disable code signing for this target. +CODE_SIGN_IDENTITIY = +CODE_SIGN_STYLE = Manual + +// Ensure that this target supports Mac Catalyst. +SUPPORTS_MACCATALYST = YES + +// Disable generation of the Objective-C compatibility header from Swift (i.e. -Swift.h.) +SWIFT_OBJC_INTERFACE_HEADER_NAME = + +// Ensure that the static library is packaged correctly for use in the project. +SKIP_INSTALL = YES +OTHER_LD_FLAGS = -ObjC +PRODUCT_NAME = $(TARGET_NAME) diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..e93c830 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,11 @@ +# MIT License + +Copyright © 2020 Ethan Wong + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +**See [License Folder](./Licences) for detailed license and attributions** diff --git a/Licences/License.md b/Licences/License.md new file mode 100644 index 0000000..d603856 --- /dev/null +++ b/Licences/License.md @@ -0,0 +1,640 @@ +# Licences + +## A Tour of Unicode + +Copyright © 2020 Ethan Wong + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## Third Party Libraries + +### ASCollectionView + +MIT License + +Copyright (c) 2019 Apptek Studios + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +### Difference Kit + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +## Bundled Resources + +### Fonts + +#### Last Resort Font + +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- + +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +#### Noto Fonts + +Copyright 2018 The Noto Project Authors (github.com/googlei18n/noto-fonts) + +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- + +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +#### Noto Emoji + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +### Data Sources + +#### Unicode Data Files + +Copyright © 1991-2021 Unicode, Inc. All rights reserved. + +Distributed under the Terms of Use in https://www.unicode.org/copyright.html + +#### Unicode Common Locale Data Repository + +Copyright © 1991-2021 Unicode, Inc. All rights reserved. +Distributed under the Terms of Use in https://www.unicode.org/copyright.html. + +#### Data for unicode-table.com + +This license describes the conditions of using the data represented in [the unicode-table-data repository](https://github.com/unicode-table/unicode-table-data). + +##### Third-party data + +This is data which is taken from external sources. +The terms of use are governed by the relevant documents of their owners. + +###### Unicode Character Database + +Data taken from [UCD](http://unicode.org/ucd/) belongs to the [Unicode Consortium](http://www.unicode.org/). +To clarify the conditions of use refer to the Unicode® Terms of Use. + +Following files contain data from UCD: + +* `data/blocks.txt` - list of Unicode blocks and their ranges (the key diap). +* `data/related.txt` - list of related characters. +* `data/versions.txt` - versions of the Unicode Standard in which there were specific characters. +* `loc/en/blocks.txt` - names of Unicode blocks in English +* `loc/en/symbols/` - names of Unicode symbols in English + +##### Unicode Table + +Files not listed above is the author's work of the [Unicode Table site](https://unicode-table.com/). + +Copyright © 2012-2017 Unicode Table + +The actual license allows to use a copy of the data without any restrictions and It is free. +DATA IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND EXPRESS OR IMPLIED. +THE AUTHORS OR COPYRIGHT HOLDERS ARE NOT LIABLE FOR ANY CLAIM FOR DAMAGE OR OTHER SITUATION ARISING OUT OF THE USE OR OTHER ACTIONS WITH THE DATA PROVIDED. diff --git a/Licences/License.pdf b/Licences/License.pdf new file mode 100644 index 0000000..0156ca2 Binary files /dev/null and b/Licences/License.pdf differ diff --git a/LiveViewTestApp/AppDelegate.swift b/LiveViewTestApp/AppDelegate.swift new file mode 100644 index 0000000..2817e0d --- /dev/null +++ b/LiveViewTestApp/AppDelegate.swift @@ -0,0 +1,32 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit +import PlaygroundSupport +import LiveViewHost + +@UIApplicationMain +class AppDelegate: LiveViewHost.AppDelegate { + + private let initialChapter = LiveViewChapter.codePointsBlocksAndPlanes + + override func setUpLiveView() -> PlaygroundLiveViewable { + // This method should return a fully-configured live view. This method must be implemented. + // + // The view or view controller returned from this method will be automatically be shown on screen, + // as if it were a live view in Swift Playgrounds. You can control how the live view is shown by + // changing the implementation of the `liveViewConfiguration` property below. + return instantiateLiveViewController(withChapter: .codePointsBlocksAndPlanes) + } + + override var liveViewConfiguration: LiveViewConfiguration { + // Make this property return the configuration of the live view which you desire to test. + // + // Valid values are `.fullScreen`, which simulates when the user has expanded the live + // view to fill the full screen in Swift Playgrounds, and `.sideBySide`, which simulates when + // the live view is shown next to or above the source code editor in Swift Playgrounds. + return .fullScreen + } + +} diff --git a/LiveViewTestApp/Base.lproj/LiveViewTestAppLaunchScreen.storyboard b/LiveViewTestApp/Base.lproj/LiveViewTestAppLaunchScreen.storyboard new file mode 100644 index 0000000..ab8946c --- /dev/null +++ b/LiveViewTestApp/Base.lproj/LiveViewTestAppLaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LiveViewTestApp/Info.plist b/LiveViewTestApp/Info.plist new file mode 100644 index 0000000..cf6f3b4 Binary files /dev/null and b/LiveViewTestApp/Info.plist differ diff --git a/LiveViewTestApp/LiveViewTestApp.entitlements b/LiveViewTestApp/LiveViewTestApp.entitlements new file mode 100644 index 0000000..ee95ab7 --- /dev/null +++ b/LiveViewTestApp/LiveViewTestApp.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/LiveViewTestApp/version.plist b/LiveViewTestApp/version.plist new file mode 100644 index 0000000..239ac4d --- /dev/null +++ b/LiveViewTestApp/version.plist @@ -0,0 +1,18 @@ + + + + + BuildAliasOf + SerenityBookTemplate + BuildVersion + 2 + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + ProjectName + SerenityBookTemplate + SourceVersion + 25000000000000 + + diff --git a/PlaygroundBook.xcodeproj/.xcodesamplecode.plist b/PlaygroundBook.xcodeproj/.xcodesamplecode.plist new file mode 100644 index 0000000..7685bd6 --- /dev/null +++ b/PlaygroundBook.xcodeproj/.xcodesamplecode.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/PlaygroundBook.xcodeproj/project.pbxproj b/PlaygroundBook.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b9e9596 --- /dev/null +++ b/PlaygroundBook.xcodeproj/project.pbxproj @@ -0,0 +1,2638 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + 5E086DD32051E3A3004D8D25 /* Manifest.plist in Copy Book Contents */ = {isa = PBXBuildFile; fileRef = 5E086DCF2051DF0F004D8D25 /* Manifest.plist */; }; + 5E3196F32061DEA40077BBD7 /* LiveViewSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E3196F22061DEA40077BBD7 /* LiveViewSupport.swift */; }; + 5E551EC52371FC3F00784365 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5E551EC42371FC3F00784365 /* Assets.xcassets */; }; + 5E70679F23678DC30094BDEF /* Modules in Copy Book Contents */ = {isa = PBXBuildFile; fileRef = 5E70679E23678DC00094BDEF /* Modules */; }; + 5EA2E3CA2056F35B00416A35 /* LiveViewTestAppLaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EA2E3C82056F35B00416A35 /* LiveViewTestAppLaunchScreen.storyboard */; }; + 5EA2E3DA2056F8D100416A35 /* PlaygroundBluetooth.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5EA2E3D82056F88700416A35 /* PlaygroundBluetooth.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 5EA2E3DB2056F8D100416A35 /* PlaygroundSupport.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5EA2E3D72056F88700416A35 /* PlaygroundSupport.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 5EBEC2F0205B399300975D3F /* LiveViewHost.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5EBEC2EF205B396500975D3F /* LiveViewHost.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 5EF2F97C2054B6E400191409 /* ManifestPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5EF2F97E2054B6E400191409 /* ManifestPlist.strings */; }; + 5EF2F9AE2054BBF300191409 /* BaseLiveViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF2F9AD2054BBF300191409 /* BaseLiveViewController.swift */; }; + 5EF2F9B72054E6FB00191409 /* Chapters in Copy Book Contents */ = {isa = PBXBuildFile; fileRef = 5EF2F9B62054E6F900191409 /* Chapters */; }; + 7100B9F62620C08900ACE8AB /* ViewSizeKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7100B9F52620C08900ACE8AB /* ViewSizeKey.swift */; }; + 7100B9F72620C08900ACE8AB /* ViewSizeKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7100B9F52620C08900ACE8AB /* ViewSizeKey.swift */; }; + 7102C1A126108A100068E8A1 /* CharactersCollectionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7102C1A026108A100068E8A1 /* CharactersCollectionHeaderView.xib */; }; + 7103E09B2619D98400F8F8AF /* UnicodeData.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = 7103E09A2619D98400F8F8AF /* UnicodeData.sqlite */; }; + 7103E09C2619D98400F8F8AF /* UnicodeData.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = 7103E09A2619D98400F8F8AF /* UnicodeData.sqlite */; }; + 7103E0C52619DB6E00F8F8AF /* UnicodeData.momd in Resources */ = {isa = PBXBuildFile; fileRef = 7103E0C42619DB6E00F8F8AF /* UnicodeData.momd */; }; + 7103E0C62619DB6E00F8F8AF /* UnicodeData.momd in Resources */ = {isa = PBXBuildFile; fileRef = 7103E0C42619DB6E00F8F8AF /* UnicodeData.momd */; }; + 7103E0D42619E11700F8F8AF /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7103E0CF2619E11700F8F8AF /* Store.swift */; }; + 7103E0E42619E7AF00F8F8AF /* BlockDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7103E0E32619E7AF00F8F8AF /* BlockDescription.swift */; }; + 7123C41F260046EA001ABF47 /* CharactersCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7123C41E260046EA001ABF47 /* CharactersCollectionViewController.swift */; }; + 7123C428260046FA001ABF47 /* CharactersCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7123C427260046FA001ABF47 /* CharactersCollectionViewCell.swift */; }; + 7123C4522600506B001ABF47 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7123C4512600506B001ABF47 /* UIColor+Extensions.swift */; }; + 71248D4826256B920077F634 /* EmojiBottomTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71248D4726256B920077F634 /* EmojiBottomTitleView.swift */; }; + 71248D4926256B920077F634 /* EmojiBottomTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71248D4726256B920077F634 /* EmojiBottomTitleView.swift */; }; + 71248D5026256C770077F634 /* ZWJFomulaInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715E236E2624852700CC6679 /* ZWJFomulaInputView.swift */; }; + 712A7F12262AF7AE00A7F746 /* ZWJInputListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7143D967262ABD18003E20D1 /* ZWJInputListItem.swift */; }; + 712CA48E25FDC4E200BEF6E4 /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = 712CA48D25FDC4E200BEF6E4 /* Category.swift */; }; + 712CA49F25FDD12B00BEF6E4 /* DataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 712CA49E25FDD12B00BEF6E4 /* DataProvider.swift */; }; + 712CA4A925FDE38200BEF6E4 /* PlaneXrayUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 712CA4A825FDE38200BEF6E4 /* PlaneXrayUIView.swift */; }; + 713249602627EFEB00643B2B /* UnicodeGeneralCategory+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7132495F2627EFEB00643B2B /* UnicodeGeneralCategory+Extensions.swift */; }; + 713249612627EFEB00643B2B /* UnicodeGeneralCategory+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7132495F2627EFEB00643B2B /* UnicodeGeneralCategory+Extensions.swift */; }; + 71356F7026270ED400CDABDA /* CharacterBottomViewModel+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71356F6F26270ED400CDABDA /* CharacterBottomViewModel+Types.swift */; }; + 71356F7126270ED400CDABDA /* CharacterBottomViewModel+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71356F6F26270ED400CDABDA /* CharacterBottomViewModel+Types.swift */; }; + 71356F7926272DA700CDABDA /* String+renderingTrait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71356F7826272DA700CDABDA /* String+renderingTrait.swift */; }; + 71356F7A26272DA700CDABDA /* String+renderingTrait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71356F7826272DA700CDABDA /* String+renderingTrait.swift */; }; + 71356F8226272F2600CDABDA /* RenderingTrait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71356F8126272F2600CDABDA /* RenderingTrait.swift */; }; + 71356F8326272F2600CDABDA /* RenderingTrait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71356F8126272F2600CDABDA /* RenderingTrait.swift */; }; + 71403AF52622BD820047483C /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71403AF42622BD820047483C /* Colors.swift */; }; + 71403AF62622BD820047483C /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71403AF42622BD820047483C /* Colors.swift */; }; + 71403AFE2622BE0D0047483C /* IntroductionToUnicodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71403AFD2622BE0D0047483C /* IntroductionToUnicodeView.swift */; }; + 71403B022622BE380047483C /* IntroductionToUnicodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71403AFD2622BE0D0047483C /* IntroductionToUnicodeView.swift */; }; + 71403B0A2622BEC00047483C /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71403B092622BEC00047483C /* App.swift */; }; + 71403B0B2622BEC00047483C /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71403B092622BEC00047483C /* App.swift */; }; + 71403B142622C49F0047483C /* CateIterable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71403B132622C49F0047483C /* CateIterable+Extensions.swift */; }; + 71403B182622C6120047483C /* CateIterable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71403B132622C49F0047483C /* CateIterable+Extensions.swift */; }; + 71403B1D2622D4240047483C /* UICollectionView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71403B1C2622D4240047483C /* UICollectionView+Extensions.swift */; }; + 71403B1E2622D4240047483C /* UICollectionView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71403B1C2622D4240047483C /* UICollectionView+Extensions.swift */; }; + 71403B2C26230B850047483C /* MapDot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71403B2B26230B850047483C /* MapDot.swift */; }; + 71403B2D26230B850047483C /* MapDot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71403B2B26230B850047483C /* MapDot.swift */; }; + 7141BA15262B3B5D00B0ADBA /* CLDRAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7141BA12262B3B5D00B0ADBA /* CLDRAnnotation.swift */; }; + 7141BA16262B3B5D00B0ADBA /* CLDRAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7141BA12262B3B5D00B0ADBA /* CLDRAnnotation.swift */; }; + 7141BA25262B417700B0ADBA /* ChapterEmojiFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7141BA24262B417700B0ADBA /* ChapterEmojiFont.swift */; }; + 7141BA26262B417700B0ADBA /* ChapterEmojiFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7141BA24262B417700B0ADBA /* ChapterEmojiFont.swift */; }; + 7141BA68262BDEE000B0ADBA /* String+chapterEmoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7141BA67262BDEE000B0ADBA /* String+chapterEmoji.swift */; }; + 7141BA69262BDEE000B0ADBA /* String+chapterEmoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7141BA67262BDEE000B0ADBA /* String+chapterEmoji.swift */; }; + 7143D9302629DD60003E20D1 /* EmojiCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7143D92F2629DD60003E20D1 /* EmojiCell.swift */; }; + 7143D9312629DD60003E20D1 /* EmojiCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7143D92F2629DD60003E20D1 /* EmojiCell.swift */; }; + 7143D94A2629E9B9003E20D1 /* NotoEmoji-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7143D9492629E9B9003E20D1 /* NotoEmoji-Regular.ttf */; }; + 7143D94B2629E9B9003E20D1 /* NotoEmoji-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7143D9492629E9B9003E20D1 /* NotoEmoji-Regular.ttf */; }; + 7143D968262ABD18003E20D1 /* ZWJInputListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7143D967262ABD18003E20D1 /* ZWJInputListItem.swift */; }; + 714AD5FC260DCEE7008C8BB2 /* CodePointsBlocksAndPlanesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718D75EB260DCC850076AAE2 /* CodePointsBlocksAndPlanesView.swift */; }; + 715AA8E8260D95C0003CD334 /* BlockInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715AA8E7260D95C0003CD334 /* BlockInfoView.swift */; }; + 715AA8E9260D95C0003CD334 /* BlockInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715AA8E7260D95C0003CD334 /* BlockInfoView.swift */; }; + 715E2358262484BE00CC6679 /* EmojiTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715E2357262484BE00CC6679 /* EmojiTopView.swift */; }; + 715E2359262484BE00CC6679 /* EmojiTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715E2357262484BE00CC6679 /* EmojiTopView.swift */; }; + 715E2361262484E400CC6679 /* EmojiLargeImputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715E2360262484E400CC6679 /* EmojiLargeImputView.swift */; }; + 715E2362262484E400CC6679 /* EmojiLargeImputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715E2360262484E400CC6679 /* EmojiLargeImputView.swift */; }; + 715E23672624850300CC6679 /* EmojiFormulaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715E23662624850300CC6679 /* EmojiFormulaView.swift */; }; + 715E23682624850300CC6679 /* EmojiFormulaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715E23662624850300CC6679 /* EmojiFormulaView.swift */; }; + 715E236F2624852700CC6679 /* ZWJFomulaInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715E236E2624852700CC6679 /* ZWJFomulaInputView.swift */; }; + 715E237C2624854E00CC6679 /* EmojiGridInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715E237B2624854E00CC6679 /* EmojiGridInputView.swift */; }; + 715E2385262485C800CC6679 /* EmojiBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715E2384262485C800CC6679 /* EmojiBottomView.swift */; }; + 715E2386262485C800CC6679 /* EmojiBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715E2384262485C800CC6679 /* EmojiBottomView.swift */; }; + 71698E32261A9FA000B4A6C7 /* NibInstantiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71698E31261A9FA000B4A6C7 /* NibInstantiable.swift */; }; + 71698E33261A9FA000B4A6C7 /* NibInstantiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71698E31261A9FA000B4A6C7 /* NibInstantiable.swift */; }; + 71698E45261AA3CD00B4A6C7 /* NSManagedObject+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71698E44261AA3CD00B4A6C7 /* NSManagedObject+Extensions.swift */; }; + 71698E46261AA3CD00B4A6C7 /* NSManagedObject+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71698E44261AA3CD00B4A6C7 /* NSManagedObject+Extensions.swift */; }; + 71698E51261AAFAD00B4A6C7 /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7103E0CF2619E11700F8F8AF /* Store.swift */; }; + 71698E69261AAFC300B4A6C7 /* BlockDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7103E0E32619E7AF00F8F8AF /* BlockDescription.swift */; }; + 71698E7B261AD70A00B4A6C7 /* RichTextDescriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71698E7A261AD70A00B4A6C7 /* RichTextDescriptionView.swift */; }; + 71698E86261AD93600B4A6C7 /* RichTextDescriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71698E7A261AD70A00B4A6C7 /* RichTextDescriptionView.swift */; }; + 71698E8D261AE2C900B4A6C7 /* String+formatLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71698E8C261AE2C900B4A6C7 /* String+formatLinks.swift */; }; + 71698E8E261AE2C900B4A6C7 /* String+formatLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71698E8C261AE2C900B4A6C7 /* String+formatLinks.swift */; }; + 71698E9E261AEF8E00B4A6C7 /* PlaneIntroductionXrayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71E52A45260054870064BC6E /* PlaneIntroductionXrayView.swift */; }; + 716C56272601F54B000160FF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA2E3BF2056F35A00416A35 /* AppDelegate.swift */; }; + 716C564E2601FB29000160FF /* WelcomeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 71BE783525FA46BD005D6CCB /* WelcomeViewController.xib */; }; + 7179EB1A26285F020054765B /* EmojiGridInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 715E237B2624854E00CC6679 /* EmojiGridInputView.swift */; }; + 7179EB43262861110054765B /* AnyDifferentiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB342628610F0054765B /* AnyDifferentiable.swift */; }; + 7179EB44262861110054765B /* ArraySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB35262861100054765B /* ArraySection.swift */; }; + 7179EB45262861110054765B /* ContentEquatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB36262861100054765B /* ContentEquatable.swift */; }; + 7179EB46262861110054765B /* Algorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB37262861100054765B /* Algorithm.swift */; }; + 7179EB47262861110054765B /* StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB38262861100054765B /* StagedChangeset.swift */; }; + 7179EB48262861110054765B /* UIKitExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB39262861100054765B /* UIKitExtension.swift */; }; + 7179EB49262861110054765B /* Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB3A262861100054765B /* Changeset.swift */; }; + 7179EB4A262861110054765B /* ElementPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB3B262861100054765B /* ElementPath.swift */; }; + 7179EB4B262861110054765B /* DifferentiableSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB3C262861100054765B /* DifferentiableSection.swift */; }; + 7179EB4F262861110054765B /* ContentIdentifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB41262861100054765B /* ContentIdentifiable.swift */; }; + 7179EB50262861110054765B /* Differentiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB42262861110054765B /* Differentiable.swift */; }; + 7179EB62262861580054765B /* libDifferenceKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7179EB23262860C60054765B /* libDifferenceKit.a */; }; + 7179EB63262861620054765B /* libDifferenceKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7179EB23262860C60054765B /* libDifferenceKit.a */; }; + 7179EBC1262862280054765B /* ASDiffableDataSourceCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB8C262862260054765B /* ASDiffableDataSourceCollectionView.swift */; }; + 7179EBC2262862280054765B /* ASDiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB8D262862260054765B /* ASDiffableDataSource.swift */; }; + 7179EBC3262862280054765B /* ASCellContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB8E262862260054765B /* ASCellContext.swift */; }; + 7179EBC5262862280054765B /* ASSectionDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB91262862270054765B /* ASSectionDataSource.swift */; }; + 7179EBC6262862280054765B /* ASSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB92262862270054765B /* ASSection.swift */; }; + 7179EBC7262862280054765B /* ASHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB93262862270054765B /* ASHostingController.swift */; }; + 7179EBC8262862280054765B /* ASCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB94262862270054765B /* ASCollectionView.swift */; }; + 7179EBC9262862280054765B /* ASOptionalSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB96262862270054765B /* ASOptionalSize.swift */; }; + 7179EBCA262862280054765B /* ShrinkToFitWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB97262862270054765B /* ShrinkToFitWrapper.swift */; }; + 7179EBCB262862280054765B /* Binding+Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB98262862270054765B /* Binding+Sequence.swift */; }; + 7179EBCC262862280054765B /* RandomAccessCollection+Safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB99262862270054765B /* RandomAccessCollection+Safe.swift */; }; + 7179EBCD262862280054765B /* ASPriorityCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB9A262862270054765B /* ASPriorityCache.swift */; }; + 7179EBCE262862280054765B /* ASSelfSizingSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB9B262862270054765B /* ASSelfSizingSettings.swift */; }; + 7179EBCF262862280054765B /* ASIndexedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB9C262862270054765B /* ASIndexedDictionary.swift */; }; + 7179EBD0262862280054765B /* GlobalConvenienceFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB9D262862270054765B /* GlobalConvenienceFunctions.swift */; }; + 7179EBD1262862280054765B /* UIScrollView+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EB9F262862270054765B /* UIScrollView+Convenience.swift */; }; + 7179EBD2262862280054765B /* UICollectionView+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBA0262862270054765B /* UICollectionView+Convenience.swift */; }; + 7179EBD3262862280054765B /* UIView+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBA1262862270054765B /* UIView+Convenience.swift */; }; + 7179EBD4262862280054765B /* ASCollectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBA3262862270054765B /* ASCollectionViewLayout.swift */; }; + 7179EBD7262862280054765B /* ASDragDropConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBA7262862270054765B /* ASDragDropConfig.swift */; }; + 7179EBD8262862280054765B /* ClosureTypeAliases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBA8262862270054765B /* ClosureTypeAliases.swift */; }; + 7179EBD9262862280054765B /* ASDragDropConfig+Public.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBA9262862270054765B /* ASDragDropConfig+Public.swift */; }; + 7179EBDA262862280054765B /* ASSection+Modifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBAA262862270054765B /* ASSection+Modifiers.swift */; }; + 7179EBDB262862280054765B /* ASCollectionViewDecoration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBAC262862270054765B /* ASCollectionViewDecoration.swift */; }; + 7179EBDC262862280054765B /* ASCollectionViewSupplementaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBAD262862270054765B /* ASCollectionViewSupplementaryView.swift */; }; + 7179EBDD262862280054765B /* ASSupplementaryCellID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBAE262862270054765B /* ASSupplementaryCellID.swift */; }; + 7179EBDF262862280054765B /* ASCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBB0262862270054765B /* ASCollectionViewCell.swift */; }; + 7179EBE2262862280054765B /* ViewArrayBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBB4262862280054765B /* ViewArrayBuilder.swift */; }; + 7179EBE3262862280054765B /* SectionArrayBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBB5262862280054765B /* SectionArrayBuilder.swift */; }; + 7179EBE4262862280054765B /* ASCollectionView+Initialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBB6262862280054765B /* ASCollectionView+Initialisers.swift */; }; + 7179EBE5262862280054765B /* ASSection+Initialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBB7262862280054765B /* ASSection+Initialisers.swift */; }; + 7179EBE7262862280054765B /* AS_UICollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBBA262862280054765B /* AS_UICollectionView.swift */; }; + 7179EBE8262862280054765B /* ASCollectionView+Modifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBBB262862280054765B /* ASCollectionView+Modifiers.swift */; }; + 7179EBE9262862280054765B /* EnvironmentKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBBD262862280054765B /* EnvironmentKeys.swift */; }; + 7179EBEA262862280054765B /* ASCollectionViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7179EBBF262862280054765B /* ASCollectionViewDelegate.swift */; }; + 7179EBF42628625B0054765B /* libASCollectionView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7179EB782628620B0054765B /* libASCollectionView.a */; }; + 7179EBF5262862610054765B /* libASCollectionView.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7179EB782628620B0054765B /* libASCollectionView.a */; }; + 71816617261203E100614F25 /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71816616261203E100614F25 /* MapView.swift */; }; + 7185779F26294E7C002D2D44 /* NotoSansCoptic-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7185779D26294E7C002D2D44 /* NotoSansCoptic-Regular.ttf */; }; + 718577A726294F7E002D2D44 /* NotoSansSyriac-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 718577A526294F7E002D2D44 /* NotoSansSyriac-Regular.ttf */; }; + 718577AF26295003002D2D44 /* NotoSansMalayalam-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 718577AD26295003002D2D44 /* NotoSansMalayalam-Regular.ttf */; }; + 718577B726295076002D2D44 /* NotoSansLao-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 718577B526295076002D2D44 /* NotoSansLao-Regular.ttf */; }; + 718577BF262951CA002D2D44 /* NotoSansGeorgian-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 718577BD262951CA002D2D44 /* NotoSansGeorgian-Regular.ttf */; }; + 718577C72629525F002D2D44 /* NotoSansCanadianAboriginal-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 718577C52629525F002D2D44 /* NotoSansCanadianAboriginal-Regular.ttf */; }; + 718577CF26295385002D2D44 /* NotoSansSymbols2-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 718577CD26295385002D2D44 /* NotoSansSymbols2-Regular.ttf */; }; + 718577D726295492002D2D44 /* NotoSansSymbols-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 718577D526295491002D2D44 /* NotoSansSymbols-Regular.ttf */; }; + 718577EC2629786F002D2D44 /* LastResort-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7191D0E12601020F004D7326 /* LastResort-Regular.ttf */; }; + 718577ED2629786F002D2D44 /* NotoSansSymbols2-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 718577CD26295385002D2D44 /* NotoSansSymbols2-Regular.ttf */; }; + 718577EE2629786F002D2D44 /* NotoSansGeorgian-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 718577BD262951CA002D2D44 /* NotoSansGeorgian-Regular.ttf */; }; + 718577EF2629786F002D2D44 /* NotoSansCoptic-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7185779D26294E7C002D2D44 /* NotoSansCoptic-Regular.ttf */; }; + 718577F02629786F002D2D44 /* NotoSansCanadianAboriginal-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 718577C52629525F002D2D44 /* NotoSansCanadianAboriginal-Regular.ttf */; }; + 718577F12629786F002D2D44 /* NotoSansLao-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 718577B526295076002D2D44 /* NotoSansLao-Regular.ttf */; }; + 718577F22629786F002D2D44 /* NotoSansSyriac-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 718577A526294F7E002D2D44 /* NotoSansSyriac-Regular.ttf */; }; + 718577F32629786F002D2D44 /* NotoSansMalayalam-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 718577AD26295003002D2D44 /* NotoSansMalayalam-Regular.ttf */; }; + 718577F42629786F002D2D44 /* NotoSansSymbols-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 718577D526295491002D2D44 /* NotoSansSymbols-Regular.ttf */; }; + 71857805262985FC002D2D44 /* DebugAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71857804262985FC002D2D44 /* DebugAlert.swift */; }; + 7185781526298696002D2D44 /* DebugAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71857804262985FC002D2D44 /* DebugAlert.swift */; }; + 7185782A262986E7002D2D44 /* NotoSansOldSogdian-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7185781B262986E5002D2D44 /* NotoSansOldSogdian-Regular.ttf */; }; + 7185782B262986E7002D2D44 /* NotoSansOldSogdian-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7185781B262986E5002D2D44 /* NotoSansOldSogdian-Regular.ttf */; }; + 7185782E262986E7002D2D44 /* NotoSansNushu-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7185781D262986E6002D2D44 /* NotoSansNushu-Regular.ttf */; }; + 7185782F262986E7002D2D44 /* NotoSansNushu-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7185781D262986E6002D2D44 /* NotoSansNushu-Regular.ttf */; }; + 71857830262986E7002D2D44 /* NotoSansElymaic-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7185781E262986E6002D2D44 /* NotoSansElymaic-Regular.ttf */; }; + 71857831262986E7002D2D44 /* NotoSansElymaic-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7185781E262986E6002D2D44 /* NotoSansElymaic-Regular.ttf */; }; + 71857832262986E7002D2D44 /* NotoSansGunjalaGondi-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7185781F262986E6002D2D44 /* NotoSansGunjalaGondi-Regular.ttf */; }; + 71857833262986E7002D2D44 /* NotoSansGunjalaGondi-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7185781F262986E6002D2D44 /* NotoSansGunjalaGondi-Regular.ttf */; }; + 71857834262986E7002D2D44 /* NotoSansIndicSiyaqNumbers-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857820262986E6002D2D44 /* NotoSansIndicSiyaqNumbers-Regular.ttf */; }; + 71857835262986E7002D2D44 /* NotoSansIndicSiyaqNumbers-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857820262986E6002D2D44 /* NotoSansIndicSiyaqNumbers-Regular.ttf */; }; + 71857836262986E7002D2D44 /* NotoSansMongolian-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857821262986E6002D2D44 /* NotoSansMongolian-Regular.ttf */; }; + 71857837262986E7002D2D44 /* NotoSansMongolian-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857821262986E6002D2D44 /* NotoSansMongolian-Regular.ttf */; }; + 71857838262986E7002D2D44 /* NotoSansAnatolianHieroglyphs-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857822262986E6002D2D44 /* NotoSansAnatolianHieroglyphs-Regular.ttf */; }; + 71857839262986E7002D2D44 /* NotoSansAnatolianHieroglyphs-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857822262986E6002D2D44 /* NotoSansAnatolianHieroglyphs-Regular.ttf */; }; + 7185783A262986E7002D2D44 /* NotoSansDeseret-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857823262986E6002D2D44 /* NotoSansDeseret-Regular.ttf */; }; + 7185783B262986E7002D2D44 /* NotoSansDeseret-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857823262986E6002D2D44 /* NotoSansDeseret-Regular.ttf */; }; + 7185783C262986E7002D2D44 /* NotoSansZanabazarSquare-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857824262986E6002D2D44 /* NotoSansZanabazarSquare-Regular.ttf */; }; + 7185783D262986E7002D2D44 /* NotoSansZanabazarSquare-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857824262986E6002D2D44 /* NotoSansZanabazarSquare-Regular.ttf */; }; + 7185783E262986E7002D2D44 /* NotoSansMedefaidrin-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857825262986E6002D2D44 /* NotoSansMedefaidrin-Regular.ttf */; }; + 7185783F262986E7002D2D44 /* NotoSansMedefaidrin-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857825262986E6002D2D44 /* NotoSansMedefaidrin-Regular.ttf */; }; + 71857840262986E7002D2D44 /* NotoSansMasaramGondi-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857826262986E7002D2D44 /* NotoSansMasaramGondi-Regular.ttf */; }; + 71857841262986E7002D2D44 /* NotoSansMasaramGondi-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857826262986E7002D2D44 /* NotoSansMasaramGondi-Regular.ttf */; }; + 71857842262986E7002D2D44 /* NotoSansSogdian-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857827262986E7002D2D44 /* NotoSansSogdian-Regular.ttf */; }; + 71857843262986E7002D2D44 /* NotoSansSogdian-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857827262986E7002D2D44 /* NotoSansSogdian-Regular.ttf */; }; + 71857844262986E7002D2D44 /* NotoSansSoyombo-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857828262986E7002D2D44 /* NotoSansSoyombo-Regular.ttf */; }; + 71857845262986E7002D2D44 /* NotoSansSoyombo-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71857828262986E7002D2D44 /* NotoSansSoyombo-Regular.ttf */; }; + 7185785D2629A23E002D2D44 /* emoji-input.json in Resources */ = {isa = PBXBuildFile; fileRef = 7185785C2629A23E002D2D44 /* emoji-input.json */; }; + 7185785E2629A23E002D2D44 /* emoji-input.json in Resources */ = {isa = PBXBuildFile; fileRef = 7185785C2629A23E002D2D44 /* emoji-input.json */; }; + 718578652629A451002D2D44 /* EmojiInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718578642629A451002D2D44 /* EmojiInput.swift */; }; + 718578752629AB20002D2D44 /* EmojiInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718578642629A451002D2D44 /* EmojiInput.swift */; }; + 7185787E2629AE1A002D2D44 /* NotoSerifYezidi-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7185787B2629AE1A002D2D44 /* NotoSerifYezidi-Regular.ttf */; }; + 7185787F2629AE1A002D2D44 /* NotoSerifYezidi-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7185787B2629AE1A002D2D44 /* NotoSerifYezidi-Regular.ttf */; }; + 718578802629AE1A002D2D44 /* NotoMusic-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7185787C2629AE1A002D2D44 /* NotoMusic-Regular.ttf */; }; + 718578812629AE1A002D2D44 /* NotoMusic-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7185787C2629AE1A002D2D44 /* NotoMusic-Regular.ttf */; }; + 718578822629AE1A002D2D44 /* NotoSansSignWriting-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7185787D2629AE1A002D2D44 /* NotoSansSignWriting-Regular.ttf */; }; + 718578832629AE1A002D2D44 /* NotoSansSignWriting-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7185787D2629AE1A002D2D44 /* NotoSansSignWriting-Regular.ttf */; }; + 718D75B4260DAA060076AAE2 /* planes.json in Resources */ = {isa = PBXBuildFile; fileRef = 718D75B2260DAA060076AAE2 /* planes.json */; }; + 718D75B5260DAA060076AAE2 /* categories.json in Resources */ = {isa = PBXBuildFile; fileRef = 718D75B3260DAA060076AAE2 /* categories.json */; }; + 718D75BC260DAA0B0076AAE2 /* planes.json in Resources */ = {isa = PBXBuildFile; fileRef = 718D75B2260DAA060076AAE2 /* planes.json */; }; + 718D75BD260DAA0B0076AAE2 /* categories.json in Resources */ = {isa = PBXBuildFile; fileRef = 718D75B3260DAA060076AAE2 /* categories.json */; }; + 718D75C5260DAB230076AAE2 /* FileHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718D75C4260DAB230076AAE2 /* FileHelper.swift */; }; + 718D75C6260DAB230076AAE2 /* FileHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718D75C4260DAB230076AAE2 /* FileHelper.swift */; }; + 718D7600260DCE910076AAE2 /* CodePointsBlocksAndPlanesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 718D75EB260DCC850076AAE2 /* CodePointsBlocksAndPlanesView.swift */; }; + 7191D0D826010127004D7326 /* UIFont+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7191D0D726010127004D7326 /* UIFont+Extensions.swift */; }; + 7193AE0A25FB20EF00B459A6 /* Block.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7193AE0925FB20EF00B459A6 /* Block.swift */; }; + 7193AE1425FB21FA00B459A6 /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7193AE1325FB21FA00B459A6 /* Language.swift */; }; + 7193AE1A25FB235100B459A6 /* Country.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7193AE1925FB235100B459A6 /* Country.swift */; }; + 7193AE2725FB25BB00B459A6 /* Plane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7193AE2625FB25BB00B459A6 /* Plane.swift */; }; + 719BC421260D72D50046C3ED /* CharacterBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A63F2A2601D11700519C43 /* CharacterBottomView.swift */; }; + 719BC422260D72D60046C3ED /* CharacterBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A63F2A2601D11700519C43 /* CharacterBottomView.swift */; }; + 719BC42F260D73030046C3ED /* PlaneBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71AFA0F725FD2091008E5A4C /* PlaneBottomView.swift */; }; + 719BC436260D73080046C3ED /* PlaneBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71AFA0F725FD2091008E5A4C /* PlaneBottomView.swift */; }; + 719BC44B260D732F0046C3ED /* PlaneIntroductionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71AFA0D125FD1CAF008E5A4C /* PlaneIntroductionView.swift */; }; + 719BC452260D73320046C3ED /* BlocksListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71AFA0C525FD1A47008E5A4C /* BlocksListView.swift */; }; + 719BC459260D73350046C3ED /* BlocksListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71AFA0BA25FD183C008E5A4C /* BlocksListItemView.swift */; }; + 719BC46E260D736B0046C3ED /* PlaneXrayUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 712CA4A825FDE38200BEF6E4 /* PlaneXrayUIView.swift */; }; + 719BC47C260D73960046C3ED /* CharactersCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7123C41E260046EA001ABF47 /* CharactersCollectionViewController.swift */; }; + 719BC483260D73990046C3ED /* CharactersCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7123C427260046FA001ABF47 /* CharactersCollectionViewCell.swift */; }; + 719BC491260D73B40046C3ED /* Plane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7193AE2625FB25BB00B459A6 /* Plane.swift */; }; + 719BC5B9260D74670046C3ED /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = 712CA48D25FDC4E200BEF6E4 /* Category.swift */; }; + 719BC5C0260D74850046C3ED /* DataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 712CA49E25FDD12B00BEF6E4 /* DataProvider.swift */; }; + 719BC5C7260D749A0046C3ED /* LiveViewSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E3196F22061DEA40077BBD7 /* LiveViewSupport.swift */; }; + 719BC5D5260D74B40046C3ED /* Block.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7193AE0925FB20EF00B459A6 /* Block.swift */; }; + 719BC5DC260D74BC0046C3ED /* UIFont+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7191D0D726010127004D7326 /* UIFont+Extensions.swift */; }; + 719BC5E3260D74C90046C3ED /* UIViewController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A63F192601CE0B00519C43 /* UIViewController+Extensions.swift */; }; + 719BC5EA260D74CE0046C3ED /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7193AE1325FB21FA00B459A6 /* Language.swift */; }; + 719BC5F1260D74D50046C3ED /* Country.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7193AE1925FB235100B459A6 /* Country.swift */; }; + 719BC5F8260D74E70046C3ED /* BaseLiveViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF2F9AD2054BBF300191409 /* BaseLiveViewController.swift */; }; + 719BC611260D75830046C3ED /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7123C4512600506B001ABF47 /* UIColor+Extensions.swift */; }; + 719DEDF425FE56DE007DE3B6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5E551EC42371FC3F00784365 /* Assets.xcassets */; }; + 71A5B12C262BFAEB001FC1AF /* License.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 71A5B12B262BFAEB001FC1AF /* License.pdf */; }; + 71A5B131262BFAEE001FC1AF /* License.pdf in Copy Book Contents */ = {isa = PBXBuildFile; fileRef = 71A5B12B262BFAEB001FC1AF /* License.pdf */; }; + 71A5B14B262C6040001FC1AF /* CharacterBottomView+contextConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A5B14A262C6040001FC1AF /* CharacterBottomView+contextConfiguration.swift */; }; + 71A5B14C262C6040001FC1AF /* CharacterBottomView+contextConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A5B14A262C6040001FC1AF /* CharacterBottomView+contextConfiguration.swift */; }; + 71A5B161262D2D78001FC1AF /* 1-2-user-interface.png in Resources */ = {isa = PBXBuildFile; fileRef = 71A5B160262D2D78001FC1AF /* 1-2-user-interface.png */; }; + 71A5B167262D3359001FC1AF /* 1-3-plane-view.png in Resources */ = {isa = PBXBuildFile; fileRef = 71A5B166262D3359001FC1AF /* 1-3-plane-view.png */; }; + 71A5B171262D3393001FC1AF /* 1-1-character-combining.png in Resources */ = {isa = PBXBuildFile; fileRef = 71A5B170262D3392001FC1AF /* 1-1-character-combining.png */; }; + 71A5B179262D63CF001FC1AF /* 2-1-utf-32.png in Resources */ = {isa = PBXBuildFile; fileRef = 71A5B176262D63CE001FC1AF /* 2-1-utf-32.png */; }; + 71A5B17A262D63CF001FC1AF /* 2-2-utf-16.png in Resources */ = {isa = PBXBuildFile; fileRef = 71A5B177262D63CE001FC1AF /* 2-2-utf-16.png */; }; + 71A5B17B262D63CF001FC1AF /* 2-3-utf-8.png in Resources */ = {isa = PBXBuildFile; fileRef = 71A5B178262D63CE001FC1AF /* 2-3-utf-8.png */; }; + 71A5B185262D7742001FC1AF /* 3-1-joining.png in Resources */ = {isa = PBXBuildFile; fileRef = 71A5B184262D7742001FC1AF /* 3-1-joining.png */; }; + 71A5B18D262D9CA2001FC1AF /* 3-4-variations.png in Resources */ = {isa = PBXBuildFile; fileRef = 71A5B18A262D9CA2001FC1AF /* 3-4-variations.png */; }; + 71A5B18E262D9CA2001FC1AF /* 3-3-flags-and-keycaps.png in Resources */ = {isa = PBXBuildFile; fileRef = 71A5B18B262D9CA2001FC1AF /* 3-3-flags-and-keycaps.png */; }; + 71A5B18F262D9CA2001FC1AF /* 3-2-modifiers.png in Resources */ = {isa = PBXBuildFile; fileRef = 71A5B18C262D9CA2001FC1AF /* 3-2-modifiers.png */; }; + 71A5B195262DA08C001FC1AF /* 1-4-last-resort.png in Resources */ = {isa = PBXBuildFile; fileRef = 71A5B194262DA08C001FC1AF /* 1-4-last-resort.png */; }; + 71A63F1A2601CE0B00519C43 /* UIViewController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A63F192601CE0B00519C43 /* UIViewController+Extensions.swift */; }; + 71A63F342601D12E00519C43 /* Character3DView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A63F332601D12E00519C43 /* Character3DView.swift */; }; + 71A63F3D2601D28E00519C43 /* CharacterIntroductionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A63F3C2601D28E00519C43 /* CharacterIntroductionView.swift */; }; + 71A74066261748A900ACC64D /* View+Modifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A74065261748A900ACC64D /* View+Modifiers.swift */; }; + 71A74067261748A900ACC64D /* View+Modifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A74065261748A900ACC64D /* View+Modifiers.swift */; }; + 71A7408326175BC600ACC64D /* PlaneXrayUIViewMaskLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A7408226175BC600ACC64D /* PlaneXrayUIViewMaskLayer.swift */; }; + 71A7408F26175BEE00ACC64D /* PlaneXrayBlocksLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A7408E26175BEE00ACC64D /* PlaneXrayBlocksLayer.swift */; }; + 71A7409526175C4B00ACC64D /* PlaneXrayUIViewMaskLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A7408226175BC600ACC64D /* PlaneXrayUIViewMaskLayer.swift */; }; + 71A7409B26175C4D00ACC64D /* PlaneXrayBlocksLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A7408E26175BEE00ACC64D /* PlaneXrayBlocksLayer.swift */; }; + 71A740AF2618B5BF00ACC64D /* Character3DView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A63F332601D12E00519C43 /* Character3DView.swift */; }; + 71A740B52618B68300ACC64D /* CharacterIntroductionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A63F3C2601D28E00519C43 /* CharacterIntroductionView.swift */; }; + 71A740C626196BD700ACC64D /* String+codePoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A740C526196BD700ACC64D /* String+codePoint.swift */; }; + 71A740C726196BD700ACC64D /* String+codePoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A740C526196BD700ACC64D /* String+codePoint.swift */; }; + 71AFA0BB25FD183C008E5A4C /* BlocksListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71AFA0BA25FD183C008E5A4C /* BlocksListItemView.swift */; }; + 71AFA0C625FD1A47008E5A4C /* BlocksListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71AFA0C525FD1A47008E5A4C /* BlocksListView.swift */; }; + 71AFA0D225FD1CAF008E5A4C /* PlaneIntroductionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71AFA0D125FD1CAF008E5A4C /* PlaneIntroductionView.swift */; }; + 71B02D2E262BE2B60037FA30 /* ChapterThreeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B02D26262BE2B60037FA30 /* ChapterThreeState.swift */; }; + 71B02D2F262BE2B60037FA30 /* ChapterThreeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B02D26262BE2B60037FA30 /* ChapterThreeState.swift */; }; + 71B02D30262BE2B60037FA30 /* EncodeWithUtfView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B02D28262BE2B60037FA30 /* EncodeWithUtfView.swift */; }; + 71B02D31262BE2B60037FA30 /* EncodeWithUtfView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B02D28262BE2B60037FA30 /* EncodeWithUtfView.swift */; }; + 71B02D32262BE2B60037FA30 /* CodeSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B02D2B262BE2B60037FA30 /* CodeSlider.swift */; }; + 71B02D33262BE2B60037FA30 /* CodeSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B02D2B262BE2B60037FA30 /* CodeSlider.swift */; }; + 71B02D34262BE2B60037FA30 /* SliderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B02D2C262BE2B60037FA30 /* SliderCell.swift */; }; + 71B02D35262BE2B60037FA30 /* SliderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B02D2C262BE2B60037FA30 /* SliderCell.swift */; }; + 71B02D36262BE2B60037FA30 /* SliderGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B02D2D262BE2B60037FA30 /* SliderGroup.swift */; }; + 71B02D37262BE2B60037FA30 /* SliderGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B02D2D262BE2B60037FA30 /* SliderGroup.swift */; }; + 71B1174E2627051400BDC1A5 /* SampleText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B1174D2627051400BDC1A5 /* SampleText.swift */; }; + 71B1174F2627051400BDC1A5 /* SampleText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B1174D2627051400BDC1A5 /* SampleText.swift */; }; + 71B11754262706E300BDC1A5 /* View+EnumeratedForEach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B11753262706E300BDC1A5 /* View+EnumeratedForEach.swift */; }; + 71B11755262706E300BDC1A5 /* View+EnumeratedForEach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B11753262706E300BDC1A5 /* View+EnumeratedForEach.swift */; }; + 71BB759C2626AD3100FA11C3 /* sample_text_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 71BB759B2626AD3100FA11C3 /* sample_text_data.json */; }; + 71BB759D2626AD3100FA11C3 /* sample_text_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 71BB759B2626AD3100FA11C3 /* sample_text_data.json */; }; + 71BE77F725FA0FAF005D6CCB /* IntroductionToUnicodeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 71BE77F425FA0FAF005D6CCB /* IntroductionToUnicodeViewController.xib */; }; + 71BE77FD25FA0FC1005D6CCB /* EncodingWithUtfViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 71BE77FC25FA0FC1005D6CCB /* EncodingWithUtfViewController.xib */; }; + 71BE783625FA46BD005D6CCB /* WelcomeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 71BE783525FA46BD005D6CCB /* WelcomeViewController.xib */; }; + 71CA942A260E403600F04F9F /* View+RoundedCorner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CA9429260E403600F04F9F /* View+RoundedCorner.swift */; }; + 71CA9434260E409300F04F9F /* ChapterTwoState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CA9433260E409300F04F9F /* ChapterTwoState.swift */; }; + 71CD670F26274D65001D3693 /* UIImage+backgroundColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CD670E26274D64001D3693 /* UIImage+backgroundColor.swift */; }; + 71CD671026274D65001D3693 /* UIImage+backgroundColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CD670E26274D64001D3693 /* UIImage+backgroundColor.swift */; }; + 71CD671526275DBF001D3693 /* UnicodeScalar+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CD671426275DBF001D3693 /* UnicodeScalar+Extensions.swift */; }; + 71CD671626275DBF001D3693 /* UnicodeScalar+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CD671426275DBF001D3693 /* UnicodeScalar+Extensions.swift */; }; + 71CE628B260F4E38003F94BE /* blocks.json in Resources */ = {isa = PBXBuildFile; fileRef = 71CE628A260F4E38003F94BE /* blocks.json */; }; + 71CE628C260F4E39003F94BE /* blocks.json in Resources */ = {isa = PBXBuildFile; fileRef = 71CE628A260F4E38003F94BE /* blocks.json */; }; + 71CE62B32610805D003F94BE /* View+RoundedCorner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CA9429260E403600F04F9F /* View+RoundedCorner.swift */; }; + 71CE62CA26108507003F94BE /* CharactersCollectionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 71CE62C926108507003F94BE /* CharactersCollectionHeaderView.xib */; }; + 71CE62D326108519003F94BE /* CharactersCollectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CE62D226108519003F94BE /* CharactersCollectionHeaderView.swift */; }; + 71CE62D426108519003F94BE /* CharactersCollectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CE62D226108519003F94BE /* CharactersCollectionHeaderView.swift */; }; + 71CF28BA261D60DA00894E8C /* TextInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CF28B9261D60DA00894E8C /* TextInputView.swift */; }; + 71CF28BB261D60DA00894E8C /* TextInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CF28B9261D60DA00894E8C /* TextInputView.swift */; }; + 71CF28C3261D6CD800894E8C /* TextInputOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CF28C2261D6CD800894E8C /* TextInputOption.swift */; }; + 71CF28C4261D6CD800894E8C /* TextInputOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CF28C2261D6CD800894E8C /* TextInputOption.swift */; }; + 71CF28C9261D6CE500894E8C /* TextInputOptionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CF28C8261D6CE500894E8C /* TextInputOptionItem.swift */; }; + 71CF28CA261D6CE500894E8C /* TextInputOptionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CF28C8261D6CE500894E8C /* TextInputOptionItem.swift */; }; + 71CF28D8261D6F1B00894E8C /* TextInputField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CF28D7261D6F1B00894E8C /* TextInputField.swift */; }; + 71CF28D9261D6F1B00894E8C /* TextInputField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CF28D7261D6F1B00894E8C /* TextInputField.swift */; }; + 71D8987E261C1BF500035CCD /* EmojiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D8987D261C1BF500035CCD /* EmojiView.swift */; }; + 71D8988A261C1C0D00035CCD /* ChapterFourState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D89889261C1C0D00035CCD /* ChapterFourState.swift */; }; + 71D8988B261C1C0D00035CCD /* ChapterFourState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D89889261C1C0D00035CCD /* ChapterFourState.swift */; }; + 71D89894261C1C9C00035CCD /* EmojiView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D8987D261C1BF500035CCD /* EmojiView.swift */; }; + 71D8989B261C1DEF00035CCD /* ChapterTwoAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D8989A261C1DEF00035CCD /* ChapterTwoAction.swift */; }; + 71D8989C261C1DEF00035CCD /* ChapterTwoAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D8989A261C1DEF00035CCD /* ChapterTwoAction.swift */; }; + 71D898A3261C1DFA00035CCD /* ChapterTwoReduce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D898A2261C1DFA00035CCD /* ChapterTwoReduce.swift */; }; + 71D898A4261C1DFA00035CCD /* ChapterTwoReduce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D898A2261C1DFA00035CCD /* ChapterTwoReduce.swift */; }; + 71DBC65A2612075B00C736ED /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71816616261203E100614F25 /* MapView.swift */; }; + 71E52A46260054870064BC6E /* PlaneIntroductionXrayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71E52A45260054870064BC6E /* PlaneIntroductionXrayView.swift */; }; + 71E83479260E41BC008954E8 /* ChapterTwoState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71CA9433260E409300F04F9F /* ChapterTwoState.swift */; }; + 71F52C67260DE5F400AC82F4 /* CharactersCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 71F52C66260DE5F400AC82F4 /* CharactersCollectionViewCell.xib */; }; + 71F52CED260DED5800AC82F4 /* CharactersCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 71F52CEB260DED5800AC82F4 /* CharactersCollectionViewCell.xib */; }; + 71F6473F261440320087ED8A /* PlaneListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F6473E261440320087ED8A /* PlaneListView.swift */; }; + 71F64746261440410087ED8A /* PlaneListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F64745261440410087ED8A /* PlaneListItemView.swift */; }; + 71F64751261441AD0087ED8A /* PlaneListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F64745261440410087ED8A /* PlaneListItemView.swift */; }; + 71F64757261442770087ED8A /* PlaneListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F6473E261440320087ED8A /* PlaneListView.swift */; }; + 71F64763261464640087ED8A /* View+ifTrue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F64762261464640087ED8A /* View+ifTrue.swift */; }; + 71F64764261464640087ED8A /* View+ifTrue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F64762261464640087ED8A /* View+ifTrue.swift */; }; + 71F647DC261487FB0087ED8A /* Fonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F647DB261487FB0087ED8A /* Fonts.swift */; }; + 71F647DD261487FB0087ED8A /* Fonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F647DB261487FB0087ED8A /* Fonts.swift */; }; + 71F6482D261495F60087ED8A /* LastResort-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7191D0E12601020F004D7326 /* LastResort-Regular.ttf */; }; + 71F91E442629CF2D00C40D33 /* NotoSerifNyiakengPuachueHmong-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71F91E402629CF2D00C40D33 /* NotoSerifNyiakengPuachueHmong-Regular.ttf */; }; + 71F91E452629CF2D00C40D33 /* NotoSerifNyiakengPuachueHmong-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71F91E402629CF2D00C40D33 /* NotoSerifNyiakengPuachueHmong-Regular.ttf */; }; + 71F91E462629CF2D00C40D33 /* NotoSansSinhala-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71F91E412629CF2D00C40D33 /* NotoSansSinhala-Regular.ttf */; }; + 71F91E472629CF2D00C40D33 /* NotoSansSinhala-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71F91E412629CF2D00C40D33 /* NotoSansSinhala-Regular.ttf */; }; + 71F91E482629CF2D00C40D33 /* NotoSerifDogra-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71F91E422629CF2D00C40D33 /* NotoSerifDogra-Regular.ttf */; }; + 71F91E492629CF2D00C40D33 /* NotoSerifDogra-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71F91E422629CF2D00C40D33 /* NotoSerifDogra-Regular.ttf */; }; + 71F91E4A2629CF2D00C40D33 /* NotoSerifTangut-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71F91E432629CF2D00C40D33 /* NotoSerifTangut-Regular.ttf */; }; + 71F91E4B2629CF2D00C40D33 /* NotoSerifTangut-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71F91E432629CF2D00C40D33 /* NotoSerifTangut-Regular.ttf */; }; + 71F91E572629D98D00C40D33 /* ChapterEmoji-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71F91E562629D98D00C40D33 /* ChapterEmoji-Regular.ttf */; }; + 71F91E582629D98D00C40D33 /* ChapterEmoji-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 71F91E562629D98D00C40D33 /* ChapterEmoji-Regular.ttf */; }; + 71F9580B26202B2E0095F391 /* CharacterInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F9580A26202B2E0095F391 /* CharacterInformation.swift */; }; + 71F9580C26202B2E0095F391 /* CharacterInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F9580A26202B2E0095F391 /* CharacterInformation.swift */; }; + 71F9581E26202D1D0095F391 /* CharacterBottomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F9581D26202D1D0095F391 /* CharacterBottomViewModel.swift */; }; + 71F9581F26202D1D0095F391 /* CharacterBottomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F9581D26202D1D0095F391 /* CharacterBottomViewModel.swift */; }; + 71F9582D2620A68C0095F391 /* CharactersBottomTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F9582C2620A68C0095F391 /* CharactersBottomTitleView.swift */; }; + 71F9582E2620A68C0095F391 /* CharactersBottomTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F9582C2620A68C0095F391 /* CharactersBottomTitleView.swift */; }; + 71F958372620A69D0095F391 /* Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F958362620A69D0095F391 /* Badge.swift */; }; + 71F9583E2620B74B0095F391 /* Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71F958362620A69D0095F391 /* Badge.swift */; }; + 71FE70582611C42E004B7DA9 /* CATransaction+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71FE70572611C42E004B7DA9 /* CATransaction+Extensions.swift */; }; + 71FE70722611C4BC004B7DA9 /* CATransaction+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71FE70572611C42E004B7DA9 /* CATransaction+Extensions.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXBuildRule section */ + 5E086DD42051E415004D8D25 /* PBXBuildRule */ = { + isa = PBXBuildRule; + compilerSpec = com.apple.compilers.proxy.script; + filePatterns = "*/Manifest.plist"; + fileType = pattern.proxy; + inputFiles = ( + ); + isEditable = 1; + outputFiles = ( + "$(DERIVED_FILE_DIR)/$(INPUT_FILE_NAME)", + ); + script = "mkdir -p \"${DERIVED_FILE_DIR}\"\n\n\"${PROJECT_DIR}\"/SupportingContent/Tools/expandBuildSettingReferences --output \"${SCRIPT_OUTPUT_FILE_0}\" --use-environment \"${SCRIPT_INPUT_FILE}\"\n"; + }; +/* End PBXBuildRule section */ + +/* Begin PBXContainerItemProxy section */ + 5EF2F9B42054BDD100191409 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5E2A7ADD204F611300F4E17A /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5EF2F9AA2054BBF300191409; + remoteInfo = Book_Sources; + }; + 7179EB5D262861450054765B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5E2A7ADD204F611300F4E17A /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7179EB22262860C60054765B; + remoteInfo = DifferenceKit; + }; + 7179EB5F2628614D0054765B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5E2A7ADD204F611300F4E17A /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7179EB22262860C60054765B; + remoteInfo = DifferenceKit; + }; + 7179EBF02628624C0054765B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5E2A7ADD204F611300F4E17A /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7179EB22262860C60054765B; + remoteInfo = DifferenceKit; + }; + 7179EBF2262862540054765B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5E2A7ADD204F611300F4E17A /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7179EB772628620B0054765B; + remoteInfo = ASCollectionView; + }; + 7179EBF6262862640054765B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5E2A7ADD204F611300F4E17A /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7179EB772628620B0054765B; + remoteInfo = ASCollectionView; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 5E086DD22051E35D004D8D25 /* Copy Book Contents */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)"; + dstSubfolderSpec = 16; + files = ( + 71A5B131262BFAEE001FC1AF /* License.pdf in Copy Book Contents */, + 5E086DD32051E3A3004D8D25 /* Manifest.plist in Copy Book Contents */, + 5EF2F9B72054E6FB00191409 /* Chapters in Copy Book Contents */, + 5E70679F23678DC30094BDEF /* Modules in Copy Book Contents */, + ); + name = "Copy Book Contents"; + runOnlyForDeploymentPostprocessing = 0; + }; + 5EA2E3D92056F8BD00416A35 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 5EBEC2F0205B399300975D3F /* LiveViewHost.framework in Embed Frameworks */, + 5EA2E3DA2056F8D100416A35 /* PlaygroundBluetooth.framework in Embed Frameworks */, + 5EA2E3DB2056F8D100416A35 /* PlaygroundSupport.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 7179EB21262860C60054765B /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7179EB762628620B0054765B /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 5E086DC02051C5A7004D8D25 /* PlaygroundBook.playgroundbook */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PlaygroundBook.playgroundbook; sourceTree = BUILT_PRODUCTS_DIR; }; + 5E086DC72051DD03004D8D25 /* BookOverridingBuildSettings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = BookOverridingBuildSettings.xcconfig; sourceTree = ""; }; + 5E086DCF2051DF0F004D8D25 /* Manifest.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Manifest.plist; sourceTree = ""; }; + 5E0E37522065981C008FA4BE /* BuildSettings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = BuildSettings.xcconfig; sourceTree = ""; }; + 5E0E37542065A065008FA4BE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 5E2A7AE6204F630600F4E17A /* BaseBuildSettings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = BaseBuildSettings.xcconfig; sourceTree = ""; }; + 5E3196F22061DEA40077BBD7 /* LiveViewSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveViewSupport.swift; sourceTree = ""; }; + 5E551EC42371FC3F00784365 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 5E6F7E6D2368FC9A008CC191 /* ModuleOverridingBuildSettings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ModuleOverridingBuildSettings.xcconfig; sourceTree = ""; }; + 5E70679E23678DC00094BDEF /* Modules */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Modules; sourceTree = ""; }; + 5E80DF292342971E00595EB4 /* LiveViewTestApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LiveViewTestApp.entitlements; sourceTree = ""; }; + 5EA2E3BD2056F35A00416A35 /* LiveViewTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LiveViewTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 5EA2E3BF2056F35A00416A35 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 5EA2E3C92056F35B00416A35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LiveViewTestAppLaunchScreen.storyboard; sourceTree = ""; }; + 5EA2E3CB2056F35B00416A35 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 5EA2E3D72056F88700416A35 /* PlaygroundSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = PlaygroundSupport.framework; sourceTree = PLAYGROUNDS_FRAMEWORKS_DIR; }; + 5EA2E3D82056F88700416A35 /* PlaygroundBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = PlaygroundBluetooth.framework; sourceTree = PLAYGROUNDS_FRAMEWORKS_DIR; }; + 5EBEC2EF205B396500975D3F /* LiveViewHost.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = LiveViewHost.framework; sourceTree = OTHER_FRAMEWORKS_DIR; }; + 5EF2F97D2054B6E400191409 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/ManifestPlist.strings; sourceTree = ""; }; + 5EF2F9AB2054BBF300191409 /* libBookCore.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBookCore.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 5EF2F9AD2054BBF300191409 /* BaseLiveViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseLiveViewController.swift; sourceTree = ""; }; + 5EF2F9B62054E6F900191409 /* Chapters */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Chapters; sourceTree = ""; }; + 7100B9F52620C08900ACE8AB /* ViewSizeKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewSizeKey.swift; sourceTree = ""; }; + 7102C1A026108A100068E8A1 /* CharactersCollectionHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CharactersCollectionHeaderView.xib; sourceTree = ""; }; + 7103E09A2619D98400F8F8AF /* UnicodeData.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = UnicodeData.sqlite; sourceTree = ""; }; + 7103E0C42619DB6E00F8F8AF /* UnicodeData.momd */ = {isa = PBXFileReference; lastKnownFileType = folder; path = UnicodeData.momd; sourceTree = ""; }; + 7103E0CF2619E11700F8F8AF /* Store.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = ""; }; + 7103E0E32619E7AF00F8F8AF /* BlockDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockDescription.swift; sourceTree = ""; }; + 7123C41E260046EA001ABF47 /* CharactersCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharactersCollectionViewController.swift; sourceTree = ""; }; + 7123C427260046FA001ABF47 /* CharactersCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharactersCollectionViewCell.swift; sourceTree = ""; }; + 7123C4512600506B001ABF47 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = ""; }; + 71248D4726256B920077F634 /* EmojiBottomTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiBottomTitleView.swift; sourceTree = ""; }; + 712CA48D25FDC4E200BEF6E4 /* Category.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = ""; }; + 712CA49E25FDD12B00BEF6E4 /* DataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProvider.swift; sourceTree = ""; }; + 712CA4A825FDE38200BEF6E4 /* PlaneXrayUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaneXrayUIView.swift; sourceTree = ""; }; + 7132495F2627EFEB00643B2B /* UnicodeGeneralCategory+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UnicodeGeneralCategory+Extensions.swift"; sourceTree = ""; }; + 71356F6F26270ED400CDABDA /* CharacterBottomViewModel+Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CharacterBottomViewModel+Types.swift"; sourceTree = ""; }; + 71356F7826272DA700CDABDA /* String+renderingTrait.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+renderingTrait.swift"; sourceTree = ""; }; + 71356F8126272F2600CDABDA /* RenderingTrait.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingTrait.swift; sourceTree = ""; }; + 71403AF42622BD820047483C /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; + 71403AFD2622BE0D0047483C /* IntroductionToUnicodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntroductionToUnicodeView.swift; sourceTree = ""; }; + 71403B092622BEC00047483C /* App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; + 71403B132622C49F0047483C /* CateIterable+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CateIterable+Extensions.swift"; sourceTree = ""; }; + 71403B1C2622D4240047483C /* UICollectionView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Extensions.swift"; sourceTree = ""; }; + 71403B2B26230B850047483C /* MapDot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapDot.swift; sourceTree = ""; }; + 7141BA12262B3B5D00B0ADBA /* CLDRAnnotation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CLDRAnnotation.swift; sourceTree = ""; }; + 7141BA24262B417700B0ADBA /* ChapterEmojiFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChapterEmojiFont.swift; sourceTree = ""; }; + 7141BA67262BDEE000B0ADBA /* String+chapterEmoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+chapterEmoji.swift"; sourceTree = ""; }; + 7143D92F2629DD60003E20D1 /* EmojiCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiCell.swift; sourceTree = ""; }; + 7143D9492629E9B9003E20D1 /* NotoEmoji-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoEmoji-Regular.ttf"; sourceTree = ""; }; + 7143D967262ABD18003E20D1 /* ZWJInputListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZWJInputListItem.swift; sourceTree = ""; }; + 715AA8E7260D95C0003CD334 /* BlockInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockInfoView.swift; sourceTree = ""; }; + 715E2357262484BE00CC6679 /* EmojiTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiTopView.swift; sourceTree = ""; }; + 715E2360262484E400CC6679 /* EmojiLargeImputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiLargeImputView.swift; sourceTree = ""; }; + 715E23662624850300CC6679 /* EmojiFormulaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiFormulaView.swift; sourceTree = ""; }; + 715E236E2624852700CC6679 /* ZWJFomulaInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZWJFomulaInputView.swift; sourceTree = ""; }; + 715E237B2624854E00CC6679 /* EmojiGridInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiGridInputView.swift; sourceTree = ""; }; + 715E2384262485C800CC6679 /* EmojiBottomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiBottomView.swift; sourceTree = ""; }; + 71698E31261A9FA000B4A6C7 /* NibInstantiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibInstantiable.swift; sourceTree = ""; }; + 71698E44261AA3CD00B4A6C7 /* NSManagedObject+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Extensions.swift"; sourceTree = ""; }; + 71698E7A261AD70A00B4A6C7 /* RichTextDescriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichTextDescriptionView.swift; sourceTree = ""; }; + 71698E8C261AE2C900B4A6C7 /* String+formatLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+formatLinks.swift"; sourceTree = ""; }; + 7179EB23262860C60054765B /* libDifferenceKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libDifferenceKit.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 7179EB342628610F0054765B /* AnyDifferentiable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyDifferentiable.swift; sourceTree = ""; }; + 7179EB35262861100054765B /* ArraySection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArraySection.swift; sourceTree = ""; }; + 7179EB36262861100054765B /* ContentEquatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentEquatable.swift; sourceTree = ""; }; + 7179EB37262861100054765B /* Algorithm.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Algorithm.swift; sourceTree = ""; }; + 7179EB38262861100054765B /* StagedChangeset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StagedChangeset.swift; sourceTree = ""; }; + 7179EB39262861100054765B /* UIKitExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitExtension.swift; sourceTree = ""; }; + 7179EB3A262861100054765B /* Changeset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Changeset.swift; sourceTree = ""; }; + 7179EB3B262861100054765B /* ElementPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElementPath.swift; sourceTree = ""; }; + 7179EB3C262861100054765B /* DifferentiableSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DifferentiableSection.swift; sourceTree = ""; }; + 7179EB41262861100054765B /* ContentIdentifiable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentIdentifiable.swift; sourceTree = ""; }; + 7179EB42262861110054765B /* Differentiable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Differentiable.swift; sourceTree = ""; }; + 7179EB782628620B0054765B /* libASCollectionView.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libASCollectionView.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 7179EB8C262862260054765B /* ASDiffableDataSourceCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASDiffableDataSourceCollectionView.swift; sourceTree = ""; }; + 7179EB8D262862260054765B /* ASDiffableDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASDiffableDataSource.swift; sourceTree = ""; }; + 7179EB8E262862260054765B /* ASCellContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASCellContext.swift; sourceTree = ""; }; + 7179EB91262862270054765B /* ASSectionDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASSectionDataSource.swift; sourceTree = ""; }; + 7179EB92262862270054765B /* ASSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASSection.swift; sourceTree = ""; }; + 7179EB93262862270054765B /* ASHostingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASHostingController.swift; sourceTree = ""; }; + 7179EB94262862270054765B /* ASCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASCollectionView.swift; sourceTree = ""; }; + 7179EB96262862270054765B /* ASOptionalSize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASOptionalSize.swift; sourceTree = ""; }; + 7179EB97262862270054765B /* ShrinkToFitWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShrinkToFitWrapper.swift; sourceTree = ""; }; + 7179EB98262862270054765B /* Binding+Sequence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Binding+Sequence.swift"; sourceTree = ""; }; + 7179EB99262862270054765B /* RandomAccessCollection+Safe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RandomAccessCollection+Safe.swift"; sourceTree = ""; }; + 7179EB9A262862270054765B /* ASPriorityCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASPriorityCache.swift; sourceTree = ""; }; + 7179EB9B262862270054765B /* ASSelfSizingSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASSelfSizingSettings.swift; sourceTree = ""; }; + 7179EB9C262862270054765B /* ASIndexedDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASIndexedDictionary.swift; sourceTree = ""; }; + 7179EB9D262862270054765B /* GlobalConvenienceFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlobalConvenienceFunctions.swift; sourceTree = ""; }; + 7179EB9F262862270054765B /* UIScrollView+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Convenience.swift"; sourceTree = ""; }; + 7179EBA0262862270054765B /* UICollectionView+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Convenience.swift"; sourceTree = ""; }; + 7179EBA1262862270054765B /* UIView+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Convenience.swift"; sourceTree = ""; }; + 7179EBA3262862270054765B /* ASCollectionViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASCollectionViewLayout.swift; sourceTree = ""; }; + 7179EBA7262862270054765B /* ASDragDropConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASDragDropConfig.swift; sourceTree = ""; }; + 7179EBA8262862270054765B /* ClosureTypeAliases.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClosureTypeAliases.swift; sourceTree = ""; }; + 7179EBA9262862270054765B /* ASDragDropConfig+Public.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ASDragDropConfig+Public.swift"; sourceTree = ""; }; + 7179EBAA262862270054765B /* ASSection+Modifiers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ASSection+Modifiers.swift"; sourceTree = ""; }; + 7179EBAC262862270054765B /* ASCollectionViewDecoration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASCollectionViewDecoration.swift; sourceTree = ""; }; + 7179EBAD262862270054765B /* ASCollectionViewSupplementaryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASCollectionViewSupplementaryView.swift; sourceTree = ""; }; + 7179EBAE262862270054765B /* ASSupplementaryCellID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASSupplementaryCellID.swift; sourceTree = ""; }; + 7179EBB0262862270054765B /* ASCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASCollectionViewCell.swift; sourceTree = ""; }; + 7179EBB4262862280054765B /* ViewArrayBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewArrayBuilder.swift; sourceTree = ""; }; + 7179EBB5262862280054765B /* SectionArrayBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionArrayBuilder.swift; sourceTree = ""; }; + 7179EBB6262862280054765B /* ASCollectionView+Initialisers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ASCollectionView+Initialisers.swift"; sourceTree = ""; }; + 7179EBB7262862280054765B /* ASSection+Initialisers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ASSection+Initialisers.swift"; sourceTree = ""; }; + 7179EBBA262862280054765B /* AS_UICollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AS_UICollectionView.swift; sourceTree = ""; }; + 7179EBBB262862280054765B /* ASCollectionView+Modifiers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ASCollectionView+Modifiers.swift"; sourceTree = ""; }; + 7179EBBD262862280054765B /* EnvironmentKeys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnvironmentKeys.swift; sourceTree = ""; }; + 7179EBBF262862280054765B /* ASCollectionViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASCollectionViewDelegate.swift; sourceTree = ""; }; + 71816616261203E100614F25 /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = ""; }; + 7185779D26294E7C002D2D44 /* NotoSansCoptic-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansCoptic-Regular.ttf"; sourceTree = ""; }; + 718577A526294F7E002D2D44 /* NotoSansSyriac-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansSyriac-Regular.ttf"; sourceTree = ""; }; + 718577AD26295003002D2D44 /* NotoSansMalayalam-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansMalayalam-Regular.ttf"; sourceTree = ""; }; + 718577B526295076002D2D44 /* NotoSansLao-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansLao-Regular.ttf"; sourceTree = ""; }; + 718577BD262951CA002D2D44 /* NotoSansGeorgian-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansGeorgian-Regular.ttf"; sourceTree = ""; }; + 718577C52629525F002D2D44 /* NotoSansCanadianAboriginal-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansCanadianAboriginal-Regular.ttf"; sourceTree = ""; }; + 718577CD26295385002D2D44 /* NotoSansSymbols2-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansSymbols2-Regular.ttf"; sourceTree = ""; }; + 718577D526295491002D2D44 /* NotoSansSymbols-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansSymbols-Regular.ttf"; sourceTree = ""; }; + 71857804262985FC002D2D44 /* DebugAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugAlert.swift; sourceTree = ""; }; + 7185781B262986E5002D2D44 /* NotoSansOldSogdian-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansOldSogdian-Regular.ttf"; sourceTree = ""; }; + 7185781D262986E6002D2D44 /* NotoSansNushu-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansNushu-Regular.ttf"; sourceTree = ""; }; + 7185781E262986E6002D2D44 /* NotoSansElymaic-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansElymaic-Regular.ttf"; sourceTree = ""; }; + 7185781F262986E6002D2D44 /* NotoSansGunjalaGondi-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansGunjalaGondi-Regular.ttf"; sourceTree = ""; }; + 71857820262986E6002D2D44 /* NotoSansIndicSiyaqNumbers-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansIndicSiyaqNumbers-Regular.ttf"; sourceTree = ""; }; + 71857821262986E6002D2D44 /* NotoSansMongolian-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansMongolian-Regular.ttf"; sourceTree = ""; }; + 71857822262986E6002D2D44 /* NotoSansAnatolianHieroglyphs-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansAnatolianHieroglyphs-Regular.ttf"; sourceTree = ""; }; + 71857823262986E6002D2D44 /* NotoSansDeseret-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansDeseret-Regular.ttf"; sourceTree = ""; }; + 71857824262986E6002D2D44 /* NotoSansZanabazarSquare-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansZanabazarSquare-Regular.ttf"; sourceTree = ""; }; + 71857825262986E6002D2D44 /* NotoSansMedefaidrin-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansMedefaidrin-Regular.ttf"; sourceTree = ""; }; + 71857826262986E7002D2D44 /* NotoSansMasaramGondi-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansMasaramGondi-Regular.ttf"; sourceTree = ""; }; + 71857827262986E7002D2D44 /* NotoSansSogdian-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansSogdian-Regular.ttf"; sourceTree = ""; }; + 71857828262986E7002D2D44 /* NotoSansSoyombo-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansSoyombo-Regular.ttf"; sourceTree = ""; }; + 7185785C2629A23E002D2D44 /* emoji-input.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "emoji-input.json"; sourceTree = ""; }; + 718578642629A451002D2D44 /* EmojiInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiInput.swift; sourceTree = ""; }; + 7185787B2629AE1A002D2D44 /* NotoSerifYezidi-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSerifYezidi-Regular.ttf"; sourceTree = ""; }; + 7185787C2629AE1A002D2D44 /* NotoMusic-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoMusic-Regular.ttf"; sourceTree = ""; }; + 7185787D2629AE1A002D2D44 /* NotoSansSignWriting-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansSignWriting-Regular.ttf"; sourceTree = ""; }; + 718D75B2260DAA060076AAE2 /* planes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = planes.json; sourceTree = ""; }; + 718D75B3260DAA060076AAE2 /* categories.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = categories.json; sourceTree = ""; }; + 718D75C4260DAB230076AAE2 /* FileHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileHelper.swift; sourceTree = ""; }; + 718D75EB260DCC850076AAE2 /* CodePointsBlocksAndPlanesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodePointsBlocksAndPlanesView.swift; sourceTree = ""; }; + 7191D0D726010127004D7326 /* UIFont+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Extensions.swift"; sourceTree = ""; }; + 7191D0E12601020F004D7326 /* LastResort-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "LastResort-Regular.ttf"; sourceTree = ""; }; + 7193AE0925FB20EF00B459A6 /* Block.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Block.swift; sourceTree = ""; }; + 7193AE1325FB21FA00B459A6 /* Language.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Language.swift; sourceTree = ""; }; + 7193AE1925FB235100B459A6 /* Country.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Country.swift; sourceTree = ""; }; + 7193AE2625FB25BB00B459A6 /* Plane.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Plane.swift; sourceTree = ""; }; + 71A5B12B262BFAEB001FC1AF /* License.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = License.pdf; path = Licences/License.pdf; sourceTree = ""; }; + 71A5B14A262C6040001FC1AF /* CharacterBottomView+contextConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CharacterBottomView+contextConfiguration.swift"; sourceTree = ""; }; + 71A5B160262D2D78001FC1AF /* 1-2-user-interface.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "1-2-user-interface.png"; sourceTree = ""; }; + 71A5B166262D3359001FC1AF /* 1-3-plane-view.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "1-3-plane-view.png"; sourceTree = ""; }; + 71A5B170262D3392001FC1AF /* 1-1-character-combining.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "1-1-character-combining.png"; sourceTree = ""; }; + 71A5B176262D63CE001FC1AF /* 2-1-utf-32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "2-1-utf-32.png"; sourceTree = ""; }; + 71A5B177262D63CE001FC1AF /* 2-2-utf-16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "2-2-utf-16.png"; sourceTree = ""; }; + 71A5B178262D63CE001FC1AF /* 2-3-utf-8.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "2-3-utf-8.png"; sourceTree = ""; }; + 71A5B184262D7742001FC1AF /* 3-1-joining.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "3-1-joining.png"; sourceTree = ""; }; + 71A5B18A262D9CA2001FC1AF /* 3-4-variations.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "3-4-variations.png"; sourceTree = ""; }; + 71A5B18B262D9CA2001FC1AF /* 3-3-flags-and-keycaps.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "3-3-flags-and-keycaps.png"; sourceTree = ""; }; + 71A5B18C262D9CA2001FC1AF /* 3-2-modifiers.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "3-2-modifiers.png"; sourceTree = ""; }; + 71A5B194262DA08C001FC1AF /* 1-4-last-resort.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "1-4-last-resort.png"; sourceTree = ""; }; + 71A63F192601CE0B00519C43 /* UIViewController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extensions.swift"; sourceTree = ""; }; + 71A63F2A2601D11700519C43 /* CharacterBottomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterBottomView.swift; sourceTree = ""; }; + 71A63F332601D12E00519C43 /* Character3DView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Character3DView.swift; sourceTree = ""; }; + 71A63F3C2601D28E00519C43 /* CharacterIntroductionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterIntroductionView.swift; sourceTree = ""; }; + 71A74065261748A900ACC64D /* View+Modifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Modifiers.swift"; sourceTree = ""; }; + 71A7408226175BC600ACC64D /* PlaneXrayUIViewMaskLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaneXrayUIViewMaskLayer.swift; sourceTree = ""; }; + 71A7408E26175BEE00ACC64D /* PlaneXrayBlocksLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaneXrayBlocksLayer.swift; sourceTree = ""; }; + 71A740C526196BD700ACC64D /* String+codePoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+codePoint.swift"; sourceTree = ""; }; + 71AFA0BA25FD183C008E5A4C /* BlocksListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlocksListItemView.swift; sourceTree = ""; }; + 71AFA0C525FD1A47008E5A4C /* BlocksListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlocksListView.swift; sourceTree = ""; }; + 71AFA0D125FD1CAF008E5A4C /* PlaneIntroductionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaneIntroductionView.swift; sourceTree = ""; }; + 71AFA0F725FD2091008E5A4C /* PlaneBottomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaneBottomView.swift; sourceTree = ""; }; + 71B02D26262BE2B60037FA30 /* ChapterThreeState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChapterThreeState.swift; sourceTree = ""; }; + 71B02D28262BE2B60037FA30 /* EncodeWithUtfView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncodeWithUtfView.swift; sourceTree = ""; }; + 71B02D2B262BE2B60037FA30 /* CodeSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeSlider.swift; sourceTree = ""; }; + 71B02D2C262BE2B60037FA30 /* SliderCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderCell.swift; sourceTree = ""; }; + 71B02D2D262BE2B60037FA30 /* SliderGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderGroup.swift; sourceTree = ""; }; + 71B1174D2627051400BDC1A5 /* SampleText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleText.swift; sourceTree = ""; }; + 71B11753262706E300BDC1A5 /* View+EnumeratedForEach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+EnumeratedForEach.swift"; sourceTree = ""; }; + 71BB759B2626AD3100FA11C3 /* sample_text_data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = sample_text_data.json; sourceTree = ""; }; + 71BE77F425FA0FAF005D6CCB /* IntroductionToUnicodeViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IntroductionToUnicodeViewController.xib; sourceTree = ""; }; + 71BE77FC25FA0FC1005D6CCB /* EncodingWithUtfViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EncodingWithUtfViewController.xib; sourceTree = ""; }; + 71BE783525FA46BD005D6CCB /* WelcomeViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WelcomeViewController.xib; sourceTree = ""; }; + 71CA9429260E403600F04F9F /* View+RoundedCorner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+RoundedCorner.swift"; sourceTree = ""; }; + 71CA9433260E409300F04F9F /* ChapterTwoState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChapterTwoState.swift; sourceTree = ""; }; + 71CD670E26274D64001D3693 /* UIImage+backgroundColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+backgroundColor.swift"; sourceTree = ""; }; + 71CD671426275DBF001D3693 /* UnicodeScalar+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UnicodeScalar+Extensions.swift"; sourceTree = ""; }; + 71CE628A260F4E38003F94BE /* blocks.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = blocks.json; sourceTree = ""; }; + 71CE62C926108507003F94BE /* CharactersCollectionHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = CharactersCollectionHeaderView.xib; path = PlaygroundBook/PrivateResources/XIBs/CharactersCollectionHeaderView.xib; sourceTree = SOURCE_ROOT; }; + 71CE62D226108519003F94BE /* CharactersCollectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharactersCollectionHeaderView.swift; sourceTree = ""; }; + 71CF28B9261D60DA00894E8C /* TextInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextInputView.swift; sourceTree = ""; }; + 71CF28C2261D6CD800894E8C /* TextInputOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextInputOption.swift; sourceTree = ""; }; + 71CF28C8261D6CE500894E8C /* TextInputOptionItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextInputOptionItem.swift; sourceTree = ""; }; + 71CF28D7261D6F1B00894E8C /* TextInputField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextInputField.swift; sourceTree = ""; }; + 71D8987D261C1BF500035CCD /* EmojiView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiView.swift; sourceTree = ""; }; + 71D89889261C1C0D00035CCD /* ChapterFourState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChapterFourState.swift; sourceTree = ""; }; + 71D8989A261C1DEF00035CCD /* ChapterTwoAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChapterTwoAction.swift; sourceTree = ""; }; + 71D898A2261C1DFA00035CCD /* ChapterTwoReduce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChapterTwoReduce.swift; sourceTree = ""; }; + 71E52A45260054870064BC6E /* PlaneIntroductionXrayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaneIntroductionXrayView.swift; sourceTree = ""; }; + 71F52C66260DE5F400AC82F4 /* CharactersCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = CharactersCollectionViewCell.xib; path = PlaygroundBook/PrivateResources/XIBs/CharactersCollectionViewCell.xib; sourceTree = SOURCE_ROOT; }; + 71F52CEB260DED5800AC82F4 /* CharactersCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CharactersCollectionViewCell.xib; sourceTree = ""; }; + 71F6473E261440320087ED8A /* PlaneListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaneListView.swift; sourceTree = ""; }; + 71F64745261440410087ED8A /* PlaneListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaneListItemView.swift; sourceTree = ""; }; + 71F64762261464640087ED8A /* View+ifTrue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+ifTrue.swift"; sourceTree = ""; }; + 71F647DB261487FB0087ED8A /* Fonts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fonts.swift; sourceTree = ""; }; + 71F91E402629CF2D00C40D33 /* NotoSerifNyiakengPuachueHmong-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSerifNyiakengPuachueHmong-Regular.ttf"; sourceTree = ""; }; + 71F91E412629CF2D00C40D33 /* NotoSansSinhala-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSansSinhala-Regular.ttf"; sourceTree = ""; }; + 71F91E422629CF2D00C40D33 /* NotoSerifDogra-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSerifDogra-Regular.ttf"; sourceTree = ""; }; + 71F91E432629CF2D00C40D33 /* NotoSerifTangut-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSerifTangut-Regular.ttf"; sourceTree = ""; }; + 71F91E562629D98D00C40D33 /* ChapterEmoji-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ChapterEmoji-Regular.ttf"; sourceTree = ""; }; + 71F9580A26202B2E0095F391 /* CharacterInformation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacterInformation.swift; sourceTree = ""; }; + 71F9581D26202D1D0095F391 /* CharacterBottomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterBottomViewModel.swift; sourceTree = ""; }; + 71F9582C2620A68C0095F391 /* CharactersBottomTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharactersBottomTitleView.swift; sourceTree = ""; }; + 71F958362620A69D0095F391 /* Badge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Badge.swift; sourceTree = ""; }; + 71FE70572611C42E004B7DA9 /* CATransaction+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extensions.swift"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 5EA2E3BA2056F35A00416A35 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7179EB62262861580054765B /* libDifferenceKit.a in Frameworks */, + 7179EBF5262862610054765B /* libASCollectionView.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5EF2F9A82054BBF300191409 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7179EB63262861620054765B /* libDifferenceKit.a in Frameworks */, + 7179EBF42628625B0054765B /* libASCollectionView.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7179EB20262860C60054765B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7179EB752628620B0054765B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 5E086DA4204F706E004D8D25 /* Products */ = { + isa = PBXGroup; + children = ( + 5E086DC02051C5A7004D8D25 /* PlaygroundBook.playgroundbook */, + 5EF2F9AB2054BBF300191409 /* libBookCore.a */, + 5EA2E3BD2056F35A00416A35 /* LiveViewTestApp.app */, + 7179EB23262860C60054765B /* libDifferenceKit.a */, + 7179EB782628620B0054765B /* libASCollectionView.a */, + ); + name = Products; + sourceTree = ""; + }; + 5E086DC12051C5A7004D8D25 /* PlaygroundBook */ = { + isa = PBXGroup; + children = ( + 5E086DCF2051DF0F004D8D25 /* Manifest.plist */, + 5E70679B23678D810094BDEF /* Modules */, + 5EF2F9792054B64800191409 /* PrivateResources */, + 5EF2F9B62054E6F900191409 /* Chapters */, + 5EF2F9A42054BB9500191409 /* Supporting Content */, + ); + path = PlaygroundBook; + sourceTree = ""; + }; + 5E0E3751206597E7008FA4BE /* Base */ = { + isa = PBXGroup; + children = ( + 5E2A7AE6204F630600F4E17A /* BaseBuildSettings.xcconfig */, + ); + path = Base; + sourceTree = ""; + }; + 5E0E3753206599A0008FA4BE /* Overrides */ = { + isa = PBXGroup; + children = ( + 5E086DC72051DD03004D8D25 /* BookOverridingBuildSettings.xcconfig */, + 5E6F7E6D2368FC9A008CC191 /* ModuleOverridingBuildSettings.xcconfig */, + ); + path = Overrides; + sourceTree = ""; + }; + 5E2A7ADC204F611300F4E17A = { + isa = PBXGroup; + children = ( + 5E0E37542065A065008FA4BE /* README.md */, + 71A5B12B262BFAEB001FC1AF /* License.pdf */, + 5E086DC12051C5A7004D8D25 /* PlaygroundBook */, + 5EA2E3BE2056F35A00416A35 /* LiveViewTestApp */, + 5E2A7AE3204F628B00F4E17A /* Config Files */, + 5EA2E3D52056F83400416A35 /* Supporting Content */, + 5E086DA4204F706E004D8D25 /* Products */, + 7179EB61262861580054765B /* Frameworks */, + ); + sourceTree = ""; + }; + 5E2A7AE3204F628B00F4E17A /* Config Files */ = { + isa = PBXGroup; + children = ( + 5E0E37522065981C008FA4BE /* BuildSettings.xcconfig */, + 5E0E3751206597E7008FA4BE /* Base */, + 5E0E3753206599A0008FA4BE /* Overrides */, + ); + name = "Config Files"; + path = ConfigFiles; + sourceTree = ""; + }; + 5E70679B23678D810094BDEF /* Modules */ = { + isa = PBXGroup; + children = ( + 7179EB792628620B0054765B /* ASCollectionView.playgroundmodule */, + 7179EB24262860C60054765B /* DifferenceKit.playgroundmodule */, + 5E70679C23678D8A0094BDEF /* BookCore.playgroundmodule */, + ); + path = Modules; + sourceTree = ""; + }; + 5E70679C23678D8A0094BDEF /* BookCore.playgroundmodule */ = { + isa = PBXGroup; + children = ( + 5E70679D23678DA00094BDEF /* Sources */, + ); + path = BookCore.playgroundmodule; + sourceTree = ""; + }; + 5E70679D23678DA00094BDEF /* Sources */ = { + isa = PBXGroup; + children = ( + 71BE779825FA0D9A005D6CCB /* Chapters */, + 7193AE0725FB20D700B459A6 /* Core */, + 71BE781A25FA12E2005D6CCB /* Extensions */, + 71BE779225FA0D3A005D6CCB /* Base */, + ); + path = Sources; + sourceTree = ""; + }; + 5EA2E3BE2056F35A00416A35 /* LiveViewTestApp */ = { + isa = PBXGroup; + children = ( + 5EA2E3BF2056F35A00416A35 /* AppDelegate.swift */, + 5EA2E3C82056F35B00416A35 /* LiveViewTestAppLaunchScreen.storyboard */, + 5E80DF292342971E00595EB4 /* LiveViewTestApp.entitlements */, + 5EA2E3CB2056F35B00416A35 /* Info.plist */, + ); + path = LiveViewTestApp; + sourceTree = ""; + }; + 5EA2E3D52056F83400416A35 /* Supporting Content */ = { + isa = PBXGroup; + children = ( + 5EA2E3D62056F84000416A35 /* Playgrounds Frameworks */, + 5EBEC2EE205B395200975D3F /* Other Frameworks */, + ); + name = "Supporting Content"; + sourceTree = ""; + }; + 5EA2E3D62056F84000416A35 /* Playgrounds Frameworks */ = { + isa = PBXGroup; + children = ( + 5EA2E3D82056F88700416A35 /* PlaygroundBluetooth.framework */, + 5EA2E3D72056F88700416A35 /* PlaygroundSupport.framework */, + ); + name = "Playgrounds Frameworks"; + sourceTree = ""; + }; + 5EBEC2EE205B395200975D3F /* Other Frameworks */ = { + isa = PBXGroup; + children = ( + 5EBEC2EF205B396500975D3F /* LiveViewHost.framework */, + ); + name = "Other Frameworks"; + sourceTree = ""; + }; + 5EF2F9792054B64800191409 /* PrivateResources */ = { + isa = PBXGroup; + children = ( + 71A5B159262D25BA001FC1AF /* Illustrations */, + 71BE77B725FA0E44005D6CCB /* Chapters */, + 7185789B2629B487002D2D44 /* JSON */, + 718578902629B451002D2D44 /* Fonts */, + 71F52CE9260DED5800AC82F4 /* XIBs_LiveViewTestApp */, + 7103E0C42619DB6E00F8F8AF /* UnicodeData.momd */, + 7103E09A2619D98400F8F8AF /* UnicodeData.sqlite */, + 5EF2F97E2054B6E400191409 /* ManifestPlist.strings */, + 5E551EC42371FC3F00784365 /* Assets.xcassets */, + ); + path = PrivateResources; + sourceTree = ""; + }; + 5EF2F9A42054BB9500191409 /* Supporting Content */ = { + isa = PBXGroup; + children = ( + 5E70679E23678DC00094BDEF /* Modules */, + ); + name = "Supporting Content"; + sourceTree = ""; + }; + 7103E0CD2619E0FC00F8F8AF /* DataFlow */ = { + isa = PBXGroup; + children = ( + 7103E0CF2619E11700F8F8AF /* Store.swift */, + ); + path = DataFlow; + sourceTree = ""; + }; + 7123C3D925FFB256001ABF47 /* Characters */ = { + isa = PBXGroup; + children = ( + 7123C41E260046EA001ABF47 /* CharactersCollectionViewController.swift */, + 7123C427260046FA001ABF47 /* CharactersCollectionViewCell.swift */, + 71F52C66260DE5F400AC82F4 /* CharactersCollectionViewCell.xib */, + 71CE62D226108519003F94BE /* CharactersCollectionHeaderView.swift */, + 71CE62C926108507003F94BE /* CharactersCollectionHeaderView.xib */, + ); + path = Characters; + sourceTree = ""; + }; + 712A7F11262AF79700A7F746 /* ZWJInputView */ = { + isa = PBXGroup; + children = ( + 715E236E2624852700CC6679 /* ZWJFomulaInputView.swift */, + 7143D967262ABD18003E20D1 /* ZWJInputListItem.swift */, + ); + path = ZWJInputView; + sourceTree = ""; + }; + 713249652627F00700643B2B /* Extensions */ = { + isa = PBXGroup; + children = ( + 7132495F2627EFEB00643B2B /* UnicodeGeneralCategory+Extensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 71333BA425FB428900CBA8CB /* Planes */ = { + isa = PBXGroup; + children = ( + 71F6473E261440320087ED8A /* PlaneListView.swift */, + 71F64745261440410087ED8A /* PlaneListItemView.swift */, + ); + path = Planes; + sourceTree = ""; + }; + 71356F8726272F2E00CDABDA /* DomainModel */ = { + isa = PBXGroup; + children = ( + 71F9581026202CF50095F391 /* Managed */, + 7193AE2625FB25BB00B459A6 /* Plane.swift */, + 7193AE0925FB20EF00B459A6 /* Block.swift */, + 7193AE1325FB21FA00B459A6 /* Language.swift */, + 712CA48D25FDC4E200BEF6E4 /* Category.swift */, + 7193AE1925FB235100B459A6 /* Country.swift */, + ); + path = DomainModel; + sourceTree = ""; + }; + 71403AE62622B92F0047483C /* Views */ = { + isa = PBXGroup; + children = ( + 71403AFD2622BE0D0047483C /* IntroductionToUnicodeView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 71403B122622C4930047483C /* Swift */ = { + isa = PBXGroup; + children = ( + 71403B132622C49F0047483C /* CateIterable+Extensions.swift */, + ); + path = Swift; + sourceTree = ""; + }; + 7143D92E2629DD41003E20D1 /* Components */ = { + isa = PBXGroup; + children = ( + 715E2360262484E400CC6679 /* EmojiLargeImputView.swift */, + 7143D92F2629DD60003E20D1 /* EmojiCell.swift */, + 715E23662624850300CC6679 /* EmojiFormulaView.swift */, + ); + path = Components; + sourceTree = ""; + }; + 715AA8DF260D93A4003CD334 /* CharacterBottomView */ = { + isa = PBXGroup; + children = ( + 71A63F2A2601D11700519C43 /* CharacterBottomView.swift */, + 71A5B14A262C6040001FC1AF /* CharacterBottomView+contextConfiguration.swift */, + 71F9582C2620A68C0095F391 /* CharactersBottomTitleView.swift */, + 71A63F3C2601D28E00519C43 /* CharacterIntroductionView.swift */, + 71A63F332601D12E00519C43 /* Character3DView.swift */, + ); + path = CharacterBottomView; + sourceTree = ""; + }; + 715AA8F6260D95D4003CD334 /* BlockInfo */ = { + isa = PBXGroup; + children = ( + 715AA8E7260D95C0003CD334 /* BlockInfoView.swift */, + ); + path = BlockInfo; + sourceTree = ""; + }; + 715E23562624844C00CC6679 /* EmojiTopView */ = { + isa = PBXGroup; + children = ( + 715E2357262484BE00CC6679 /* EmojiTopView.swift */, + 715E236D2624851700CC6679 /* EmojiBottomView */, + ); + path = EmojiTopView; + sourceTree = ""; + }; + 715E236D2624851700CC6679 /* EmojiBottomView */ = { + isa = PBXGroup; + children = ( + 715E2384262485C800CC6679 /* EmojiBottomView.swift */, + 71248D4726256B920077F634 /* EmojiBottomTitleView.swift */, + 715E2383262485B800CC6679 /* Views */, + ); + path = EmojiBottomView; + sourceTree = ""; + }; + 715E2383262485B800CC6679 /* Views */ = { + isa = PBXGroup; + children = ( + 712A7F11262AF79700A7F746 /* ZWJInputView */, + 715E237B2624854E00CC6679 /* EmojiGridInputView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 71698E43261AA35000B4A6C7 /* CoreData */ = { + isa = PBXGroup; + children = ( + 71698E44261AA3CD00B4A6C7 /* NSManagedObject+Extensions.swift */, + ); + path = CoreData; + sourceTree = ""; + }; + 71698E74261AD6FA00B4A6C7 /* RichTextDescription */ = { + isa = PBXGroup; + children = ( + 71698E7A261AD70A00B4A6C7 /* RichTextDescriptionView.swift */, + ); + path = RichTextDescription; + sourceTree = ""; + }; + 7179EB24262860C60054765B /* DifferenceKit.playgroundmodule */ = { + isa = PBXGroup; + children = ( + 7179EB32262860DA0054765B /* Sources */, + ); + path = DifferenceKit.playgroundmodule; + sourceTree = ""; + }; + 7179EB32262860DA0054765B /* Sources */ = { + isa = PBXGroup; + children = ( + 7179EB37262861100054765B /* Algorithm.swift */, + 7179EB342628610F0054765B /* AnyDifferentiable.swift */, + 7179EB35262861100054765B /* ArraySection.swift */, + 7179EB3A262861100054765B /* Changeset.swift */, + 7179EB36262861100054765B /* ContentEquatable.swift */, + 7179EB41262861100054765B /* ContentIdentifiable.swift */, + 7179EB42262861110054765B /* Differentiable.swift */, + 7179EB3C262861100054765B /* DifferentiableSection.swift */, + 7179EB3B262861100054765B /* ElementPath.swift */, + 7179EB38262861100054765B /* StagedChangeset.swift */, + 7179EB39262861100054765B /* UIKitExtension.swift */, + ); + path = Sources; + sourceTree = ""; + }; + 7179EB61262861580054765B /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 7179EB792628620B0054765B /* ASCollectionView.playgroundmodule */ = { + isa = PBXGroup; + children = ( + 7179EB892628621C0054765B /* Sources */, + ); + path = ASCollectionView.playgroundmodule; + sourceTree = ""; + }; + 7179EB892628621C0054765B /* Sources */ = { + isa = PBXGroup; + children = ( + 7179EB8E262862260054765B /* ASCellContext.swift */, + 7179EBB6262862280054765B /* ASCollectionView+Initialisers.swift */, + 7179EBBB262862280054765B /* ASCollectionView+Modifiers.swift */, + 7179EBA9262862270054765B /* ASDragDropConfig+Public.swift */, + 7179EBB7262862280054765B /* ASSection+Initialisers.swift */, + 7179EBAA262862270054765B /* ASSection+Modifiers.swift */, + 7179EBAB262862270054765B /* Cells */, + 7179EBA6262862270054765B /* Config */, + 7179EB8A262862260054765B /* Datasource */, + 7179EBBE262862280054765B /* Delegate */, + 7179EBBC262862280054765B /* Environment */, + 7179EBB3262862280054765B /* FunctionBuilders */, + 7179EB8F262862270054765B /* Implementation */, + 7179EBA2262862270054765B /* Layout */, + 7179EB95262862270054765B /* Support */, + 7179EBB8262862280054765B /* UIKit */, + 7179EB9E262862270054765B /* UIKitExtensions */, + ); + path = Sources; + sourceTree = ""; + }; + 7179EB8A262862260054765B /* Datasource */ = { + isa = PBXGroup; + children = ( + 7179EB8C262862260054765B /* ASDiffableDataSourceCollectionView.swift */, + 7179EB8D262862260054765B /* ASDiffableDataSource.swift */, + ); + path = Datasource; + sourceTree = ""; + }; + 7179EB8F262862270054765B /* Implementation */ = { + isa = PBXGroup; + children = ( + 7179EB91262862270054765B /* ASSectionDataSource.swift */, + 7179EB92262862270054765B /* ASSection.swift */, + 7179EB93262862270054765B /* ASHostingController.swift */, + 7179EB94262862270054765B /* ASCollectionView.swift */, + ); + path = Implementation; + sourceTree = ""; + }; + 7179EB95262862270054765B /* Support */ = { + isa = PBXGroup; + children = ( + 7179EB96262862270054765B /* ASOptionalSize.swift */, + 7179EB97262862270054765B /* ShrinkToFitWrapper.swift */, + 7179EB98262862270054765B /* Binding+Sequence.swift */, + 7179EB99262862270054765B /* RandomAccessCollection+Safe.swift */, + 7179EB9A262862270054765B /* ASPriorityCache.swift */, + 7179EB9B262862270054765B /* ASSelfSizingSettings.swift */, + 7179EB9C262862270054765B /* ASIndexedDictionary.swift */, + 7179EB9D262862270054765B /* GlobalConvenienceFunctions.swift */, + ); + path = Support; + sourceTree = ""; + }; + 7179EB9E262862270054765B /* UIKitExtensions */ = { + isa = PBXGroup; + children = ( + 7179EB9F262862270054765B /* UIScrollView+Convenience.swift */, + 7179EBA0262862270054765B /* UICollectionView+Convenience.swift */, + 7179EBA1262862270054765B /* UIView+Convenience.swift */, + ); + path = UIKitExtensions; + sourceTree = ""; + }; + 7179EBA2262862270054765B /* Layout */ = { + isa = PBXGroup; + children = ( + 7179EBA3262862270054765B /* ASCollectionViewLayout.swift */, + ); + path = Layout; + sourceTree = ""; + }; + 7179EBA6262862270054765B /* Config */ = { + isa = PBXGroup; + children = ( + 7179EBA7262862270054765B /* ASDragDropConfig.swift */, + 7179EBA8262862270054765B /* ClosureTypeAliases.swift */, + ); + path = Config; + sourceTree = ""; + }; + 7179EBAB262862270054765B /* Cells */ = { + isa = PBXGroup; + children = ( + 7179EBAC262862270054765B /* ASCollectionViewDecoration.swift */, + 7179EBAD262862270054765B /* ASCollectionViewSupplementaryView.swift */, + 7179EBAE262862270054765B /* ASSupplementaryCellID.swift */, + 7179EBB0262862270054765B /* ASCollectionViewCell.swift */, + ); + path = Cells; + sourceTree = ""; + }; + 7179EBB3262862280054765B /* FunctionBuilders */ = { + isa = PBXGroup; + children = ( + 7179EBB4262862280054765B /* ViewArrayBuilder.swift */, + 7179EBB5262862280054765B /* SectionArrayBuilder.swift */, + ); + path = FunctionBuilders; + sourceTree = ""; + }; + 7179EBB8262862280054765B /* UIKit */ = { + isa = PBXGroup; + children = ( + 7179EBBA262862280054765B /* AS_UICollectionView.swift */, + ); + path = UIKit; + sourceTree = ""; + }; + 7179EBBC262862280054765B /* Environment */ = { + isa = PBXGroup; + children = ( + 7179EBBD262862280054765B /* EnvironmentKeys.swift */, + ); + path = Environment; + sourceTree = ""; + }; + 7179EBBE262862280054765B /* Delegate */ = { + isa = PBXGroup; + children = ( + 7179EBBF262862280054765B /* ASCollectionViewDelegate.swift */, + ); + path = Delegate; + sourceTree = ""; + }; + 71816600261203A400614F25 /* Map */ = { + isa = PBXGroup; + children = ( + 71816616261203E100614F25 /* MapView.swift */, + 71403B2B26230B850047483C /* MapDot.swift */, + ); + path = Map; + sourceTree = ""; + }; + 718578902629B451002D2D44 /* Fonts */ = { + isa = PBXGroup; + children = ( + 7143D9492629E9B9003E20D1 /* NotoEmoji-Regular.ttf */, + 71F91E562629D98D00C40D33 /* ChapterEmoji-Regular.ttf */, + 7191D0E12601020F004D7326 /* LastResort-Regular.ttf */, + 7185787C2629AE1A002D2D44 /* NotoMusic-Regular.ttf */, + 71857822262986E6002D2D44 /* NotoSansAnatolianHieroglyphs-Regular.ttf */, + 718577C52629525F002D2D44 /* NotoSansCanadianAboriginal-Regular.ttf */, + 7185779D26294E7C002D2D44 /* NotoSansCoptic-Regular.ttf */, + 71857823262986E6002D2D44 /* NotoSansDeseret-Regular.ttf */, + 7185781E262986E6002D2D44 /* NotoSansElymaic-Regular.ttf */, + 718577BD262951CA002D2D44 /* NotoSansGeorgian-Regular.ttf */, + 7185781F262986E6002D2D44 /* NotoSansGunjalaGondi-Regular.ttf */, + 71857820262986E6002D2D44 /* NotoSansIndicSiyaqNumbers-Regular.ttf */, + 718577B526295076002D2D44 /* NotoSansLao-Regular.ttf */, + 718577AD26295003002D2D44 /* NotoSansMalayalam-Regular.ttf */, + 71857826262986E7002D2D44 /* NotoSansMasaramGondi-Regular.ttf */, + 71857825262986E6002D2D44 /* NotoSansMedefaidrin-Regular.ttf */, + 71857821262986E6002D2D44 /* NotoSansMongolian-Regular.ttf */, + 7185781D262986E6002D2D44 /* NotoSansNushu-Regular.ttf */, + 7185781B262986E5002D2D44 /* NotoSansOldSogdian-Regular.ttf */, + 7185787D2629AE1A002D2D44 /* NotoSansSignWriting-Regular.ttf */, + 71F91E412629CF2D00C40D33 /* NotoSansSinhala-Regular.ttf */, + 71857827262986E7002D2D44 /* NotoSansSogdian-Regular.ttf */, + 71857828262986E7002D2D44 /* NotoSansSoyombo-Regular.ttf */, + 718577D526295491002D2D44 /* NotoSansSymbols-Regular.ttf */, + 718577CD26295385002D2D44 /* NotoSansSymbols2-Regular.ttf */, + 718577A526294F7E002D2D44 /* NotoSansSyriac-Regular.ttf */, + 71857824262986E6002D2D44 /* NotoSansZanabazarSquare-Regular.ttf */, + 71F91E422629CF2D00C40D33 /* NotoSerifDogra-Regular.ttf */, + 71F91E402629CF2D00C40D33 /* NotoSerifNyiakengPuachueHmong-Regular.ttf */, + 71F91E432629CF2D00C40D33 /* NotoSerifTangut-Regular.ttf */, + 7185787B2629AE1A002D2D44 /* NotoSerifYezidi-Regular.ttf */, + ); + path = Fonts; + sourceTree = ""; + }; + 7185789B2629B487002D2D44 /* JSON */ = { + isa = PBXGroup; + children = ( + 71BB759B2626AD3100FA11C3 /* sample_text_data.json */, + 71CE628A260F4E38003F94BE /* blocks.json */, + 718D75B2260DAA060076AAE2 /* planes.json */, + 718D75B3260DAA060076AAE2 /* categories.json */, + 7185785C2629A23E002D2D44 /* emoji-input.json */, + ); + path = JSON; + sourceTree = ""; + }; + 7193AE0725FB20D700B459A6 /* Core */ = { + isa = PBXGroup; + children = ( + 71D89893261C1C8700035CCD /* Traits */, + 71D89892261C1C6B00035CCD /* Components */, + 7103E0CD2619E0FC00F8F8AF /* DataFlow */, + 7193AE0825FB20E300B459A6 /* Models */, + ); + path = Core; + sourceTree = ""; + }; + 7193AE0825FB20E300B459A6 /* Models */ = { + isa = PBXGroup; + children = ( + 71356F8726272F2E00CDABDA /* DomainModel */, + 71B1174C262704F700BDC1A5 /* ViewModel */, + ); + path = Models; + sourceTree = ""; + }; + 71A5B159262D25BA001FC1AF /* Illustrations */ = { + isa = PBXGroup; + children = ( + 71A5B170262D3392001FC1AF /* 1-1-character-combining.png */, + 71A5B160262D2D78001FC1AF /* 1-2-user-interface.png */, + 71A5B166262D3359001FC1AF /* 1-3-plane-view.png */, + 71A5B194262DA08C001FC1AF /* 1-4-last-resort.png */, + 71A5B176262D63CE001FC1AF /* 2-1-utf-32.png */, + 71A5B177262D63CE001FC1AF /* 2-2-utf-16.png */, + 71A5B178262D63CE001FC1AF /* 2-3-utf-8.png */, + 71A5B184262D7742001FC1AF /* 3-1-joining.png */, + 71A5B18C262D9CA2001FC1AF /* 3-2-modifiers.png */, + 71A5B18B262D9CA2001FC1AF /* 3-3-flags-and-keycaps.png */, + 71A5B18A262D9CA2001FC1AF /* 3-4-variations.png */, + ); + path = Illustrations; + sourceTree = ""; + }; + 71A63F172601CDEC00519C43 /* UIKit */ = { + isa = PBXGroup; + children = ( + 7123C4512600506B001ABF47 /* UIColor+Extensions.swift */, + 7191D0D726010127004D7326 /* UIFont+Extensions.swift */, + 71A63F192601CE0B00519C43 /* UIViewController+Extensions.swift */, + 71403B1C2622D4240047483C /* UICollectionView+Extensions.swift */, + 71CD670E26274D64001D3693 /* UIImage+backgroundColor.swift */, + ); + path = UIKit; + sourceTree = ""; + }; + 71A63F182601CDF600519C43 /* Foundation */ = { + isa = PBXGroup; + children = ( + 7141BA67262BDEE000B0ADBA /* String+chapterEmoji.swift */, + 71A740C526196BD700ACC64D /* String+codePoint.swift */, + 71698E8C261AE2C900B4A6C7 /* String+formatLinks.swift */, + 71356F7826272DA700CDABDA /* String+renderingTrait.swift */, + 71CD671426275DBF001D3693 /* UnicodeScalar+Extensions.swift */, + ); + path = Foundation; + sourceTree = ""; + }; + 71A7408126175BB200ACC64D /* Layers */ = { + isa = PBXGroup; + children = ( + 71A7408226175BC600ACC64D /* PlaneXrayUIViewMaskLayer.swift */, + 71A7408E26175BEE00ACC64D /* PlaneXrayBlocksLayer.swift */, + ); + path = Layers; + sourceTree = ""; + }; + 71AFA0B925FD1823008E5A4C /* BottomView */ = { + isa = PBXGroup; + children = ( + 71AFA0F725FD2091008E5A4C /* PlaneBottomView.swift */, + 71E52A432600544D0064BC6E /* Xray */, + 71AFA10225FD2166008E5A4C /* Info */, + ); + path = BottomView; + sourceTree = ""; + }; + 71AFA0D025FD1C90008E5A4C /* BlocksList */ = { + isa = PBXGroup; + children = ( + 71AFA0C525FD1A47008E5A4C /* BlocksListView.swift */, + 71AFA0BA25FD183C008E5A4C /* BlocksListItemView.swift */, + ); + path = BlocksList; + sourceTree = ""; + }; + 71AFA10225FD2166008E5A4C /* Info */ = { + isa = PBXGroup; + children = ( + 71AFA0D125FD1CAF008E5A4C /* PlaneIntroductionView.swift */, + 715AA8F6260D95D4003CD334 /* BlockInfo */, + 71AFA0D025FD1C90008E5A4C /* BlocksList */, + ); + path = Info; + sourceTree = ""; + }; + 71AFA11F25FDA21E008E5A4C /* Xray */ = { + isa = PBXGroup; + children = ( + 712CA4A825FDE38200BEF6E4 /* PlaneXrayUIView.swift */, + 71A7408126175BB200ACC64D /* Layers */, + ); + path = Xray; + sourceTree = ""; + }; + 71B02D24262BE2B60037FA30 /* 03-EncodingsWithUtf */ = { + isa = PBXGroup; + children = ( + 71B02D25262BE2B60037FA30 /* States */, + 71B02D27262BE2B60037FA30 /* Views */, + ); + path = "03-EncodingsWithUtf"; + sourceTree = ""; + }; + 71B02D25262BE2B60037FA30 /* States */ = { + isa = PBXGroup; + children = ( + 71B02D26262BE2B60037FA30 /* ChapterThreeState.swift */, + ); + path = States; + sourceTree = ""; + }; + 71B02D27262BE2B60037FA30 /* Views */ = { + isa = PBXGroup; + children = ( + 71B02D28262BE2B60037FA30 /* EncodeWithUtfView.swift */, + 71CF28B8261D602200894E8C /* TextInput */, + 71B02D29262BE2B60037FA30 /* SliderGroup */, + ); + path = Views; + sourceTree = ""; + }; + 71B02D29262BE2B60037FA30 /* SliderGroup */ = { + isa = PBXGroup; + children = ( + 71B02D2A262BE2B60037FA30 /* Slider */, + 71B02D2D262BE2B60037FA30 /* SliderGroup.swift */, + ); + path = SliderGroup; + sourceTree = ""; + }; + 71B02D2A262BE2B60037FA30 /* Slider */ = { + isa = PBXGroup; + children = ( + 71B02D2B262BE2B60037FA30 /* CodeSlider.swift */, + 71B02D2C262BE2B60037FA30 /* SliderCell.swift */, + ); + path = Slider; + sourceTree = ""; + }; + 71B1174C262704F700BDC1A5 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 713249652627F00700643B2B /* Extensions */, + 71356F8126272F2600CDABDA /* RenderingTrait.swift */, + 71F9581D26202D1D0095F391 /* CharacterBottomViewModel.swift */, + 71356F6F26270ED400CDABDA /* CharacterBottomViewModel+Types.swift */, + 71B1174D2627051400BDC1A5 /* SampleText.swift */, + 718578642629A451002D2D44 /* EmojiInput.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 71BE779225FA0D3A005D6CCB /* Base */ = { + isa = PBXGroup; + children = ( + 5EF2F9AD2054BBF300191409 /* BaseLiveViewController.swift */, + 5E3196F22061DEA40077BBD7 /* LiveViewSupport.swift */, + ); + path = Base; + sourceTree = ""; + }; + 71BE779425FA0D49005D6CCB /* ATourOfUnicode */ = { + isa = PBXGroup; + children = ( + 71D89873261BF9BD00035CCD /* 01-Welcome */, + 71BE779625FA0D6A005D6CCB /* 02-CodePointsBlocksAndPlanes */, + 71B02D24262BE2B60037FA30 /* 03-EncodingsWithUtf */, + 71D8987A261C1AF500035CCD /* 04-Emoji */, + ); + path = ATourOfUnicode; + sourceTree = ""; + }; + 71BE779625FA0D6A005D6CCB /* 02-CodePointsBlocksAndPlanes */ = { + isa = PBXGroup; + children = ( + 71CA9432260E406700F04F9F /* States */, + 71CA9431260E405E00F04F9F /* Views */, + ); + path = "02-CodePointsBlocksAndPlanes"; + sourceTree = ""; + }; + 71BE779825FA0D9A005D6CCB /* Chapters */ = { + isa = PBXGroup; + children = ( + 71D89924261C35E200035CCD /* Components */, + 71BE779425FA0D49005D6CCB /* ATourOfUnicode */, + ); + path = Chapters; + sourceTree = ""; + }; + 71BE77B725FA0E44005D6CCB /* Chapters */ = { + isa = PBXGroup; + children = ( + 71BE77F525FA0FAF005D6CCB /* 00-Welcome */, + 71BE77EF25FA0FAF005D6CCB /* 01-Basics */, + ); + path = Chapters; + sourceTree = ""; + }; + 71BE77EF25FA0FAF005D6CCB /* 01-Basics */ = { + isa = PBXGroup; + children = ( + 71BE77F325FA0FAF005D6CCB /* 01-IntroductionToUnicode */, + 71BE77F025FA0FAF005D6CCB /* 03-EncodingWithUtf */, + ); + path = "01-Basics"; + sourceTree = ""; + }; + 71BE77F025FA0FAF005D6CCB /* 03-EncodingWithUtf */ = { + isa = PBXGroup; + children = ( + 71BE77FC25FA0FC1005D6CCB /* EncodingWithUtfViewController.xib */, + ); + path = "03-EncodingWithUtf"; + sourceTree = ""; + }; + 71BE77F325FA0FAF005D6CCB /* 01-IntroductionToUnicode */ = { + isa = PBXGroup; + children = ( + 71BE77F425FA0FAF005D6CCB /* IntroductionToUnicodeViewController.xib */, + ); + path = "01-IntroductionToUnicode"; + sourceTree = ""; + }; + 71BE77F525FA0FAF005D6CCB /* 00-Welcome */ = { + isa = PBXGroup; + children = ( + 71BE783525FA46BD005D6CCB /* WelcomeViewController.xib */, + ); + path = "00-Welcome"; + sourceTree = ""; + }; + 71BE781A25FA12E2005D6CCB /* Extensions */ = { + isa = PBXGroup; + children = ( + 71403B122622C4930047483C /* Swift */, + 71698E43261AA35000B4A6C7 /* CoreData */, + 71FE70562611C421004B7DA9 /* CoreAnimation */, + 71A63F182601CDF600519C43 /* Foundation */, + 71A63F172601CDEC00519C43 /* UIKit */, + 71CA9422260E1BE400F04F9F /* SwiftUI */, + ); + path = Extensions; + sourceTree = ""; + }; + 71CA9422260E1BE400F04F9F /* SwiftUI */ = { + isa = PBXGroup; + children = ( + 71A74065261748A900ACC64D /* View+Modifiers.swift */, + 71CA9429260E403600F04F9F /* View+RoundedCorner.swift */, + 71F64762261464640087ED8A /* View+ifTrue.swift */, + 71B11753262706E300BDC1A5 /* View+EnumeratedForEach.swift */, + ); + path = SwiftUI; + sourceTree = ""; + }; + 71CA9431260E405E00F04F9F /* Views */ = { + isa = PBXGroup; + children = ( + 718D75EB260DCC850076AAE2 /* CodePointsBlocksAndPlanesView.swift */, + 71AFA0B925FD1823008E5A4C /* BottomView */, + 7123C3D925FFB256001ABF47 /* Characters */, + 71333BA425FB428900CBA8CB /* Planes */, + ); + path = Views; + sourceTree = ""; + }; + 71CA9432260E406700F04F9F /* States */ = { + isa = PBXGroup; + children = ( + 71CA9433260E409300F04F9F /* ChapterTwoState.swift */, + 71D8989A261C1DEF00035CCD /* ChapterTwoAction.swift */, + 71D898A2261C1DFA00035CCD /* ChapterTwoReduce.swift */, + ); + path = States; + sourceTree = ""; + }; + 71CF28B8261D602200894E8C /* TextInput */ = { + isa = PBXGroup; + children = ( + 71CF28B9261D60DA00894E8C /* TextInputView.swift */, + 71CF28D7261D6F1B00894E8C /* TextInputField.swift */, + 71CF28C2261D6CD800894E8C /* TextInputOption.swift */, + 71CF28C8261D6CE500894E8C /* TextInputOptionItem.swift */, + ); + path = TextInput; + sourceTree = ""; + }; + 71D89873261BF9BD00035CCD /* 01-Welcome */ = { + isa = PBXGroup; + children = ( + 71403AE62622B92F0047483C /* Views */, + ); + path = "01-Welcome"; + sourceTree = ""; + }; + 71D8987A261C1AF500035CCD /* 04-Emoji */ = { + isa = PBXGroup; + children = ( + 71D8987B261C1B4E00035CCD /* States */, + 71D8987C261C1B5400035CCD /* Views */, + ); + path = "04-Emoji"; + sourceTree = ""; + }; + 71D8987B261C1B4E00035CCD /* States */ = { + isa = PBXGroup; + children = ( + 71D89889261C1C0D00035CCD /* ChapterFourState.swift */, + ); + path = States; + sourceTree = ""; + }; + 71D8987C261C1B5400035CCD /* Views */ = { + isa = PBXGroup; + children = ( + 71D8987D261C1BF500035CCD /* EmojiView.swift */, + 7143D92E2629DD41003E20D1 /* Components */, + 715E23562624844C00CC6679 /* EmojiTopView */, + ); + path = Views; + sourceTree = ""; + }; + 71D89892261C1C6B00035CCD /* Components */ = { + isa = PBXGroup; + children = ( + 71403B092622BEC00047483C /* App.swift */, + 71403AF42622BD820047483C /* Colors.swift */, + 712CA49E25FDD12B00BEF6E4 /* DataProvider.swift */, + 71F647DB261487FB0087ED8A /* Fonts.swift */, + 718D75C4260DAB230076AAE2 /* FileHelper.swift */, + 7141BA24262B417700B0ADBA /* ChapterEmojiFont.swift */, + ); + path = Components; + sourceTree = ""; + }; + 71D89893261C1C8700035CCD /* Traits */ = { + isa = PBXGroup; + children = ( + 71698E31261A9FA000B4A6C7 /* NibInstantiable.swift */, + ); + path = Traits; + sourceTree = ""; + }; + 71D89924261C35E200035CCD /* Components */ = { + isa = PBXGroup; + children = ( + 71857804262985FC002D2D44 /* DebugAlert.swift */, + 7100B9F52620C08900ACE8AB /* ViewSizeKey.swift */, + 71F958352620A6920095F391 /* Badge */, + 71698E74261AD6FA00B4A6C7 /* RichTextDescription */, + 71816600261203A400614F25 /* Map */, + 71AFA11F25FDA21E008E5A4C /* Xray */, + 715AA8DF260D93A4003CD334 /* CharacterBottomView */, + ); + path = Components; + sourceTree = ""; + }; + 71E52A432600544D0064BC6E /* Xray */ = { + isa = PBXGroup; + children = ( + 71E52A45260054870064BC6E /* PlaneIntroductionXrayView.swift */, + ); + path = Xray; + sourceTree = ""; + }; + 71F52CE9260DED5800AC82F4 /* XIBs_LiveViewTestApp */ = { + isa = PBXGroup; + children = ( + 71F52CEB260DED5800AC82F4 /* CharactersCollectionViewCell.xib */, + 7102C1A026108A100068E8A1 /* CharactersCollectionHeaderView.xib */, + ); + path = XIBs_LiveViewTestApp; + sourceTree = ""; + }; + 71F9581026202CF50095F391 /* Managed */ = { + isa = PBXGroup; + children = ( + 71F9580A26202B2E0095F391 /* CharacterInformation.swift */, + 7103E0E32619E7AF00F8F8AF /* BlockDescription.swift */, + 7141BA12262B3B5D00B0ADBA /* CLDRAnnotation.swift */, + ); + path = Managed; + sourceTree = ""; + }; + 71F958352620A6920095F391 /* Badge */ = { + isa = PBXGroup; + children = ( + 71F958362620A69D0095F391 /* Badge.swift */, + ); + path = Badge; + sourceTree = ""; + }; + 71FE70562611C421004B7DA9 /* CoreAnimation */ = { + isa = PBXGroup; + children = ( + 71FE70572611C42E004B7DA9 /* CATransaction+Extensions.swift */, + ); + path = CoreAnimation; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 5E086DBF2051C5A7004D8D25 /* PlaygroundBook */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5E086DC52051C5A7004D8D25 /* Build configuration list for PBXNativeTarget "PlaygroundBook" */; + buildPhases = ( + 5E086DD22051E35D004D8D25 /* Copy Book Contents */, + 5E086DC62051DB17004D8D25 /* Resources */, + 71C1266225F9F346009D2FBF /* Run SwiftLint */, + ); + buildRules = ( + 5E086DD42051E415004D8D25 /* PBXBuildRule */, + ); + dependencies = ( + 5EF2F9B52054BDD100191409 /* PBXTargetDependency */, + ); + name = PlaygroundBook; + productName = PlaygroundBook; + productReference = 5E086DC02051C5A7004D8D25 /* PlaygroundBook.playgroundbook */; + productType = "com.apple.product-type.bundle"; + }; + 5EA2E3BC2056F35A00416A35 /* LiveViewTestApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5EA2E3CE2056F35B00416A35 /* Build configuration list for PBXNativeTarget "LiveViewTestApp" */; + buildPhases = ( + 5EA2E3B92056F35A00416A35 /* Sources */, + 5EA2E3BA2056F35A00416A35 /* Frameworks */, + 71F52C76260DE61A00AC82F4 /* Hack XIBs */, + 5EA2E3BB2056F35A00416A35 /* Resources */, + 5EA2E3D92056F8BD00416A35 /* Embed Frameworks */, + 5EA2E3DD2056FB2900416A35 /* Embed Playground Book PublicResources & PrivateResources */, + 71A63F0F2601CD9700519C43 /* Run SwiftLint */, + ); + buildRules = ( + ); + dependencies = ( + 7179EB602628614D0054765B /* PBXTargetDependency */, + 7179EBF7262862640054765B /* PBXTargetDependency */, + ); + name = LiveViewTestApp; + productName = LiveViewTestApp; + productReference = 5EA2E3BD2056F35A00416A35 /* LiveViewTestApp.app */; + productType = "com.apple.product-type.application"; + }; + 5EF2F9AA2054BBF300191409 /* BookCore */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5EF2F9AF2054BBF300191409 /* Build configuration list for PBXNativeTarget "BookCore" */; + buildPhases = ( + 5EF2F9A72054BBF300191409 /* Sources */, + 5EF2F9A82054BBF300191409 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 7179EB5E262861450054765B /* PBXTargetDependency */, + 7179EBF3262862540054765B /* PBXTargetDependency */, + ); + name = BookCore; + productName = Book_Sources; + productReference = 5EF2F9AB2054BBF300191409 /* libBookCore.a */; + productType = "com.apple.product-type.library.static"; + }; + 7179EB22262860C60054765B /* DifferenceKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7179EB27262860C60054765B /* Build configuration list for PBXNativeTarget "DifferenceKit" */; + buildPhases = ( + 7179EB1F262860C60054765B /* Sources */, + 7179EB20262860C60054765B /* Frameworks */, + 7179EB21262860C60054765B /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DifferenceKit; + productName = DifferenceKit; + productReference = 7179EB23262860C60054765B /* libDifferenceKit.a */; + productType = "com.apple.product-type.library.static"; + }; + 7179EB772628620B0054765B /* ASCollectionView */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7179EB7C2628620B0054765B /* Build configuration list for PBXNativeTarget "ASCollectionView" */; + buildPhases = ( + 7179EB742628620B0054765B /* Sources */, + 7179EB752628620B0054765B /* Frameworks */, + 7179EB762628620B0054765B /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 7179EBF12628624C0054765B /* PBXTargetDependency */, + ); + name = ASCollectionView; + productName = ASCollectionView; + productReference = 7179EB782628620B0054765B /* libASCollectionView.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 5E2A7ADD204F611300F4E17A /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1240; + LastUpgradeCheck = 1220; + TargetAttributes = { + 5E086DBF2051C5A7004D8D25 = { + CreatedOnToolsVersion = 9.3; + LastSwiftMigration = 1240; + }; + 5EA2E3BC2056F35A00416A35 = { + CreatedOnToolsVersion = 9.3; + LastSwiftMigration = 1000; + }; + 5EF2F9AA2054BBF300191409 = { + CreatedOnToolsVersion = 9.3; + LastSwiftMigration = 1020; + }; + 7179EB22262860C60054765B = { + CreatedOnToolsVersion = 12.4; + LastSwiftMigration = 1240; + }; + 7179EB772628620B0054765B = { + CreatedOnToolsVersion = 12.4; + LastSwiftMigration = 1240; + }; + }; + }; + buildConfigurationList = 5E2A7AE0204F611300F4E17A /* Build configuration list for PBXProject "PlaygroundBook" */; + compatibilityVersion = "Xcode 11.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 5E2A7ADC204F611300F4E17A; + productRefGroup = 5E086DA4204F706E004D8D25 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 5E086DBF2051C5A7004D8D25 /* PlaygroundBook */, + 5EF2F9AA2054BBF300191409 /* BookCore */, + 5EA2E3BC2056F35A00416A35 /* LiveViewTestApp */, + 7179EB22262860C60054765B /* DifferenceKit */, + 7179EB772628620B0054765B /* ASCollectionView */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 5E086DC62051DB17004D8D25 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 71BB759C2626AD3100FA11C3 /* sample_text_data.json in Resources */, + 71857838262986E7002D2D44 /* NotoSansAnatolianHieroglyphs-Regular.ttf in Resources */, + 7185783A262986E7002D2D44 /* NotoSansDeseret-Regular.ttf in Resources */, + 718577F22629786F002D2D44 /* NotoSansSyriac-Regular.ttf in Resources */, + 7185783E262986E7002D2D44 /* NotoSansMedefaidrin-Regular.ttf in Resources */, + 718577EF2629786F002D2D44 /* NotoSansCoptic-Regular.ttf in Resources */, + 71F91E4A2629CF2D00C40D33 /* NotoSerifTangut-Regular.ttf in Resources */, + 71A5B17B262D63CF001FC1AF /* 2-3-utf-8.png in Resources */, + 71A5B195262DA08C001FC1AF /* 1-4-last-resort.png in Resources */, + 718577ED2629786F002D2D44 /* NotoSansSymbols2-Regular.ttf in Resources */, + 718577F02629786F002D2D44 /* NotoSansCanadianAboriginal-Regular.ttf in Resources */, + 71A5B185262D7742001FC1AF /* 3-1-joining.png in Resources */, + 71857830262986E7002D2D44 /* NotoSansElymaic-Regular.ttf in Resources */, + 718577F12629786F002D2D44 /* NotoSansLao-Regular.ttf in Resources */, + 71857832262986E7002D2D44 /* NotoSansGunjalaGondi-Regular.ttf in Resources */, + 7185787E2629AE1A002D2D44 /* NotoSerifYezidi-Regular.ttf in Resources */, + 718577F42629786F002D2D44 /* NotoSansSymbols-Regular.ttf in Resources */, + 71CE62CA26108507003F94BE /* CharactersCollectionHeaderView.xib in Resources */, + 718577EC2629786F002D2D44 /* LastResort-Regular.ttf in Resources */, + 5EF2F97C2054B6E400191409 /* ManifestPlist.strings in Resources */, + 71857844262986E7002D2D44 /* NotoSansSoyombo-Regular.ttf in Resources */, + 71A5B167262D3359001FC1AF /* 1-3-plane-view.png in Resources */, + 71F91E572629D98D00C40D33 /* ChapterEmoji-Regular.ttf in Resources */, + 7185783C262986E7002D2D44 /* NotoSansZanabazarSquare-Regular.ttf in Resources */, + 7143D94A2629E9B9003E20D1 /* NotoEmoji-Regular.ttf in Resources */, + 71857836262986E7002D2D44 /* NotoSansMongolian-Regular.ttf in Resources */, + 718578822629AE1A002D2D44 /* NotoSansSignWriting-Regular.ttf in Resources */, + 7185782E262986E7002D2D44 /* NotoSansNushu-Regular.ttf in Resources */, + 718D75B5260DAA060076AAE2 /* categories.json in Resources */, + 5E551EC52371FC3F00784365 /* Assets.xcassets in Resources */, + 71F91E442629CF2D00C40D33 /* NotoSerifNyiakengPuachueHmong-Regular.ttf in Resources */, + 71857834262986E7002D2D44 /* NotoSansIndicSiyaqNumbers-Regular.ttf in Resources */, + 71A5B179262D63CF001FC1AF /* 2-1-utf-32.png in Resources */, + 71857842262986E7002D2D44 /* NotoSansSogdian-Regular.ttf in Resources */, + 71A5B171262D3393001FC1AF /* 1-1-character-combining.png in Resources */, + 7103E0C52619DB6E00F8F8AF /* UnicodeData.momd in Resources */, + 71F91E462629CF2D00C40D33 /* NotoSansSinhala-Regular.ttf in Resources */, + 718577F32629786F002D2D44 /* NotoSansMalayalam-Regular.ttf in Resources */, + 718577EE2629786F002D2D44 /* NotoSansGeorgian-Regular.ttf in Resources */, + 71BE77FD25FA0FC1005D6CCB /* EncodingWithUtfViewController.xib in Resources */, + 71A5B161262D2D78001FC1AF /* 1-2-user-interface.png in Resources */, + 7103E09B2619D98400F8F8AF /* UnicodeData.sqlite in Resources */, + 71A5B18E262D9CA2001FC1AF /* 3-3-flags-and-keycaps.png in Resources */, + 71857840262986E7002D2D44 /* NotoSansMasaramGondi-Regular.ttf in Resources */, + 7185785D2629A23E002D2D44 /* emoji-input.json in Resources */, + 718578802629AE1A002D2D44 /* NotoMusic-Regular.ttf in Resources */, + 71A5B18F262D9CA2001FC1AF /* 3-2-modifiers.png in Resources */, + 71BE77F725FA0FAF005D6CCB /* IntroductionToUnicodeViewController.xib in Resources */, + 7185782A262986E7002D2D44 /* NotoSansOldSogdian-Regular.ttf in Resources */, + 71A5B18D262D9CA2001FC1AF /* 3-4-variations.png in Resources */, + 71F91E482629CF2D00C40D33 /* NotoSerifDogra-Regular.ttf in Resources */, + 718D75B4260DAA060076AAE2 /* planes.json in Resources */, + 71CE628B260F4E38003F94BE /* blocks.json in Resources */, + 71F52C67260DE5F400AC82F4 /* CharactersCollectionViewCell.xib in Resources */, + 71A5B12C262BFAEB001FC1AF /* License.pdf in Resources */, + 71BE783625FA46BD005D6CCB /* WelcomeViewController.xib in Resources */, + 71A5B17A262D63CF001FC1AF /* 2-2-utf-16.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5EA2E3BB2056F35A00416A35 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 71BB759D2626AD3100FA11C3 /* sample_text_data.json in Resources */, + 7185785E2629A23E002D2D44 /* emoji-input.json in Resources */, + 71857839262986E7002D2D44 /* NotoSansAnatolianHieroglyphs-Regular.ttf in Resources */, + 719DEDF425FE56DE007DE3B6 /* Assets.xcassets in Resources */, + 7102C1A126108A100068E8A1 /* CharactersCollectionHeaderView.xib in Resources */, + 718577C72629525F002D2D44 /* NotoSansCanadianAboriginal-Regular.ttf in Resources */, + 718577B726295076002D2D44 /* NotoSansLao-Regular.ttf in Resources */, + 71857841262986E7002D2D44 /* NotoSansMasaramGondi-Regular.ttf in Resources */, + 71857835262986E7002D2D44 /* NotoSansIndicSiyaqNumbers-Regular.ttf in Resources */, + 718577CF26295385002D2D44 /* NotoSansSymbols2-Regular.ttf in Resources */, + 71F6482D261495F60087ED8A /* LastResort-Regular.ttf in Resources */, + 71F91E492629CF2D00C40D33 /* NotoSerifDogra-Regular.ttf in Resources */, + 71F91E582629D98D00C40D33 /* ChapterEmoji-Regular.ttf in Resources */, + 7185783B262986E7002D2D44 /* NotoSansDeseret-Regular.ttf in Resources */, + 71857833262986E7002D2D44 /* NotoSansGunjalaGondi-Regular.ttf in Resources */, + 71857831262986E7002D2D44 /* NotoSansElymaic-Regular.ttf in Resources */, + 7185782B262986E7002D2D44 /* NotoSansOldSogdian-Regular.ttf in Resources */, + 7185783F262986E7002D2D44 /* NotoSansMedefaidrin-Regular.ttf in Resources */, + 718D75BC260DAA0B0076AAE2 /* planes.json in Resources */, + 7185783D262986E7002D2D44 /* NotoSansZanabazarSquare-Regular.ttf in Resources */, + 718578832629AE1A002D2D44 /* NotoSansSignWriting-Regular.ttf in Resources */, + 718577D726295492002D2D44 /* NotoSansSymbols-Regular.ttf in Resources */, + 718577A726294F7E002D2D44 /* NotoSansSyriac-Regular.ttf in Resources */, + 71F91E452629CF2D00C40D33 /* NotoSerifNyiakengPuachueHmong-Regular.ttf in Resources */, + 718D75BD260DAA0B0076AAE2 /* categories.json in Resources */, + 5EA2E3CA2056F35B00416A35 /* LiveViewTestAppLaunchScreen.storyboard in Resources */, + 7103E0C62619DB6E00F8F8AF /* UnicodeData.momd in Resources */, + 71F91E4B2629CF2D00C40D33 /* NotoSerifTangut-Regular.ttf in Resources */, + 7143D94B2629E9B9003E20D1 /* NotoEmoji-Regular.ttf in Resources */, + 718577BF262951CA002D2D44 /* NotoSansGeorgian-Regular.ttf in Resources */, + 71857843262986E7002D2D44 /* NotoSansSogdian-Regular.ttf in Resources */, + 7103E09C2619D98400F8F8AF /* UnicodeData.sqlite in Resources */, + 71F91E472629CF2D00C40D33 /* NotoSansSinhala-Regular.ttf in Resources */, + 7185779F26294E7C002D2D44 /* NotoSansCoptic-Regular.ttf in Resources */, + 716C564E2601FB29000160FF /* WelcomeViewController.xib in Resources */, + 718577AF26295003002D2D44 /* NotoSansMalayalam-Regular.ttf in Resources */, + 7185787F2629AE1A002D2D44 /* NotoSerifYezidi-Regular.ttf in Resources */, + 7185782F262986E7002D2D44 /* NotoSansNushu-Regular.ttf in Resources */, + 71F52CED260DED5800AC82F4 /* CharactersCollectionViewCell.xib in Resources */, + 71857845262986E7002D2D44 /* NotoSansSoyombo-Regular.ttf in Resources */, + 718578812629AE1A002D2D44 /* NotoMusic-Regular.ttf in Resources */, + 71857837262986E7002D2D44 /* NotoSansMongolian-Regular.ttf in Resources */, + 71CE628C260F4E39003F94BE /* blocks.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 5EA2E3DD2056FB2900416A35 /* Embed Playground Book PublicResources & PrivateResources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(BUILT_PRODUCTS_DIR)/PlaygroundBook.playgroundbook", + ); + name = "Embed Playground Book PublicResources & PrivateResources"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/embed-resources-marker", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "PLAYGROUND_BOOK_PATH=\"${BUILT_PRODUCTS_DIR}/${PLAYGROUND_BOOK_FILE_NAME}.playgroundbook\"\nPRIVATERESOURCES_FOLDER_PATH=\"${PLAYGROUND_BOOK_PATH}/Contents/PrivateResources\"\nPUBLICRESOURCES_FOLDER_PATH=\"${PLAYGROUND_BOOK_PATH}/Contents/PublicResources\"\n\nDESTINATION_FOLDER_PATH=\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\n\nif [[ -d \"${PRIVATERESOURCES_FOLDER_PATH}\" ]]; then\n ditto \"${PRIVATERESOURCES_FOLDER_PATH}\" \"${DESTINATION_FOLDER_PATH}\"\nfi\n\nif [[ -d \"${PUBLICRESOURCES_FOLDER_PATH}\" ]]; then\n ditto \"${PUBLICRESOURCES_FOLDER_PATH}\" \"${DESTINATION_FOLDER_PATH}\"\nfi\n\nmkdir -p \"${DERIVED_FILE_DIR}\"\ntouch \"${DERIVED_FILE_DIR}/embed-resources-marker\"\n"; + }; + 71A63F0F2601CD9700519C43 /* Run SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run SwiftLint"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; + 71C1266225F9F346009D2FBF /* Run SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run SwiftLint"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; + 71F52C76260DE61A00AC82F4 /* Hack XIBs */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Hack XIBs"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "PRIVATERESOURCES_FOLDER_PATH=\"${SRCROOT}/PlaygroundBook/PrivateResources\"\nXIBS_INPUT=\"${PRIVATERESOURCES_FOLDER_PATH}/XIBs\"\nXIBS_OUTPUT=\"${PRIVATERESOURCES_FOLDER_PATH}/XIBs_LiveViewTestApp\"\n\nrm -rf \"${XIBS_OUTPUT}\"\ncp -R \"${XIBS_INPUT}\" \"${XIBS_OUTPUT}\"\n\nfind \"${XIBS_OUTPUT}\" -type f | xargs sed -i '' -e 's/BookCore/LiveViewTestApp/g'\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 5EA2E3B92056F35A00416A35 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 71F9583E2620B74B0095F391 /* Badge.swift in Sources */, + 71A7409B26175C4D00ACC64D /* PlaneXrayBlocksLayer.swift in Sources */, + 715E23682624850300CC6679 /* EmojiFormulaView.swift in Sources */, + 719BC483260D73990046C3ED /* CharactersCollectionViewCell.swift in Sources */, + 71356F7A26272DA700CDABDA /* String+renderingTrait.swift in Sources */, + 719BC47C260D73960046C3ED /* CharactersCollectionViewController.swift in Sources */, + 71B02D31262BE2B60037FA30 /* EncodeWithUtfView.swift in Sources */, + 7103E0E42619E7AF00F8F8AF /* BlockDescription.swift in Sources */, + 71D89894261C1C9C00035CCD /* EmojiView.swift in Sources */, + 719BC459260D73350046C3ED /* BlocksListItemView.swift in Sources */, + 71CE62D426108519003F94BE /* CharactersCollectionHeaderView.swift in Sources */, + 715AA8E9260D95C0003CD334 /* BlockInfoView.swift in Sources */, + 71403B182622C6120047483C /* CateIterable+Extensions.swift in Sources */, + 719BC5F8260D74E70046C3ED /* BaseLiveViewController.swift in Sources */, + 719BC44B260D732F0046C3ED /* PlaneIntroductionView.swift in Sources */, + 71D8989C261C1DEF00035CCD /* ChapterTwoAction.swift in Sources */, + 719BC5D5260D74B40046C3ED /* Block.swift in Sources */, + 71A5B14C262C6040001FC1AF /* CharacterBottomView+contextConfiguration.swift in Sources */, + 719BC491260D73B40046C3ED /* Plane.swift in Sources */, + 719BC42F260D73030046C3ED /* PlaneBottomView.swift in Sources */, + 71698E9E261AEF8E00B4A6C7 /* PlaneIntroductionXrayView.swift in Sources */, + 71698E8E261AE2C900B4A6C7 /* String+formatLinks.swift in Sources */, + 71D898A4261C1DFA00035CCD /* ChapterTwoReduce.swift in Sources */, + 713249612627EFEB00643B2B /* UnicodeGeneralCategory+Extensions.swift in Sources */, + 71F64764261464640087ED8A /* View+ifTrue.swift in Sources */, + 716C56272601F54B000160FF /* AppDelegate.swift in Sources */, + 719BC611260D75830046C3ED /* UIColor+Extensions.swift in Sources */, + 71248D5026256C770077F634 /* ZWJFomulaInputView.swift in Sources */, + 71CF28CA261D6CE500894E8C /* TextInputOptionItem.swift in Sources */, + 71403B2D26230B850047483C /* MapDot.swift in Sources */, + 71B1174F2627051400BDC1A5 /* SampleText.swift in Sources */, + 71CE62B32610805D003F94BE /* View+RoundedCorner.swift in Sources */, + 719BC5C0260D74850046C3ED /* DataProvider.swift in Sources */, + 71A740C726196BD700ACC64D /* String+codePoint.swift in Sources */, + 71698E33261A9FA000B4A6C7 /* NibInstantiable.swift in Sources */, + 7141BA16262B3B5D00B0ADBA /* CLDRAnnotation.swift in Sources */, + 71E83479260E41BC008954E8 /* ChapterTwoState.swift in Sources */, + 71403B0B2622BEC00047483C /* App.swift in Sources */, + 71F9580C26202B2E0095F391 /* CharacterInformation.swift in Sources */, + 71B02D33262BE2B60037FA30 /* CodeSlider.swift in Sources */, + 71D8988B261C1C0D00035CCD /* ChapterFourState.swift in Sources */, + 71CF28C4261D6CD800894E8C /* TextInputOption.swift in Sources */, + 71B02D2F262BE2B60037FA30 /* ChapterThreeState.swift in Sources */, + 71B02D37262BE2B60037FA30 /* SliderGroup.swift in Sources */, + 71403B022622BE380047483C /* IntroductionToUnicodeView.swift in Sources */, + 7179EB1A26285F020054765B /* EmojiGridInputView.swift in Sources */, + 71403AF62622BD820047483C /* Colors.swift in Sources */, + 71F9582E2620A68C0095F391 /* CharactersBottomTitleView.swift in Sources */, + 719BC5EA260D74CE0046C3ED /* Language.swift in Sources */, + 71248D4926256B920077F634 /* EmojiBottomTitleView.swift in Sources */, + 71A740B52618B68300ACC64D /* CharacterIntroductionView.swift in Sources */, + 71A7409526175C4B00ACC64D /* PlaneXrayUIViewMaskLayer.swift in Sources */, + 71F9581F26202D1D0095F391 /* CharacterBottomViewModel.swift in Sources */, + 71F64757261442770087ED8A /* PlaneListView.swift in Sources */, + 71F647DD261487FB0087ED8A /* Fonts.swift in Sources */, + 71CD671626275DBF001D3693 /* UnicodeScalar+Extensions.swift in Sources */, + 719BC5C7260D749A0046C3ED /* LiveViewSupport.swift in Sources */, + 71403B1E2622D4240047483C /* UICollectionView+Extensions.swift in Sources */, + 7141BA26262B417700B0ADBA /* ChapterEmojiFont.swift in Sources */, + 718D7600260DCE910076AAE2 /* CodePointsBlocksAndPlanesView.swift in Sources */, + 7143D968262ABD18003E20D1 /* ZWJInputListItem.swift in Sources */, + 715E2386262485C800CC6679 /* EmojiBottomView.swift in Sources */, + 71857805262985FC002D2D44 /* DebugAlert.swift in Sources */, + 7100B9F72620C08900ACE8AB /* ViewSizeKey.swift in Sources */, + 71CD671026274D65001D3693 /* UIImage+backgroundColor.swift in Sources */, + 719BC46E260D736B0046C3ED /* PlaneXrayUIView.swift in Sources */, + 71356F8326272F2600CDABDA /* RenderingTrait.swift in Sources */, + 719BC422260D72D60046C3ED /* CharacterBottomView.swift in Sources */, + 719BC5E3260D74C90046C3ED /* UIViewController+Extensions.swift in Sources */, + 71698E86261AD93600B4A6C7 /* RichTextDescriptionView.swift in Sources */, + 715E2362262484E400CC6679 /* EmojiLargeImputView.swift in Sources */, + 71DBC65A2612075B00C736ED /* MapView.swift in Sources */, + 71F64751261441AD0087ED8A /* PlaneListItemView.swift in Sources */, + 718578652629A451002D2D44 /* EmojiInput.swift in Sources */, + 71B11755262706E300BDC1A5 /* View+EnumeratedForEach.swift in Sources */, + 71A740AF2618B5BF00ACC64D /* Character3DView.swift in Sources */, + 71A74067261748A900ACC64D /* View+Modifiers.swift in Sources */, + 719BC5DC260D74BC0046C3ED /* UIFont+Extensions.swift in Sources */, + 71B02D35262BE2B60037FA30 /* SliderCell.swift in Sources */, + 7103E0D42619E11700F8F8AF /* Store.swift in Sources */, + 71CF28D9261D6F1B00894E8C /* TextInputField.swift in Sources */, + 7143D9312629DD60003E20D1 /* EmojiCell.swift in Sources */, + 715E2359262484BE00CC6679 /* EmojiTopView.swift in Sources */, + 71CF28BB261D60DA00894E8C /* TextInputView.swift in Sources */, + 719BC5F1260D74D50046C3ED /* Country.swift in Sources */, + 71698E46261AA3CD00B4A6C7 /* NSManagedObject+Extensions.swift in Sources */, + 719BC5B9260D74670046C3ED /* Category.swift in Sources */, + 719BC452260D73320046C3ED /* BlocksListView.swift in Sources */, + 71FE70722611C4BC004B7DA9 /* CATransaction+Extensions.swift in Sources */, + 71356F7126270ED400CDABDA /* CharacterBottomViewModel+Types.swift in Sources */, + 718D75C6260DAB230076AAE2 /* FileHelper.swift in Sources */, + 7141BA69262BDEE000B0ADBA /* String+chapterEmoji.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 5EF2F9A72054BBF300191409 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 71B02D2E262BE2B60037FA30 /* ChapterThreeState.swift in Sources */, + 71CF28BA261D60DA00894E8C /* TextInputView.swift in Sources */, + 71F958372620A69D0095F391 /* Badge.swift in Sources */, + 712A7F12262AF7AE00A7F746 /* ZWJInputListItem.swift in Sources */, + 71356F7926272DA700CDABDA /* String+renderingTrait.swift in Sources */, + 71FE70582611C42E004B7DA9 /* CATransaction+Extensions.swift in Sources */, + 715E23672624850300CC6679 /* EmojiFormulaView.swift in Sources */, + 719BC421260D72D50046C3ED /* CharacterBottomView.swift in Sources */, + 714AD5FC260DCEE7008C8BB2 /* CodePointsBlocksAndPlanesView.swift in Sources */, + 712CA4A925FDE38200BEF6E4 /* PlaneXrayUIView.swift in Sources */, + 71248D4826256B920077F634 /* EmojiBottomTitleView.swift in Sources */, + 71CE62D326108519003F94BE /* CharactersCollectionHeaderView.swift in Sources */, + 5E3196F32061DEA40077BBD7 /* LiveViewSupport.swift in Sources */, + 7193AE0A25FB20EF00B459A6 /* Block.swift in Sources */, + 71A7408326175BC600ACC64D /* PlaneXrayUIViewMaskLayer.swift in Sources */, + 715AA8E8260D95C0003CD334 /* BlockInfoView.swift in Sources */, + 71403B142622C49F0047483C /* CateIterable+Extensions.swift in Sources */, + 71CD670F26274D65001D3693 /* UIImage+backgroundColor.swift in Sources */, + 71356F8226272F2600CDABDA /* RenderingTrait.swift in Sources */, + 71CA942A260E403600F04F9F /* View+RoundedCorner.swift in Sources */, + 71F9580B26202B2E0095F391 /* CharacterInformation.swift in Sources */, + 71A5B14B262C6040001FC1AF /* CharacterBottomView+contextConfiguration.swift in Sources */, + 715E237C2624854E00CC6679 /* EmojiGridInputView.swift in Sources */, + 71A63F3D2601D28E00519C43 /* CharacterIntroductionView.swift in Sources */, + 7123C428260046FA001ABF47 /* CharactersCollectionViewCell.swift in Sources */, + 71698E7B261AD70A00B4A6C7 /* RichTextDescriptionView.swift in Sources */, + 71B02D34262BE2B60037FA30 /* SliderCell.swift in Sources */, + 7193AE1425FB21FA00B459A6 /* Language.swift in Sources */, + 71A63F342601D12E00519C43 /* Character3DView.swift in Sources */, + 71CF28D8261D6F1B00894E8C /* TextInputField.swift in Sources */, + 71698E51261AAFAD00B4A6C7 /* Store.swift in Sources */, + 71698E32261A9FA000B4A6C7 /* NibInstantiable.swift in Sources */, + 71B11754262706E300BDC1A5 /* View+EnumeratedForEach.swift in Sources */, + 71698E8D261AE2C900B4A6C7 /* String+formatLinks.swift in Sources */, + 71816617261203E100614F25 /* MapView.swift in Sources */, + 71B02D32262BE2B60037FA30 /* CodeSlider.swift in Sources */, + 71403B2C26230B850047483C /* MapDot.swift in Sources */, + 71D898A3261C1DFA00035CCD /* ChapterTwoReduce.swift in Sources */, + 7141BA15262B3B5D00B0ADBA /* CLDRAnnotation.swift in Sources */, + 71B02D36262BE2B60037FA30 /* SliderGroup.swift in Sources */, + 71D8987E261C1BF500035CCD /* EmojiView.swift in Sources */, + 7185781526298696002D2D44 /* DebugAlert.swift in Sources */, + 71F647DC261487FB0087ED8A /* Fonts.swift in Sources */, + 71B1174E2627051400BDC1A5 /* SampleText.swift in Sources */, + 715E236F2624852700CC6679 /* ZWJFomulaInputView.swift in Sources */, + 715E2385262485C800CC6679 /* EmojiBottomView.swift in Sources */, + 71F9581E26202D1D0095F391 /* CharacterBottomViewModel.swift in Sources */, + 71D8989B261C1DEF00035CCD /* ChapterTwoAction.swift in Sources */, + 71403B0A2622BEC00047483C /* App.swift in Sources */, + 5EF2F9AE2054BBF300191409 /* BaseLiveViewController.swift in Sources */, + 71CD671526275DBF001D3693 /* UnicodeScalar+Extensions.swift in Sources */, + 71F9582D2620A68C0095F391 /* CharactersBottomTitleView.swift in Sources */, + 71356F7026270ED400CDABDA /* CharacterBottomViewModel+Types.swift in Sources */, + 71AFA0C625FD1A47008E5A4C /* BlocksListView.swift in Sources */, + 71403AFE2622BE0D0047483C /* IntroductionToUnicodeView.swift in Sources */, + 71403AF52622BD820047483C /* Colors.swift in Sources */, + 718D75C5260DAB230076AAE2 /* FileHelper.swift in Sources */, + 7123C41F260046EA001ABF47 /* CharactersCollectionViewController.swift in Sources */, + 71698E69261AAFC300B4A6C7 /* BlockDescription.swift in Sources */, + 71A74066261748A900ACC64D /* View+Modifiers.swift in Sources */, + 7141BA25262B417700B0ADBA /* ChapterEmojiFont.swift in Sources */, + 71A740C626196BD700ACC64D /* String+codePoint.swift in Sources */, + 71F64746261440410087ED8A /* PlaneListItemView.swift in Sources */, + 71B02D30262BE2B60037FA30 /* EncodeWithUtfView.swift in Sources */, + 71E52A46260054870064BC6E /* PlaneIntroductionXrayView.swift in Sources */, + 71403B1D2622D4240047483C /* UICollectionView+Extensions.swift in Sources */, + 7100B9F62620C08900ACE8AB /* ViewSizeKey.swift in Sources */, + 71CF28C9261D6CE500894E8C /* TextInputOptionItem.swift in Sources */, + 713249602627EFEB00643B2B /* UnicodeGeneralCategory+Extensions.swift in Sources */, + 71698E45261AA3CD00B4A6C7 /* NSManagedObject+Extensions.swift in Sources */, + 71F64763261464640087ED8A /* View+ifTrue.swift in Sources */, + 7191D0D826010127004D7326 /* UIFont+Extensions.swift in Sources */, + 715E2361262484E400CC6679 /* EmojiLargeImputView.swift in Sources */, + 718578752629AB20002D2D44 /* EmojiInput.swift in Sources */, + 71AFA0D225FD1CAF008E5A4C /* PlaneIntroductionView.swift in Sources */, + 7193AE1A25FB235100B459A6 /* Country.swift in Sources */, + 7193AE2725FB25BB00B459A6 /* Plane.swift in Sources */, + 71A63F1A2601CE0B00519C43 /* UIViewController+Extensions.swift in Sources */, + 712CA49F25FDD12B00BEF6E4 /* DataProvider.swift in Sources */, + 71F6473F261440320087ED8A /* PlaneListView.swift in Sources */, + 7143D9302629DD60003E20D1 /* EmojiCell.swift in Sources */, + 715E2358262484BE00CC6679 /* EmojiTopView.swift in Sources */, + 71AFA0BB25FD183C008E5A4C /* BlocksListItemView.swift in Sources */, + 719BC436260D73080046C3ED /* PlaneBottomView.swift in Sources */, + 71CA9434260E409300F04F9F /* ChapterTwoState.swift in Sources */, + 712CA48E25FDC4E200BEF6E4 /* Category.swift in Sources */, + 71CF28C3261D6CD800894E8C /* TextInputOption.swift in Sources */, + 7141BA68262BDEE000B0ADBA /* String+chapterEmoji.swift in Sources */, + 71D8988A261C1C0D00035CCD /* ChapterFourState.swift in Sources */, + 71A7408F26175BEE00ACC64D /* PlaneXrayBlocksLayer.swift in Sources */, + 7123C4522600506B001ABF47 /* UIColor+Extensions.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7179EB1F262860C60054765B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7179EB48262861110054765B /* UIKitExtension.swift in Sources */, + 7179EB43262861110054765B /* AnyDifferentiable.swift in Sources */, + 7179EB4F262861110054765B /* ContentIdentifiable.swift in Sources */, + 7179EB49262861110054765B /* Changeset.swift in Sources */, + 7179EB4A262861110054765B /* ElementPath.swift in Sources */, + 7179EB46262861110054765B /* Algorithm.swift in Sources */, + 7179EB4B262861110054765B /* DifferentiableSection.swift in Sources */, + 7179EB45262861110054765B /* ContentEquatable.swift in Sources */, + 7179EB50262861110054765B /* Differentiable.swift in Sources */, + 7179EB47262861110054765B /* StagedChangeset.swift in Sources */, + 7179EB44262861110054765B /* ArraySection.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7179EB742628620B0054765B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7179EBC7262862280054765B /* ASHostingController.swift in Sources */, + 7179EBE8262862280054765B /* ASCollectionView+Modifiers.swift in Sources */, + 7179EBCA262862280054765B /* ShrinkToFitWrapper.swift in Sources */, + 7179EBE2262862280054765B /* ViewArrayBuilder.swift in Sources */, + 7179EBE5262862280054765B /* ASSection+Initialisers.swift in Sources */, + 7179EBC6262862280054765B /* ASSection.swift in Sources */, + 7179EBDB262862280054765B /* ASCollectionViewDecoration.swift in Sources */, + 7179EBD3262862280054765B /* UIView+Convenience.swift in Sources */, + 7179EBD7262862280054765B /* ASDragDropConfig.swift in Sources */, + 7179EBD8262862280054765B /* ClosureTypeAliases.swift in Sources */, + 7179EBC2262862280054765B /* ASDiffableDataSource.swift in Sources */, + 7179EBD2262862280054765B /* UICollectionView+Convenience.swift in Sources */, + 7179EBE9262862280054765B /* EnvironmentKeys.swift in Sources */, + 7179EBC1262862280054765B /* ASDiffableDataSourceCollectionView.swift in Sources */, + 7179EBDC262862280054765B /* ASCollectionViewSupplementaryView.swift in Sources */, + 7179EBEA262862280054765B /* ASCollectionViewDelegate.swift in Sources */, + 7179EBDD262862280054765B /* ASSupplementaryCellID.swift in Sources */, + 7179EBE7262862280054765B /* AS_UICollectionView.swift in Sources */, + 7179EBDF262862280054765B /* ASCollectionViewCell.swift in Sources */, + 7179EBD0262862280054765B /* GlobalConvenienceFunctions.swift in Sources */, + 7179EBC9262862280054765B /* ASOptionalSize.swift in Sources */, + 7179EBCD262862280054765B /* ASPriorityCache.swift in Sources */, + 7179EBCB262862280054765B /* Binding+Sequence.swift in Sources */, + 7179EBD9262862280054765B /* ASDragDropConfig+Public.swift in Sources */, + 7179EBD4262862280054765B /* ASCollectionViewLayout.swift in Sources */, + 7179EBC8262862280054765B /* ASCollectionView.swift in Sources */, + 7179EBE3262862280054765B /* SectionArrayBuilder.swift in Sources */, + 7179EBE4262862280054765B /* ASCollectionView+Initialisers.swift in Sources */, + 7179EBCC262862280054765B /* RandomAccessCollection+Safe.swift in Sources */, + 7179EBC5262862280054765B /* ASSectionDataSource.swift in Sources */, + 7179EBDA262862280054765B /* ASSection+Modifiers.swift in Sources */, + 7179EBCE262862280054765B /* ASSelfSizingSettings.swift in Sources */, + 7179EBC3262862280054765B /* ASCellContext.swift in Sources */, + 7179EBCF262862280054765B /* ASIndexedDictionary.swift in Sources */, + 7179EBD1262862280054765B /* UIScrollView+Convenience.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 5EF2F9B52054BDD100191409 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 5EF2F9AA2054BBF300191409 /* BookCore */; + targetProxy = 5EF2F9B42054BDD100191409 /* PBXContainerItemProxy */; + }; + 7179EB5E262861450054765B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 7179EB22262860C60054765B /* DifferenceKit */; + targetProxy = 7179EB5D262861450054765B /* PBXContainerItemProxy */; + }; + 7179EB602628614D0054765B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 7179EB22262860C60054765B /* DifferenceKit */; + targetProxy = 7179EB5F2628614D0054765B /* PBXContainerItemProxy */; + }; + 7179EBF12628624C0054765B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 7179EB22262860C60054765B /* DifferenceKit */; + targetProxy = 7179EBF02628624C0054765B /* PBXContainerItemProxy */; + }; + 7179EBF3262862540054765B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 7179EB772628620B0054765B /* ASCollectionView */; + targetProxy = 7179EBF2262862540054765B /* PBXContainerItemProxy */; + }; + 7179EBF7262862640054765B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 7179EB772628620B0054765B /* ASCollectionView */; + targetProxy = 7179EBF6262862640054765B /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 5EA2E3C82056F35B00416A35 /* LiveViewTestAppLaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 5EA2E3C92056F35B00416A35 /* Base */, + ); + name = LiveViewTestAppLaunchScreen.storyboard; + sourceTree = ""; + }; + 5EF2F97E2054B6E400191409 /* ManifestPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 5EF2F97D2054B6E400191409 /* en */, + ); + name = ManifestPlist.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 5E086DC32051C5A7004D8D25 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5E086DC72051DD03004D8D25 /* BookOverridingBuildSettings.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = "$(PLAYGROUND_BOOK_CONTENT_VERSION)"; + GCC_DYNAMIC_NO_PIC = NO; + INSTALL_PATH = /; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(PLAYGROUND_BOOK_CONTENT_IDENTIFIER)"; + PRODUCT_NAME = "$(PLAYGROUND_BOOK_FILE_NAME)"; + SKIP_INSTALL = NO; + SUPPORTS_MACCATALYST = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.3; + }; + name = Debug; + }; + 5E086DC42051C5A7004D8D25 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5E086DC72051DD03004D8D25 /* BookOverridingBuildSettings.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = "$(PLAYGROUND_BOOK_CONTENT_VERSION)"; + INSTALL_PATH = /; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "$(PLAYGROUND_BOOK_CONTENT_IDENTIFIER)"; + PRODUCT_NAME = "$(PLAYGROUND_BOOK_FILE_NAME)"; + SKIP_INSTALL = NO; + SUPPORTS_MACCATALYST = YES; + SWIFT_VERSION = 5.3; + }; + name = Release; + }; + 5E2A7AE1204F611300F4E17A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5E0E37522065981C008FA4BE /* BuildSettings.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.3; + }; + name = Debug; + }; + 5E2A7AE2204F611300F4E17A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5E0E37522065981C008FA4BE /* BuildSettings.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.3; + }; + name = Release; + }; + 5EA2E3CC2056F35B00416A35 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_ENTITLEMENTS = LiveViewTestApp/LiveViewTestApp.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; + DEVELOPMENT_TEAM = 2Z7DE68A4G; + GCC_DYNAMIC_NO_PIC = NO; + INFOPLIST_FILE = LiveViewTestApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = "-all_load"; + PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_PREFIX).LiveViewTestApp"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTS_MACCATALYST = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SYSTEM_FRAMEWORK_SEARCH_PATHS = ( + "\"$(OTHER_FRAMEWORKS_DIR)\"", + "$(inherited)", + ); + }; + name = Debug; + }; + 5EA2E3CD2056F35B00416A35 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_ENTITLEMENTS = LiveViewTestApp/LiveViewTestApp.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; + DEVELOPMENT_TEAM = 2Z7DE68A4G; + INFOPLIST_FILE = LiveViewTestApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = "-all_load"; + PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_PREFIX).LiveViewTestApp"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTS_MACCATALYST = YES; + SYSTEM_FRAMEWORK_SEARCH_PATHS = ( + "\"$(OTHER_FRAMEWORKS_DIR)\"", + "$(inherited)", + ); + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 5EF2F9B02054BBF300191409 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5E6F7E6D2368FC9A008CC191 /* ModuleOverridingBuildSettings.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 5EF2F9B12054BBF300191409 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5E6F7E6D2368FC9A008CC191 /* ModuleOverridingBuildSettings.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 7179EB28262860C60054765B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5E6F7E6D2368FC9A008CC191 /* ModuleOverridingBuildSettings.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 7179EB29262860C60054765B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5E6F7E6D2368FC9A008CC191 /* ModuleOverridingBuildSettings.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 7179EB7D2628620B0054765B /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5E6F7E6D2368FC9A008CC191 /* ModuleOverridingBuildSettings.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 7179EB7E2628620B0054765B /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5E6F7E6D2368FC9A008CC191 /* ModuleOverridingBuildSettings.xcconfig */; + buildSettings = { + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 5E086DC52051C5A7004D8D25 /* Build configuration list for PBXNativeTarget "PlaygroundBook" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5E086DC32051C5A7004D8D25 /* Debug */, + 5E086DC42051C5A7004D8D25 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5E2A7AE0204F611300F4E17A /* Build configuration list for PBXProject "PlaygroundBook" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5E2A7AE1204F611300F4E17A /* Debug */, + 5E2A7AE2204F611300F4E17A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5EA2E3CE2056F35B00416A35 /* Build configuration list for PBXNativeTarget "LiveViewTestApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5EA2E3CC2056F35B00416A35 /* Debug */, + 5EA2E3CD2056F35B00416A35 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 5EF2F9AF2054BBF300191409 /* Build configuration list for PBXNativeTarget "BookCore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5EF2F9B02054BBF300191409 /* Debug */, + 5EF2F9B12054BBF300191409 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7179EB27262860C60054765B /* Build configuration list for PBXNativeTarget "DifferenceKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7179EB28262860C60054765B /* Debug */, + 7179EB29262860C60054765B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7179EB7C2628620B0054765B /* Build configuration list for PBXNativeTarget "ASCollectionView" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7179EB7D2628620B0054765B /* Debug */, + 7179EB7E2628620B0054765B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 5E2A7ADD204F611300F4E17A /* Project object */; +} diff --git a/PlaygroundBook.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/PlaygroundBook.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..f3a608c --- /dev/null +++ b/PlaygroundBook.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/PlaygroundBook.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/PlaygroundBook.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/PlaygroundBook.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Manifest.plist b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Manifest.plist new file mode 100644 index 0000000..a6a51d4 --- /dev/null +++ b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Manifest.plist @@ -0,0 +1,16 @@ + + + + + Name + A Tour of Unicode + Pages + + 01-Welcome.playgroundpage + 02-CodePointsBlocksAndPlanes.playgroundpage + 03-EncodingWithUtf.playgroundpage + 04-Emoji.playgroundpage + 05-Conclusion.playgroundpage + + + diff --git a/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/01-Welcome.playgroundpage/LiveView.swift b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/01-Welcome.playgroundpage/LiveView.swift new file mode 100644 index 0000000..968beab --- /dev/null +++ b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/01-Welcome.playgroundpage/LiveView.swift @@ -0,0 +1,11 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit +import BookCore +import PlaygroundSupport + +PlaygroundPage.current.liveView = instantiateLiveViewController( + withChapter: .introductionToUnicode +) diff --git a/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/01-Welcome.playgroundpage/Manifest.plist b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/01-Welcome.playgroundpage/Manifest.plist new file mode 100644 index 0000000..08f960c --- /dev/null +++ b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/01-Welcome.playgroundpage/Manifest.plist @@ -0,0 +1,14 @@ + + + + + Name + Welcome + LiveViewEdgeToEdge + + LiveViewMode + VisibleByDefault + PlaygroundLoggingMode + Off + + diff --git a/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/01-Welcome.playgroundpage/main.swift b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/01-Welcome.playgroundpage/main.swift new file mode 100644 index 0000000..0d4ff8c --- /dev/null +++ b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/01-Welcome.playgroundpage/main.swift @@ -0,0 +1,47 @@ +//#-hidden-code +// +// Copyright © 2021 Ethan Wong. Licensed under MIT. +// +//#-end-hidden-code +//#-code-completion(everything, hide) +/*: +# A Tour of Unicode + +## What Is of Unicode? + +**Unicode is the world standard for text and emoji.** It is the foundation for text in any languages in all modern software. + +Every time we type a key on our phone or desktop computer, or look at a web page or text in an application, we are interacting with Unicode. + +## What Does Unicode Include? + +Contents included in the Unicode standard are: + +* A set of code charts for visual reference +* An encoding method and a set of standard character encodings +* A set of reference data files, and a number of related items, including: + * Character properties + * Rules for normalization, decomposition, collation, rendering, and bidirectional text display order (for text mixing both right-to-left scripts, such as Arabic and Hebrew, and left-to-right scripts). + +## How Is Unicode Named? + +Joe Becker, one of the co-founders of the Unicode project, published a draft proposal for an "international/multilingual text character encoding system" in August 1988. He called it "Unicode" and explained that: *The name 'Unicode' is intended to suggest a **unique, unified, universal** encoding*. + +## How Unicode Evolves? + +Unicode Consortium is the nonprofit organization that coordinates Unicode's development. + +The Unicode Standard Version 1.0 was first published in 1991), and has published new versions on a regular basis since then. + +The latest and the single valid version is version 13.0. It was released in March 2020, and has been implemented in all morden operating systems. + +## What Will We Learn in This Book? + +* [Chapter One](Code%20Points%2C%20Blocks%20and%20Planes) will walk you through basic concepts such as **planes, blocks and code points*, with an interactive character table. + +* [Chapter Two](Encoding%20Text%20with%20UTF) will compare different popular encoding methods for Unicode, which are known as **Unicode Transform Format**, and provides interactive demonstrate that you can explore. + +* [Chapter Three](Emoji) will unveil the mystery of **Emoji**. Emoji is far more complex in its design than we might think, with great respect for neutrality regarding race, ethnicity, and gender. + +[Let's get started! 🏃‍♂️](@next) +*/ diff --git a/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/02-CodePointsBlocksAndPlanes.playgroundpage/LiveView.swift b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/02-CodePointsBlocksAndPlanes.playgroundpage/LiveView.swift new file mode 100644 index 0000000..8c634b6 --- /dev/null +++ b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/02-CodePointsBlocksAndPlanes.playgroundpage/LiveView.swift @@ -0,0 +1,11 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit +import BookCore +import PlaygroundSupport + +PlaygroundPage.current.liveView = instantiateLiveViewController( + withChapter: .codePointsBlocksAndPlanes +) diff --git a/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/02-CodePointsBlocksAndPlanes.playgroundpage/Manifest.plist b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/02-CodePointsBlocksAndPlanes.playgroundpage/Manifest.plist new file mode 100644 index 0000000..f0bdbe7 --- /dev/null +++ b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/02-CodePointsBlocksAndPlanes.playgroundpage/Manifest.plist @@ -0,0 +1,14 @@ + + + + + Name + Code Points, Blocks and Planes + LiveViewEdgeToEdge + + LiveViewMode + VisibleByDefault + PlaygroundLoggingMode + Off + + diff --git a/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/02-CodePointsBlocksAndPlanes.playgroundpage/main.swift b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/02-CodePointsBlocksAndPlanes.playgroundpage/main.swift new file mode 100644 index 0000000..f0bdd98 --- /dev/null +++ b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/02-CodePointsBlocksAndPlanes.playgroundpage/main.swift @@ -0,0 +1,40 @@ +//#-hidden-code +// +// Copyright © 2021 Ethan Wong. Licensed under MIT. +// +//#-end-hidden-code +//#-hidden-code +import PlaygroundSupport + +PlaygroundPage.current.needsIndefiniteExecution = true +//#-end-hidden-code +//#-code-completion(everything, hide) +/*: +# Code Points, Blocks and Planes + +## How Unicode Organizes Characters + +Unicode organizes characters into 17 planes numbered 0 to 16. A **plane** is a continuous group of 65536 code points. + +Plane 0 (Basic Multilingual Plane, BMP), contains most commonly used characters. Planes 1 through 16 are called "supplementary planes". As of Unicode version 13.0, seven of these planes have assigned code points, and five are named. + +Planes are further subdivided into **blocks**, which, unlike planes, do not have a fixed size. Unicode 13.0 defines 308 blocks, covering 26% of the possible code point space. + +**Code points** are numbers that make up the codespace, usually represented with hexadecimal starting with "U+". Unicode can address ranges from `U+000000` to `U+10FFFF`, *with highest two digits identical to plane number*. Every assigned code point has various properties as well as an uppercased permanent name. For example, 😂 (U+1F602, FACE WITH TEARS OF JOY EMOJI) lies in `Plane 1`, block `Emoticons (Emoji)`. + +**Characters** may be composed _one or more_ code points, for example, the letter é (U+00E9, LATIN SMALL LETTER E WITH ACUTE), can also be represented as a pair of e (U+0065, LATIN SMALL LETTER E), followed by ́ (U+0301, COMBINING ACUTE ACCENT). Unicode defines several related properties and rules for text-rendering systems to render properly. + +![1-1: Character Combining](1-1-character-combining.png) + +- Experiment: + Tap "Run My Code" to bring the Unicode table full screen. And explore how Unicode or organizes characters. + +![1-2: User Interface](1-2-user-interface.png) + +![1-3: Plane View](1-3-plane-view.png) + +- Note: + The "Last Resort" font is used when a character is not being capable to rendered by available fonts. + +![1-4: Last Resort Font](1-4-last-resort.png) +*/ diff --git a/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/03-EncodingWithUtf.playgroundpage/LiveView.swift b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/03-EncodingWithUtf.playgroundpage/LiveView.swift new file mode 100644 index 0000000..6beb702 --- /dev/null +++ b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/03-EncodingWithUtf.playgroundpage/LiveView.swift @@ -0,0 +1,11 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit +import BookCore +import PlaygroundSupport + +PlaygroundPage.current.liveView = instantiateLiveViewController( + withChapter: .encodingWithUtf +) diff --git a/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/03-EncodingWithUtf.playgroundpage/Manifest.plist b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/03-EncodingWithUtf.playgroundpage/Manifest.plist new file mode 100644 index 0000000..ba65806 --- /dev/null +++ b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/03-EncodingWithUtf.playgroundpage/Manifest.plist @@ -0,0 +1,14 @@ + + + + + Name + Encoding Text with UTF + LiveViewEdgeToEdge + + LiveViewMode + VisibleByDefault + PlaygroundLoggingMode + Off + + diff --git a/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/03-EncodingWithUtf.playgroundpage/main.swift b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/03-EncodingWithUtf.playgroundpage/main.swift new file mode 100644 index 0000000..eb2871a --- /dev/null +++ b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/03-EncodingWithUtf.playgroundpage/main.swift @@ -0,0 +1,55 @@ +//#-hidden-code +// +// Copyright © 2021 Ethan Wong. Licensed under MIT. +// +//#-end-hidden-code +//#-hidden-code +import PlaygroundSupport + +PlaygroundPage.current.needsIndefiniteExecution = true +//#-end-hidden-code +//#-code-completion(everything, hide) +/*: +# Encoding Text with UTF + +## Unicode Transformation Format + +**Unicode Transformation Formats, UTF** are the mostly used strategy to encode Unicode documents. + +### UTF-32 + +**UTF-32** encodes Unicode code points with fixed 32 bits (four bytes) code units, although actually only 21 bits is needed to represent a code point. It's the simplest way to encode Unicode text, but space-inefficient. + +![2-1: UTF-32](2-1-utf-32.png) + +### UTF-16 + +**UTF-16** encodes Unicode code points into either one or two 16 bit (two bytes) code units. Code points in plane 0 (`U+0000` to `U+FFFF`) are encoded directly, Code points in supplementary planes are encoded using a method illustrated below: + +![2-2: UTF-16](2-2-utf-16.png) + +UTF-16 a bit more complex, but more space-efficient, since characters in the plane 0 only need 2 bytes to encode. However, UTF-16 are not able to represent code points from `U+D800` to `U+DBFF` and `U+DC00` to `U+DFFF`, which are called **high and low surrogates**. + +### UTF-8 + +UTF-8 encodes Unicode code points into either one or two or three 8 bit (single byte) code units. + +![2-3: UTF-16](2-3-utf-8.png) + +As of 2021, more than 97% of web pages in the Internet are encoded using UTF-8. UTF-8 is the mostly used encoding method, due to following advantages: + +* It is backwards compatible with ASCII encoding. + +* It is read and written by bytes, thus has no byte-ordering issue. + +* It can encode all code points in a relatively space-efficient way, especially for low code points. + +However, it also has some drawbacks: + +* It's relatively harder for text rendering systems parse and validate. + +* For CJK (Chinese, Japanese and Korean) characters, it consumes more space compared with UTF-16. + +- Experiment: + Tap "Run My Code" to bring the playground full screen. Tap or enter some text, and explore how characters are encoded in different formats. +*/ diff --git a/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/04-Emoji.playgroundpage/LiveView.swift b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/04-Emoji.playgroundpage/LiveView.swift new file mode 100644 index 0000000..a1bab16 --- /dev/null +++ b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/04-Emoji.playgroundpage/LiveView.swift @@ -0,0 +1,11 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit +import BookCore +import PlaygroundSupport + +PlaygroundPage.current.liveView = instantiateLiveViewController( + withChapter: .emoji +) diff --git a/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/04-Emoji.playgroundpage/Manifest.plist b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/04-Emoji.playgroundpage/Manifest.plist new file mode 100644 index 0000000..0e2d249 --- /dev/null +++ b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/04-Emoji.playgroundpage/Manifest.plist @@ -0,0 +1,14 @@ + + + + + Name + Emoji + LiveViewEdgeToEdge + + LiveViewMode + VisibleByDefault + PlaygroundLoggingMode + Off + + diff --git a/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/04-Emoji.playgroundpage/main.swift b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/04-Emoji.playgroundpage/main.swift new file mode 100644 index 0000000..07028d9 --- /dev/null +++ b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/04-Emoji.playgroundpage/main.swift @@ -0,0 +1,45 @@ +//#-hidden-code +// +// Copyright © 2021 Ethan Wong. Licensed under MIT. +// +//#-end-hidden-code +//#-hidden-code +import PlaygroundSupport + +PlaygroundPage.current.needsIndefiniteExecution = true +//#-end-hidden-code +//#-code-completion(everything, hide) +/*: +# Emoji + +Emoji, originated from Japanese "絵文字" are pictorial symbols used like text. Emoji are extremely popular worldwide on smartphones and in chat. + +The latest version of Unicode represents more than 1000 emoji characters spread across 24 blocks. Emoji are designed in a neutral way that is gender and race ignostic, with additional strategies used to increase their diversity. + +## Joining + +Several Emoji can be joined into a single one with **U+200D (Zero Width Joiner, ZWJ)** character. Emoji are usually sent as messages, this mechanism helps to convey correct meanings even if the receiving end has no support for specific character. + +![3-1: Joining](3-1-joining.png) + +## Modifiers + +Modifiers, especially, skin tone modifiers are used directly after emoji symbols to modify is representation. + +![3-2: Modifiers](3-2-modifiers.png) + +## Flags and Key Caps + +Emoji flags and key caps can are composed in a way that is compatible. + +![3-3: Flags And Key Caps](3-3-flags-and-keycaps.png) + +## Presentations + +Emoji flags and key caps can are composed in a way that is compatible. + +![3-4: Variations](3-4-variations.png) + +- Experiment: +Tap "Run My Code" to bring the playground full screen. Using provided tabs to explore with emoji joining, modifiers, flags, key caps, and presentation selectors respectively. +*/ diff --git a/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/05-Conclusion.playgroundpage/LiveView.swift b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/05-Conclusion.playgroundpage/LiveView.swift new file mode 100644 index 0000000..a1bab16 --- /dev/null +++ b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/05-Conclusion.playgroundpage/LiveView.swift @@ -0,0 +1,11 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit +import BookCore +import PlaygroundSupport + +PlaygroundPage.current.liveView = instantiateLiveViewController( + withChapter: .emoji +) diff --git a/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/05-Conclusion.playgroundpage/Manifest.plist b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/05-Conclusion.playgroundpage/Manifest.plist new file mode 100644 index 0000000..86d62d5 --- /dev/null +++ b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/05-Conclusion.playgroundpage/Manifest.plist @@ -0,0 +1,14 @@ + + + + + Name + Comclusion + LiveViewEdgeToEdge + + LiveViewMode + HiddenByDefault + PlaygroundLoggingMode + Off + + diff --git a/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/05-Conclusion.playgroundpage/main.swift b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/05-Conclusion.playgroundpage/main.swift new file mode 100644 index 0000000..3513297 --- /dev/null +++ b/PlaygroundBook/Chapters/ATourOfUnicode.playgroundchapter/Pages/05-Conclusion.playgroundpage/main.swift @@ -0,0 +1,19 @@ +//#-hidden-code +// +// Copyright © 2021 Ethan Wong. Licensed under MIT. +// +//#-end-hidden-code +//#-code-completion(everything, hide) +/*: +# Conclusion + +In this book, we've explored some basic and fun aspects of Unicode. This book only scratched the surface of Unicode. There are far more knowledge and advanced topics regarding Unicode, such as comparison, sorting, collation, rendering and much more. + +**CJK (Chinese, Japanese and Korean)** character encoding and representations, which we have not covered in this book, is extremely complex. Unicode's specific solution to CJK is called **Han unification** (Unihan), which may worth another whole book to illustrate. + +Unicode is a complex but yet a graceful design in the field of software engineering, that, most of us may not realize. It lays the foundation for text representation and rendering, which is the fundamental of digital experience and linguistic researches. + +## License + +This playground book is licensed under MIT License. For detailed licence and attributions, tap "..." at the top right corner and then "License Agreements". +*/ diff --git a/PlaygroundBook/Manifest.plist b/PlaygroundBook/Manifest.plist new file mode 100644 index 0000000..9a0c94c --- /dev/null +++ b/PlaygroundBook/Manifest.plist @@ -0,0 +1,30 @@ + + + + + Version + 7.1 + ContentIdentifier + $(PLAYGROUND_BOOK_CONTENT_IDENTIFIER) + ContentVersion + $(PLAYGROUND_BOOK_CONTENT_VERSION) + DevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + DeploymentTarget + $(PLAYGROUND_BOOK_DEPLOYMENT_TARGET_FOR_MANIFEST) + SwiftVersion + $(SWIFT_VERSION) + Name + PlaygroundBookName + Chapters + + ATourOfUnicode.playgroundchapter + + UserAutoImportedAuxiliaryModules + + UserModuleMode + Disabled + SupportsDarkMode + + + diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASCellContext.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASCellContext.swift new file mode 100644 index 0000000..c28f686 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASCellContext.swift @@ -0,0 +1,13 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation + +/// The context passed to your dynamic section initialiser. Use this to change your view content depending on the context (eg. selected) +public struct ASCellContext +{ + public var isHighlighted: Bool + public var isSelected: Bool + public var index: Int + public var isFirstInSection: Bool + public var isLastInSection: Bool +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASCollectionView+Initialisers.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASCollectionView+Initialisers.swift new file mode 100644 index 0000000..207cbef --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASCollectionView+Initialisers.swift @@ -0,0 +1,88 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI + +// MARK: Init for multi-section CVs + +public extension ASCollectionView +{ + /** + Initializes a collection view with the given sections + + - Parameters: + - sections: An array of sections (ASCollectionViewSection) + */ + init(editMode: Bool = false, sections: [Section]) + { + self.editMode = editMode + self.sections = sections + } + + /** + Initializes a collection view with the given sections + + - Parameters: + - sectionBuilder: A closure containing multiple sections (ASCollectionViewSection) + */ + init(editMode: Bool = false, @SectionArrayBuilder sectionBuilder: () -> [Section]) + { + sections = sectionBuilder() + } +} + +// MARK: Init for single-section CV + +public extension ASCollectionView where SectionID == Int +{ + /** + Initializes a collection view with a single section. + + - Parameters: + - section: A single section (ASCollectionViewSection) + */ + init(editMode: Bool = false, section: Section) + { + self.editMode = editMode + sections = [section] + } + + /** + Initializes a collection view with a single section of static content + */ + init(editMode: Bool = false, @ViewArrayBuilder staticContent: () -> ViewArrayBuilder.Wrapper) + { + self.init(editMode: editMode, sections: [ASCollectionViewSection(id: 0, content: staticContent)]) + } + + /** + Initializes a collection view with a single section. + */ + init( + editMode: Bool = false, + data: DataCollection, + dataID dataIDKeyPath: KeyPath, + @ViewBuilder contentBuilder: @escaping ((DataCollection.Element, ASCellContext) -> Content)) + where DataCollection.Index == Int + { + self.editMode = editMode + let section = ASCollectionViewSection( + id: 0, + data: data, + dataID: dataIDKeyPath, + contentBuilder: contentBuilder) + sections = [section] + } + + /** + Initializes a collection view with a single section with identifiable data + */ + init( + editMode: Bool = false, + data: DataCollection, + @ViewBuilder contentBuilder: @escaping ((DataCollection.Element, ASCellContext) -> Content)) + where DataCollection.Index == Int, DataCollection.Element: Identifiable + { + self.init(editMode: editMode, data: data, dataID: \.id, contentBuilder: contentBuilder) + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASCollectionView+Modifiers.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASCollectionView+Modifiers.swift new file mode 100644 index 0000000..827bad4 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASCollectionView+Modifiers.swift @@ -0,0 +1,213 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI + +// MARK: Modifer: Custom Delegate + +public extension ASCollectionView +{ + /// Use this modifier to assign a custom delegate type (subclass of ASCollectionViewDelegate). This allows support for old UICollectionViewLayouts that require a delegate. + func customDelegate(_ delegateInitialiser: @escaping (() -> ASCollectionViewDelegate)) -> Self + { + var cv = self + cv.delegateInitialiser = delegateInitialiser + return cv + } +} + +// MARK: Modifer: Layout Invalidation + +public extension ASCollectionView +{ + /// For use in cases where you would like to change layout settings in response to a change in variables referenced by your layout closure. + /// Note: this ensures the layout is invalidated + /// - For UICollectionViewCompositionalLayout this means that your SectionLayout closure will be called again + /// - closures capture value types when created, therefore you must refer to a reference type in your layout closure if you want it to update. + func shouldInvalidateLayoutOnStateChange(_ shouldInvalidate: Bool, animated: Bool = true) -> Self + { + var this = self + this.shouldInvalidateLayoutOnStateChange = shouldInvalidate + this.shouldAnimateInvalidatedLayoutOnStateChange = animated + return this + } + + /// For use in cases where you would like to recreate the layout object in response to a change in state. Eg. for changing layout types completely + /// If not changing the type of layout (eg. to a different class) t is preferable to invalidate the layout and update variables in the `configureCustomLayout` closure + func shouldRecreateLayoutOnStateChange(_ shouldRecreate: Bool, animated: Bool = true) -> Self + { + var this = self + this.shouldRecreateLayoutOnStateChange = shouldRecreate + this.shouldAnimateRecreatedLayoutOnStateChange = animated + return this + } +} + +// MARK: Modifer: Other Modifiers + +public extension ASCollectionView +{ + /// Set a closure that is called whenever the collectionView is scrolled + func onScroll(_ onScroll: @escaping OnScrollCallback) -> Self + { + var this = self + this.onScrollCallback = onScroll + return this + } + + /// Set a closure that is called whenever the collectionView is scrolled to a boundary. eg. the bottom. + /// This is useful to enable loading more data when scrolling to bottom + func onReachedBoundary(_ onReachedBoundary: @escaping OnReachedBoundaryCallback) -> Self + { + var this = self + this.onReachedBoundaryCallback = onReachedBoundary + return this + } + + /// Sets the collection view's background color + func backgroundColor(_ color: UIColor?) -> Self + { + var this = self + this.backgroundColor = color + return this + } + + /// Set whether to show scroll indicators + func scrollIndicatorsEnabled(horizontal: Bool = true, vertical: Bool = true) -> Self + { + var this = self + this.horizontalScrollIndicatorEnabled = horizontal + this.verticalScrollIndicatorEnabled = vertical + return this + } + + /// Set the content insets + func contentInsets(_ insets: UIEdgeInsets) -> Self + { + var this = self + this.contentInsets = insets + return this + } + + /// Set a closure that is called when the collectionView is pulled to refresh + func onPullToRefresh(_ callback: ((_ endRefreshing: @escaping (() -> Void)) -> Void)?) -> Self + { + var this = self + this.onPullToRefresh = callback + return this + } + + /// Set whether the ASCollectionView should always allow bounce vertically + func alwaysBounceVertical(_ alwaysBounce: Bool = true) -> Self + { + var this = self + this.alwaysBounceVertical = alwaysBounce + return this + } + + /// Set whether the ASCollectionView should always allow bounce horizontally + func alwaysBounceHorizontal(_ alwaysBounce: Bool = true) -> Self + { + var this = self + this.alwaysBounceHorizontal = alwaysBounce + return this + } + + /// Set a binding that will scroll the ASCollectionView when set. It will always return nil once the scroll is applied (use onScroll to read scroll position) + func scrollPositionSetter(_ binding: Binding) -> Self + { + var this = self + _ = binding.wrappedValue // Touch the binding so that SwiftUI will notify us of future updates + this.scrollPositionSetter = binding + return this + } + + /// Set whether the ASCollectionView should animate on data refresh + func animateOnDataRefresh(_ animate: Bool = true) -> Self + { + var this = self + this.animateOnDataRefresh = animate + return this + } + + /// Set whether the ASCollectionView should attempt to maintain scroll position on orientation change, default is true + func shouldAttemptToMaintainScrollPositionOnOrientationChange(maintainPosition: Bool) -> Self + { + var this = self + this.maintainScrollPositionOnOrientationChange = maintainPosition + return this + } + + /// Set whether the ASCollectionView should automatically scroll an active textview/input to avoid the system keyboard. Default is true + func shouldScrollToAvoidKeyboard(_ avoidKeyboard: Bool = true) -> Self + { + var this = self + this.dodgeKeyboard = avoidKeyboard + return this + } +} + +// MARK: PUBLIC layout modifier functions + +public extension ASCollectionView +{ + func layout(_ layout: Layout) -> Self + { + var this = self + this.layout = layout + return this + } + + func layout( + scrollDirection: UICollectionView.ScrollDirection = .vertical, + interSectionSpacing: CGFloat = 10, + layoutPerSection: @escaping CompositionalLayout) -> Self + { + var this = self + this.layout = Layout( + scrollDirection: scrollDirection, + interSectionSpacing: interSectionSpacing, + layoutPerSection: layoutPerSection) + return this + } + + func layout( + scrollDirection: UICollectionView.ScrollDirection = .vertical, + interSectionSpacing: CGFloat = 10, + layout: @escaping CompositionalLayoutIgnoringSections) -> Self + { + var this = self + this.layout = Layout( + scrollDirection: scrollDirection, + interSectionSpacing: interSectionSpacing, + layout: layout) + return this + } + + func layout(customLayout: @escaping (() -> UICollectionViewLayout)) -> Self + { + var this = self + this.layout = Layout(customLayout: customLayout) + return this + } + + func layout(createCustomLayout: @escaping (() -> LayoutClass), configureCustomLayout: @escaping ((LayoutClass) -> Void)) -> Self + { + var this = self + this.layout = Layout(createCustomLayout: createCustomLayout, configureCustomLayout: configureCustomLayout) + return this + } +} + +public extension ASCollectionView +{ + func shrinkToContentSize(isEnabled: Bool = true, dimension: ShrinkDimension) -> some View + { + SelfSizingWrapper(content: self, shrinkDirection: dimension, isEnabled: isEnabled) + } + + func fitContentSize(isEnabled: Bool = true, dimension: ShrinkDimension) -> some View + { + SelfSizingWrapper(content: self, shrinkDirection: dimension, isEnabled: isEnabled, expandToFitMode: true) + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASDragDropConfig+Public.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASDragDropConfig+Public.swift new file mode 100644 index 0000000..60385ab --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASDragDropConfig+Public.swift @@ -0,0 +1,81 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI +import UIKit + +public extension ASDragDropConfig +{ + /// This provides automatic support for drag/drop/reorder of items in the section + /// It automatically applies changes using the data binding + /// Use the modifiers to add extra checks (eg .canDragItem, .canMoveItem, .dragItemProvider, .dropItemProvider) + init(dataBinding: Binding<[Data]>, dragEnabled: Bool = true, dropEnabled: Bool = true, reorderingEnabled: Bool = true) + { + self.dataBinding = dataBinding + self.dragEnabled = dragEnabled + self.dropEnabled = dropEnabled + self.reorderingEnabled = reorderingEnabled + } + + /// This allows you to manually implement drag/drop/reordering support + /// In the onDelete/onInsert/onMove closures return true if you apply the suggested delete/insert/move, or false if it shouldn't be applied... so that ASCollectionView can correctly animate. + /// Use the modifiers to add extra checks (eg .canDragItem, .canMoveItem, .dragItemProvider, .dropItemProvider) + init(dragEnabled: Bool = true, + dropEnabled: Bool = true, + reorderingEnabled: Bool = true, + onDeleteOrRemoveItems: ((_ indexSet: IndexSet) -> Bool)? = nil, + onInsertItems: ((_ index: Int, _ items: [Data]) -> Bool)? = nil, + onMoveItem: ((Int, Int) -> Bool)? = nil) + { + dataBinding = nil + self.onDeleteOrRemoveItems = onDeleteOrRemoveItems + self.onInsertItems = onInsertItems + self.onMoveItem = onMoveItem + self.dragEnabled = dragEnabled + self.dropEnabled = dropEnabled + self.reorderingEnabled = reorderingEnabled + } + + static var disabled: ASDragDropConfig + { + ASDragDropConfig() + } + + /// Called to check whether an item can be dragged + func canDragItem(_ closure: @escaping ((IndexPath) -> Bool)) -> Self + { + var this = self + this.canDragItem = closure + return this + } + + /// Called to check whether a move should be allowed + func canMoveItem(_ closure: @escaping ((IndexPath, IndexPath) -> Bool)) -> Self + { + var this = self + this.canMoveItem = closure + return this + } + + /// An optional closure that you can use to decide what to do with a dropped item. + /// Return nil if you want to ignore the drop. + /// Return an item (of the same type as your section data) if you want to insert a row. + /// `sourceItem`: If the drop originated from a cell with the same data source, this will provide the original item that has been dragged + /// `dragItem`: This is the further information provided by UIKit. For example, if a drag came from another app, you could deal with that using this. + func dropItemProvider(_ provider: @escaping ((Data?, UIDragItem) -> Data?)) -> Self + { + var this = self + this.dropEnabled = true + this.dropItemProvider = provider + return this + } + + /// An optional closure that you can use to provide extra info (eg. for dragging outside of your app) + func dragItemProvider(_ provider: @escaping ((_ item: Data) -> NSItemProvider?)) -> Self + { + var this = self + this.dragEnabled = true + this.dragItemProvider = provider + return this + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASSection+Initialisers.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASSection+Initialisers.swift new file mode 100644 index 0000000..ed48dab --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASSection+Initialisers.swift @@ -0,0 +1,178 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI + +// MARK: DYNAMIC CONTENT SECTION + +public extension ASSection +{ + /** + Initializes a section with data + + - Parameters: + - id: The id for this section + - data: The data to display in the section. This initialiser expects data that conforms to 'Identifiable' + - dataID: The keypath to a hashable identifier of each data item + - onCellEvent: Use this to respond to cell appearance/disappearance, and preloading events. + - onDragDropEvent: Define this closure to enable drag/drop and respond to events (default is nil: drag/drop disabled) + - contentBuilder: A closure returning a SwiftUI view for the given data item + */ + init( + id: SectionID, + data: DataCollection, + dataID dataIDKeyPath: KeyPath, + container: @escaping ((Content, ASCellContext) -> Container), + selectionMode: ASSectionSelectionMode = .none, + shouldAllowHighlight: ((_ index: Int) -> Bool)? = nil, + shouldAllowSelection: ((_ index: Int) -> Bool)? = nil, + shouldAllowDeselection: ((_ index: Int) -> Bool)? = nil, + onCellEvent: OnCellEvent? = nil, + dragDropConfig: ASDragDropConfig = .disabled, + shouldAllowSwipeToDelete: ShouldAllowSwipeToDelete? = nil, + onSwipeToDelete: OnSwipeToDelete? = nil, + contextMenuProvider: ContextMenuProvider? = nil, + @ViewBuilder contentBuilder: @escaping ((DataCollection.Element, ASCellContext) -> Content)) + where DataCollection.Index == Int + { + self.id = id + dataSource = ASSectionDataSource( + data: data, + dataIDKeyPath: dataIDKeyPath, + container: container, + content: contentBuilder, + selectionMode: selectionMode, + shouldAllowHighlight: shouldAllowHighlight, + shouldAllowSelection: shouldAllowSelection, + shouldAllowDeselection: shouldAllowDeselection, + onCellEvent: onCellEvent, + dragDropConfig: dragDropConfig, + shouldAllowSwipeToDelete: shouldAllowSwipeToDelete, + onSwipeToDelete: onSwipeToDelete, + contextMenuProvider: contextMenuProvider) + } + + init( + id: SectionID, + data: DataCollection, + dataID dataIDKeyPath: KeyPath, + selectionMode: ASSectionSelectionMode = .none, + shouldAllowHighlight: ((_ index: Int) -> Bool)? = nil, + shouldAllowSelection: ((_ index: Int) -> Bool)? = nil, + shouldAllowDeselection: ((_ index: Int) -> Bool)? = nil, + onCellEvent: OnCellEvent? = nil, + dragDropConfig: ASDragDropConfig = .disabled, + shouldAllowSwipeToDelete: ShouldAllowSwipeToDelete? = nil, + onSwipeToDelete: OnSwipeToDelete? = nil, + contextMenuProvider: ContextMenuProvider? = nil, + @ViewBuilder contentBuilder: @escaping ((DataCollection.Element, ASCellContext) -> Content)) + where DataCollection.Index == Int + { + self.init(id: id, data: data, dataID: dataIDKeyPath, container: { content, _ in content }, selectionMode: selectionMode, shouldAllowHighlight: shouldAllowHighlight, shouldAllowSelection: shouldAllowSelection, shouldAllowDeselection: shouldAllowDeselection, onCellEvent: onCellEvent, dragDropConfig: dragDropConfig, shouldAllowSwipeToDelete: shouldAllowSwipeToDelete, onSwipeToDelete: onSwipeToDelete, contextMenuProvider: contextMenuProvider, contentBuilder: contentBuilder) + } +} + +// MARK: IDENTIFIABLE DATA SECTION + +public extension ASCollectionViewSection +{ + /** + Initializes a section with identifiable data + - Parameters: + - id: The id for this section + - data: The data to display in the section. This initialiser expects data that conforms to 'Identifiable' + - onCellEvent: Use this to respond to cell appearance/disappearance, and preloading events. + - onDragDropEvent: Define this closure to enable drag/drop and respond to events (default is nil: drag/drop disabled) + - contentBuilder: A closure returning a SwiftUI view for the given data item + */ + init( + id: SectionID, + data: DataCollection, + container: @escaping ((Content, ASCellContext) -> Container), + selectionMode: ASSectionSelectionMode = .none, + shouldAllowHighlight: ((_ index: Int) -> Bool)? = nil, + shouldAllowSelection: ((_ index: Int) -> Bool)? = nil, + shouldAllowDeselection: ((_ index: Int) -> Bool)? = nil, + onCellEvent: OnCellEvent? = nil, + dragDropConfig: ASDragDropConfig = .disabled, + shouldAllowSwipeToDelete: ShouldAllowSwipeToDelete? = nil, + onSwipeToDelete: OnSwipeToDelete? = nil, + contextMenuProvider: ContextMenuProvider? = nil, + @ViewBuilder contentBuilder: @escaping ((DataCollection.Element, ASCellContext) -> Content)) + where DataCollection.Index == Int, DataCollection.Element: Identifiable + { + self.init(id: id, data: data, dataID: \.id, container: container, selectionMode: selectionMode, shouldAllowHighlight: shouldAllowHighlight, shouldAllowSelection: shouldAllowSelection, shouldAllowDeselection: shouldAllowDeselection, onCellEvent: onCellEvent, dragDropConfig: dragDropConfig, shouldAllowSwipeToDelete: shouldAllowSwipeToDelete, onSwipeToDelete: onSwipeToDelete, contextMenuProvider: contextMenuProvider, contentBuilder: contentBuilder) + } + + init( + id: SectionID, + data: DataCollection, + selectionMode: ASSectionSelectionMode = .none, + shouldAllowHighlight: ((_ index: Int) -> Bool)? = nil, + shouldAllowSelection: ((_ index: Int) -> Bool)? = nil, + shouldAllowDeselection: ((_ index: Int) -> Bool)? = nil, + onCellEvent: OnCellEvent? = nil, + dragDropConfig: ASDragDropConfig = .disabled, + shouldAllowSwipeToDelete: ShouldAllowSwipeToDelete? = nil, + onSwipeToDelete: OnSwipeToDelete? = nil, + contextMenuProvider: ContextMenuProvider? = nil, + @ViewBuilder contentBuilder: @escaping ((DataCollection.Element, ASCellContext) -> Content)) + where DataCollection.Index == Int, DataCollection.Element: Identifiable + { + self.init(id: id, data: data, container: { content, _ in content }, selectionMode: selectionMode, shouldAllowHighlight: shouldAllowHighlight, shouldAllowSelection: shouldAllowSelection, shouldAllowDeselection: shouldAllowDeselection, onCellEvent: onCellEvent, dragDropConfig: dragDropConfig, shouldAllowSwipeToDelete: shouldAllowSwipeToDelete, onSwipeToDelete: onSwipeToDelete, contextMenuProvider: contextMenuProvider, contentBuilder: contentBuilder) + } +} + +// MARK: STATIC CONTENT SECTION + +public extension ASCollectionViewSection +{ + /** + Initializes a section with static content + + - Parameters: + - id: The id for this section + - content: A closure returning a number of SwiftUI views to display in the collection view + */ + init(id: SectionID, container: @escaping ((AnyView, ASCellContext) -> Container), @ViewArrayBuilder content: () -> ViewArrayBuilder.Wrapper) + { + self.id = id + dataSource = ASSectionDataSource<[ASCollectionViewStaticContent], ASCollectionViewStaticContent.ID, AnyView, Container>( + data: content().flattened().enumerated().map + { + ASCollectionViewStaticContent(index: $0.offset, view: $0.element) + }, + dataIDKeyPath: \.id, + container: container, + content: { staticContent, _ in staticContent.view }, + dragDropConfig: .disabled) + } + + init(id: SectionID, @ViewArrayBuilder content: () -> ViewArrayBuilder.Wrapper) + { + self.init(id: id, container: { content, _ in content }, content: content) + } + + /** + Initializes a section with a single view + + - Parameters: + - id: The id for this section + - content: A single SwiftUI views to display in the collection view + */ + init(id: SectionID, container: @escaping ((AnyView, ASCellContext) -> Container), content: () -> Content) + { + self.id = id + dataSource = ASSectionDataSource<[ASCollectionViewStaticContent], ASCollectionViewStaticContent.ID, AnyView, Container>( + data: [ASCollectionViewStaticContent(index: 0, view: AnyView(content()))], + dataIDKeyPath: \.id, + container: container, + content: { staticContent, _ in staticContent.view }, + dragDropConfig: .disabled) + } + + init(id: SectionID, content: () -> Content) + { + self.init(id: id, container: { content, _ in content }, content: content) + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASSection+Modifiers.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASSection+Modifiers.swift new file mode 100644 index 0000000..037ed07 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/ASSection+Modifiers.swift @@ -0,0 +1,39 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI + +// MARK: SUPPLEMENTARY VIEWS - PUBLIC MODIFIERS + +public extension ASCollectionViewSection +{ + func sectionHeader(content: () -> Content?) -> Self + { + var section = self + section.setHeaderView(content()) + return section + } + + func sectionFooter(content: () -> Content?) -> Self + { + var section = self + section.setFooterView(content()) + return section + } + + func sectionSupplementary(ofKind kind: String, content: () -> Content?) -> Self + { + var section = self + section.setSupplementaryView(content(), ofKind: kind) + return section + } + + // MARK: Self-sizing config + + func selfSizingConfig(_ config: @escaping SelfSizingConfig) -> Self + { + var section = self + section.dataSource.setSelfSizingConfig(config: config) + return section + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Cells/ASCollectionViewCell.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Cells/ASCollectionViewCell.swift new file mode 100644 index 0000000..080f294 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Cells/ASCollectionViewCell.swift @@ -0,0 +1,107 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI +import UIKit + +class ASCollectionViewCell: UICollectionViewCell, ASDataSourceConfigurableCell +{ + var itemID: ASCollectionViewItemUniqueID? + let hostingController = ASHostingController(AnyView(EmptyView())) +// var skipNextRefresh: Bool = false + + override init(frame: CGRect) + { + super.init(frame: frame) + contentView.addSubview(hostingController.viewController.view) + hostingController.viewController.view.frame = contentView.bounds + } + + @available(*, unavailable) + required init?(coder: NSCoder) + { + fatalError("init(coder:) has not been implemented") + } + + weak var collectionViewController: AS_CollectionViewController? + { + didSet + { + if collectionViewController != oldValue + { + collectionViewController?.addChild(hostingController.viewController) + hostingController.viewController.didMove(toParent: collectionViewController) + } + } + } + + var selfSizingConfig: ASSelfSizingConfig = .init(selfSizeHorizontally: true, selfSizeVertically: true) + + override func prepareForReuse() + { + itemID = nil + isSelected = false + alpha = 1.0 +// skipNextRefresh = false + } + + override public var safeAreaInsets: UIEdgeInsets + { + .zero + } + + func setContent(itemID: ASCollectionViewItemUniqueID, content: Content) + { + self.itemID = itemID + hostingController.setView(AnyView(content.id(itemID))) + } + + override func layoutSubviews() + { + super.layoutSubviews() + + hostingController.viewController.view.frame = contentView.bounds + hostingController.viewController.view.layoutIfNeeded() + } + + override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize + { + let selfSizeHorizontal = selfSizingConfig.selfSizeHorizontally ?? (horizontalFittingPriority != .required) + let selfSizeVertical = selfSizingConfig.selfSizeVertically ?? (verticalFittingPriority != .required) + + guard selfSizeVertical || selfSizeHorizontal + else + { + return targetSize + } + + // We need to calculate a size for self-sizing. Layout the view to get swiftUI to update its state + hostingController.viewController.view.setNeedsLayout() + hostingController.viewController.view.layoutIfNeeded() + let size = hostingController.sizeThatFits( + in: targetSize, + maxSize: maxSizeForSelfSizing, + selfSizeHorizontal: selfSizeHorizontal, + selfSizeVertical: selfSizeVertical) + return size + } + + var maxSizeForSelfSizing: ASOptionalSize + { + ASOptionalSize( + width: selfSizingConfig.canExceedCollectionWidth ? nil : collectionViewController.map { $0.collectionView.contentSize.width - 0.001 }, + height: selfSizingConfig.canExceedCollectionHeight ? nil : collectionViewController.map { $0.collectionView.contentSize.height - 0.001 }) + } + + var disableSwiftUIDropInteraction: Bool + { + get { hostingController.disableSwiftUIDropInteraction } + set { hostingController.disableSwiftUIDropInteraction = newValue } + } + + var disableSwiftUIDragInteraction: Bool + { + get { hostingController.disableSwiftUIDragInteraction } + set { hostingController.disableSwiftUIDragInteraction = newValue } + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Cells/ASCollectionViewDecoration.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Cells/ASCollectionViewDecoration.swift new file mode 100644 index 0000000..e0360a5 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Cells/ASCollectionViewDecoration.swift @@ -0,0 +1,24 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI +import UIKit + +public protocol Decoration: View +{ + init() +} + +class ASCollectionViewDecoration: ASCollectionViewSupplementaryView +{ + override init(frame: CGRect) + { + super.init(frame: frame) + setContent(supplementaryID: ASSupplementaryCellID(sectionIDHash: 0, supplementaryKind: "Decoration"), content: Content()) + } + + override func prepareForReuse() + { + // Don't call super, we don't want any changes + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Cells/ASCollectionViewSupplementaryView.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Cells/ASCollectionViewSupplementaryView.swift new file mode 100644 index 0000000..60e1a6d --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Cells/ASCollectionViewSupplementaryView.swift @@ -0,0 +1,101 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI +import UIKit + +class ASCollectionViewSupplementaryView: UICollectionReusableView, ASDataSourceConfigurableSupplementary +{ + var supplementaryID: ASSupplementaryCellID? + let hostingController = ASHostingController(AnyView(EmptyView())) + + var isEmpty: Bool = true + var selfSizingConfig: ASSelfSizingConfig = .init() + + override init(frame: CGRect) + { + super.init(frame: frame) + addSubview(hostingController.viewController.view) + hostingController.viewController.view.frame = bounds + } + + @available(*, unavailable) + required init?(coder: NSCoder) + { + fatalError("init(coder:) has not been implemented") + } + + weak var collectionViewController: AS_CollectionViewController? + { + didSet + { + if collectionViewController != oldValue + { + collectionViewController?.addChild(hostingController.viewController) + hostingController.viewController.didMove(toParent: collectionViewController) + } + } + } + + override func prepareForReuse() + { + supplementaryID = nil + } + + func setContent(supplementaryID: ASSupplementaryCellID, content: Content?) + { + guard let content = content else { setAsEmpty(supplementaryID: supplementaryID); return } + self.supplementaryID = supplementaryID + isEmpty = false + hostingController.setView(AnyView(content.id(supplementaryID))) + } + + func setAsEmpty(supplementaryID: ASSupplementaryCellID?) + { + self.supplementaryID = supplementaryID + isEmpty = true + hostingController.setView(AnyView(EmptyView().id(supplementaryID))) + } + + override public var safeAreaInsets: UIEdgeInsets + { + .zero + } + + override func layoutSubviews() + { + super.layoutSubviews() + + hostingController.viewController.view.frame = bounds + } + + override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize + { + guard !isEmpty else { return CGSize(width: 1, height: 1) } + let selfSizeHorizontal = selfSizingConfig.selfSizeHorizontally ?? (horizontalFittingPriority != .required) + let selfSizeVertical = selfSizingConfig.selfSizeVertically ?? (verticalFittingPriority != .required) + + guard selfSizeVertical || selfSizeHorizontal + else + { + return targetSize + } + + // We need to calculate a size for self-sizing. Layout the view to get swiftUI to update its state + hostingController.viewController.view.setNeedsLayout() + hostingController.viewController.view.layoutIfNeeded() + let size = hostingController.sizeThatFits( + in: targetSize, + maxSize: maxSizeForSelfSizing, + selfSizeHorizontal: selfSizeHorizontal, + selfSizeVertical: selfSizeVertical) + return size + } + + var maxSizeForSelfSizing: ASOptionalSize + { + ASOptionalSize( + width: selfSizingConfig.canExceedCollectionWidth ? nil : collectionViewController.map { $0.collectionView.contentSize.width - 0.001 }, + height: selfSizingConfig.canExceedCollectionHeight ? nil : collectionViewController.map { $0.collectionView.contentSize.height - 0.001 }) + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Cells/ASSupplementaryCellID.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Cells/ASSupplementaryCellID.swift new file mode 100644 index 0000000..d4fef73 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Cells/ASSupplementaryCellID.swift @@ -0,0 +1,9 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation + +struct ASSupplementaryCellID: Hashable +{ + let sectionIDHash: Int + let supplementaryKind: String +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Config/ASDragDropConfig.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Config/ASDragDropConfig.swift new file mode 100644 index 0000000..3a30d8d --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Config/ASDragDropConfig.swift @@ -0,0 +1,39 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI +import UIKit + +public struct ASDragDropConfig +{ + // MARK: Automatic handling + + var dataBinding: Binding<[Data]>? + + // MARK: Manual handling + + var onDeleteOrRemoveItems: ((_ indexSet: IndexSet) -> Bool)? + var onInsertItems: ((_ index: Int, _ items: [Data]) -> Bool)? + var onMoveItem: ((_ from: Int, _ to: Int) -> Bool)? + + // MARK: Shared + + var dragEnabled: Bool = false + var dropEnabled: Bool = false + var reorderingEnabled: Bool = false + + /// Called to check whether an item can be dragged + var canDragItem: ((_ indexPath: IndexPath) -> Bool)? + + /// Called to check whether an item can be moved to the specified indexPath + var canMoveItem: ((_ sourceIndexPath: IndexPath, _ destinationIndexPath: IndexPath) -> Bool)? + + var dragItemProvider: ((_ item: Data) -> NSItemProvider?)? + + var dropItemProvider: ((_ sourceItem: Data?, _ dragItem: UIDragItem) -> Data?)? + + init() + { + // Used to provide `disabled` mode + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Config/ClosureTypeAliases.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Config/ClosureTypeAliases.swift new file mode 100644 index 0000000..75f61a5 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Config/ClosureTypeAliases.swift @@ -0,0 +1,29 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import UIKit + +public enum CellEvent +{ + /// Respond by starting necessary prefetch operations for this data to be displayed soon (eg. download images) + case prefetchForData(data: [Data]) + + /// Called when its no longer necessary to prefetch this data + case cancelPrefetchForData(data: [Data]) + + /// Called when an item is appearing on the screen + case onAppear(item: Data) + + /// Called when an item is disappearing from the screen + case onDisappear(item: Data) +} + +public typealias OnCellEvent = ((_ event: CellEvent) -> Void) + +public typealias ShouldAllowSwipeToDelete = ((_ index: Int) -> Bool) + +public typealias OnSwipeToDelete = ((_ index: Int, _ item: Data) -> Bool) + +public typealias ContextMenuProvider = ((_ index: Int, _ item: Data) -> UIContextMenuConfiguration?) + +public typealias SelfSizingConfig = ((_ context: ASSelfSizingContext) -> ASSelfSizingConfig?) diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Datasource/ASDiffableDataSource.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Datasource/ASDiffableDataSource.swift new file mode 100644 index 0000000..adbab52 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Datasource/ASDiffableDataSource.swift @@ -0,0 +1,131 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import DifferenceKit +import Foundation +import UIKit + +class ASDiffableDataSource: NSObject +{ + public internal(set) var currentSnapshot = ASDiffableDataSourceSnapshot() + + func identifier(at indexPath: IndexPath) -> ASCollectionViewItemUniqueID + { + currentSnapshot.sections[indexPath.section].elements[indexPath.item].differenceIdentifier + } +} + +struct ASDiffableDataSourceSnapshot +{ + private(set) var sections: [Section] + private(set) var itemPositionMap: [ASCollectionViewItemUniqueID: ItemPosition] = [:] + + init(sections: [Section] = []) + { + self.sections = sections + sections.enumerated().forEach + { sectionIndex, section in + section.elements.enumerated().forEach { itemIndex, item in itemPositionMap[item.differenceIdentifier] = ItemPosition(itemIndex: itemIndex, sectionIndex: sectionIndex) } + } + } + + mutating func appendSection(sectionID: SectionID, items: [ASCollectionViewItemUniqueID]) + { + let newSection = Section(id: sectionID, elements: items) + sections.append(newSection) + newSection.elements.enumerated().forEach { itemIndex, item in itemPositionMap[item.differenceIdentifier] = ItemPosition(itemIndex: itemIndex, sectionIndex: sections.endIndex - 1) } + } + + mutating func removeItems(fromSectionIndex sectionIndex: Int, atOffsets offsets: IndexSet) + { + guard sections.containsIndex(sectionIndex) else { return } + sections[sectionIndex].elements.remove(atOffsets: offsets) + } + + mutating func insertItems(_ items: [ASCollectionViewItemUniqueID], atSectionIndex sectionIndex: Int, atOffset offset: Int) + { + guard sections.containsIndex(sectionIndex) else { return } + sections[sectionIndex].elements.insert(contentsOf: items.map { Item(id: $0) }, at: offset) + } + + mutating func reloadItems(items: Set) + { + items.forEach + { item in + guard let position = itemPositionMap[item] else { return } + sections[position.sectionIndex].elements[position.itemIndex].shouldReload = true + } + } + + mutating func moveItem(fromIndexPath: IndexPath, toIndexPath: IndexPath) + { + guard sections.containsIndex(fromIndexPath.section), sections.containsIndex(toIndexPath.section) else { return } + if fromIndexPath.section == toIndexPath.section + { + let item = sections[fromIndexPath.section].elements.remove(at: fromIndexPath.item) + sections[toIndexPath.section].elements.insert(item, at: toIndexPath.item) + } + else + { + let item = sections[fromIndexPath.section].elements.remove(at: fromIndexPath.item) + sections[toIndexPath.section].elements.insert(item, at: toIndexPath.item) + } + } + + struct ItemPosition + { + var itemIndex: Int + var sectionIndex: Int + } + + struct Section + { + var id: SectionID + var elements: [Item] + + var differenceIdentifier: SectionID + { + id + } + + func isContentEqual(to source: ASDiffableDataSourceSnapshot.Section) -> Bool + { + source.differenceIdentifier == differenceIdentifier + } + } + + struct Item: Differentiable + { + var differenceIdentifier: ASCollectionViewItemUniqueID + var shouldReload: Bool + + init(id: ASCollectionViewItemUniqueID, shouldReload: Bool = false) + { + differenceIdentifier = id + self.shouldReload = shouldReload + } + + func isContentEqual(to source: Item) -> Bool + { + !shouldReload && differenceIdentifier == source.differenceIdentifier + } + } +} + +extension ASDiffableDataSourceSnapshot.Section +{ + init(id: SectionID, elements: [ASCollectionViewItemUniqueID], shouldReloadElements: Bool = false) + { + self.id = id + self.elements = elements.map { ASDiffableDataSourceSnapshot.Item(id: $0, shouldReload: shouldReloadElements) } + } +} + +extension ASDiffableDataSourceSnapshot.Section: DifferentiableSection +{ + init(source: Self, elements: C) where C.Element == ASDiffableDataSourceSnapshot.Item + { + self.init(id: source.differenceIdentifier, elements: Array(elements)) + } +} + +extension ASCollectionViewItemUniqueID: Differentiable {} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Datasource/ASDiffableDataSourceCollectionView.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Datasource/ASDiffableDataSourceCollectionView.swift new file mode 100644 index 0000000..e0ee68c --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Datasource/ASDiffableDataSourceCollectionView.swift @@ -0,0 +1,92 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import DifferenceKit +import SwiftUI +import UIKit + +class ASDiffableDataSourceCollectionView: ASDiffableDataSource, UICollectionViewDataSource +{ + /// The type of closure providing the cell. + public typealias Snapshot = ASDiffableDataSourceSnapshot + public typealias CellProvider = (UICollectionView, IndexPath, ASCollectionViewItemUniqueID) -> ASCollectionViewCell? + public typealias SupplementaryProvider = (UICollectionView, String, IndexPath) -> ASCollectionViewSupplementaryView? + + private weak var collectionView: UICollectionView? + var cellProvider: CellProvider + var supplementaryViewProvider: SupplementaryProvider? + + public init(collectionView: UICollectionView, cellProvider: @escaping CellProvider) + { + self.collectionView = collectionView + self.cellProvider = cellProvider + super.init() + + collectionView.dataSource = self + collectionView.register(ASCollectionViewSupplementaryView.self, forSupplementaryViewOfKind: supplementaryEmptyKind, withReuseIdentifier: supplementaryEmptyReuseID) + } + + private var firstLoad: Bool = true + + func applySnapshot(_ newSnapshot: Snapshot, animated: Bool = true, completion: (() -> Void)? = nil) + { + let changeset = StagedChangeset(source: currentSnapshot.sections, target: newSnapshot.sections) + + guard let collectionView = collectionView else { return } + + let apply = { + collectionView.reload(using: changeset, interrupt: { $0.changeCount > 100 }) + { newSections in + self.currentSnapshot = .init(sections: newSections) + } + } + CATransaction.begin() + CATransaction.setCompletionBlock(completion) + if firstLoad || !animated + { + CATransaction.setDisableActions(true) + apply() + } + else + { + apply() + } + CATransaction.commit() + firstLoad = false + } + + func numberOfSections(in collectionView: UICollectionView) -> Int + { + currentSnapshot.sections.count + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int + { + currentSnapshot.sections[section].elements.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell + { + let itemIdentifier = identifier(at: indexPath) + guard let cell = cellProvider(collectionView, indexPath, itemIdentifier) + else + { + fatalError("ASCollectionView dataSource returned a nil cell for row at index path: \(indexPath), collectionView: \(collectionView), itemIdentifier: \(itemIdentifier)") + } + return cell + } + + private let supplementaryEmptyKind = UUID().uuidString // Used to prevent crash if supplementaries defined in layout but not provided by the section + private let supplementaryEmptyReuseID = UUID().uuidString + + func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView + { + guard let cell = supplementaryViewProvider?(collectionView, kind, indexPath) + else + { + let empty = collectionView.dequeueReusableSupplementaryView(ofKind: supplementaryEmptyKind, withReuseIdentifier: supplementaryEmptyReuseID, for: indexPath) + (empty as? ASCollectionViewSupplementaryView)?.setAsEmpty(supplementaryID: ASSupplementaryCellID(sectionIDHash: 0, supplementaryKind: supplementaryEmptyKind)) + return empty + } + return cell + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Delegate/ASCollectionViewDelegate.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Delegate/ASCollectionViewDelegate.swift new file mode 100644 index 0000000..4c9aa50 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Delegate/ASCollectionViewDelegate.swift @@ -0,0 +1,128 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI + +/// ASCollectionViewDelegate: Subclass this to create a custom delegate (eg. for supporting UICollectionViewLayouts that default to using the collectionView delegate) +open class ASCollectionViewDelegate: NSObject, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout +{ + weak var coordinator: ASCollectionViewCoordinator? + + public func getDataForItem(at indexPath: IndexPath) -> Any? + { + coordinator?.typeErasedDataForItem(at: indexPath) + } + + public func getDataForItem(at indexPath: IndexPath) -> T? + { + coordinator?.typeErasedDataForItem(at: indexPath) as? T + } + + open func collectionViewSelfSizingSettings(forContext: ASSelfSizingContext) -> ASSelfSizingConfig? + { + nil + } + + open var collectionViewContentInsetAdjustmentBehavior: UIScrollView.ContentInsetAdjustmentBehavior + { + .automatic + } + + open func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) + { + coordinator?.collectionView(collectionView, willDisplay: cell, forItemAt: indexPath) + } + + open func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) + { + coordinator?.collectionView(collectionView, didEndDisplaying: cell, forItemAt: indexPath) + } + + open func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) + { + coordinator?.collectionView(collectionView, willDisplaySupplementaryView: view, forElementKind: elementKind, at: indexPath) + } + + open func collectionView(_ collectionView: UICollectionView, didEndDisplayingSupplementaryView view: UICollectionReusableView, forElementOfKind elementKind: String, at indexPath: IndexPath) + { + coordinator?.collectionView(collectionView, didEndDisplayingSupplementaryView: view, forElementOfKind: elementKind, at: indexPath) + } + + open func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool + { + coordinator?.collectionView(collectionView, shouldHighlightItemAt: indexPath) ?? true + } + + open func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) + { + coordinator?.collectionView(collectionView, didHighlightItemAt: indexPath) + } + + open func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) + { + coordinator?.collectionView(collectionView, didUnhighlightItemAt: indexPath) + } + + open func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool + { + coordinator?.collectionView(collectionView, shouldSelectItemAt: indexPath) ?? true + } + + open func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool + { + coordinator?.collectionView(collectionView, shouldDeselectItemAt: indexPath) ?? true + } + + open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) + { + coordinator?.collectionView(collectionView, didSelectItemAt: indexPath) + } + + open func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) + { + coordinator?.collectionView(collectionView, didDeselectItemAt: indexPath) + } + + /* + //REPLACED WITH CUSTOM PREFETCH SOLUTION AS PREFETCH API WAS NOT WORKING FOR COMPOSITIONAL LAYOUT + public func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) + public func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) + */ +} + +extension ASCollectionViewDelegate: UICollectionViewDragDelegate, UICollectionViewDropDelegate +{ + open func collectionView(_ collectionView: UICollectionView, dragSessionAllowsMoveOperation session: UIDragSession) -> Bool + { + true + } + + open func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] + { + coordinator?.collectionView(collectionView, itemsForBeginning: session, at: indexPath) ?? [] + } + + open func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal + { + coordinator?.collectionView(collectionView, dropSessionDidUpdate: session, withDestinationIndexPath: destinationIndexPath) ?? UICollectionViewDropProposal(operation: .cancel) + } + + // UICollectionView doesn't support dropping multiple items :( [http://www.openradar.me/42068699] + open func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) + { + self.coordinator?.collectionView(collectionView, performDropWith: coordinator) + } + + open func scrollViewDidScroll(_ scrollView: UIScrollView) + { + coordinator?.scrollViewDidScroll(scrollView) + } +} + +extension ASCollectionViewDelegate +{ + open func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? + { + coordinator?.collectionView(collectionView, contextMenuConfigurationForItemAt: indexPath, point: point) + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Environment/EnvironmentKeys.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Environment/EnvironmentKeys.swift new file mode 100644 index 0000000..beb4078 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Environment/EnvironmentKeys.swift @@ -0,0 +1,33 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI + +// MARK: Internal Key Definitions + +struct EnvironmentKeyInvalidateCellLayout: EnvironmentKey +{ + static let defaultValue: ((_ animated: Bool) -> Void)? = nil +} + +struct EnvironmentKeyCollectionViewScrollToCell: EnvironmentKey +{ + static let defaultValue: ((UICollectionView.ScrollPosition) -> Void)? = nil +} + +// MARK: Internal Helpers + +public extension EnvironmentValues +{ + var invalidateCellLayout: ((_ animated: Bool) -> Void)? + { + get { self[EnvironmentKeyInvalidateCellLayout.self] } + set { self[EnvironmentKeyInvalidateCellLayout.self] = newValue } + } + + var collectionViewScrollToCell: ((UICollectionView.ScrollPosition) -> Void)? + { + get { self[EnvironmentKeyCollectionViewScrollToCell.self] } + set { self[EnvironmentKeyCollectionViewScrollToCell.self] = newValue } + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/FunctionBuilders/SectionArrayBuilder.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/FunctionBuilders/SectionArrayBuilder.swift new file mode 100644 index 0000000..9f53988 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/FunctionBuilders/SectionArrayBuilder.swift @@ -0,0 +1,81 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI + +public protocol Nestable +{ + associatedtype T + func asArray() -> [T] +} + +extension ASSection: Nestable +{ + public func asArray() -> [ASSection] + { + [self] + } +} + +extension Optional: Nestable where Wrapped: Nestable +{ + public func asArray() -> [Wrapped.T] + { + map { $0.asArray() } ?? [] + } +} + +extension Array: Nestable +{ + public func asArray() -> Self + { + self + } +} + +public func buildSectionArray(@SectionArrayBuilder _ sections: () -> [ASSection]) -> [ASSection] +{ + sections() +} + +@_functionBuilder +public struct SectionArrayBuilder where SectionID: Hashable +{ + public typealias Section = ASCollectionViewSection + public typealias Output = [Section] + + public static func buildExpression(_ section: ASSection?) -> Output + { + section?.asArray() ?? [] + } + + public static func buildExpression(_ sections: [ASSection]) -> Output + { + sections + } + + public static func buildEither(first: Output) -> Output + { + first.asArray() + } + + public static func buildEither(second: Output) -> Output + { + second.asArray() + } + + public static func buildIf(_ item: Output?) -> Output + { + item?.asArray() ?? [] + } + + public static func buildBlock(_ item0: Output) -> Output + { + item0.asArray() + } + + public static func buildBlock(_ items: Output...) -> Output + { + items.flatMap { $0 } + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/FunctionBuilders/ViewArrayBuilder.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/FunctionBuilders/ViewArrayBuilder.swift new file mode 100644 index 0000000..c9a7cd4 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/FunctionBuilders/ViewArrayBuilder.swift @@ -0,0 +1,71 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI + +@_functionBuilder +public struct ViewArrayBuilder +{ + public enum Wrapper + { + case empty + case view(AnyView) + case group([Wrapper]) + + init(_ view: Content) + { + self = .view(AnyView(view)) + } + + func flattened() -> [AnyView] + { + switch self + { + case .empty: + return [] + case let .view(theView): + return [theView] + case let .group(wrappers): + return wrappers.flatMap { $0.flattened() } + } + } + } + + public typealias Output = Wrapper + + public static func buildExpression(_ view: Content?) -> Wrapper + { + view.map { Wrapper($0) } ?? .empty + } + + public static func buildExpression(_ views: [Content]?) -> Wrapper + { + guard let views = views else { return .empty } + return Wrapper.group(views.map { Wrapper($0) }) + } + + public static func buildEither(first: Wrapper) -> Output + { + first + } + + public static func buildEither(second: Wrapper) -> Output + { + second + } + + public static func buildIf(_ item: Wrapper?) -> Output + { + item ?? .empty + } + + public static func buildBlock(_ item0: Wrapper) -> Output + { + item0 + } + + public static func buildBlock(_ items: Wrapper...) -> Output + { + .group(items) + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Implementation/ASCollectionView.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Implementation/ASCollectionView.swift new file mode 100644 index 0000000..6ee4250 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Implementation/ASCollectionView.swift @@ -0,0 +1,1086 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Combine +import SwiftUI + +public struct ASCollectionView: UIViewControllerRepresentable, ContentSize +{ + // MARK: Type definitions + + public typealias Section = ASCollectionViewSection + public typealias Layout = ASCollectionLayout + + public typealias OnScrollCallback = ((_ contentOffset: CGPoint, _ contentSize: CGSize) -> Void) + public typealias OnReachedBoundaryCallback = ((_ boundary: Boundary) -> Void) + + // MARK: Key variables + + public var layout: Layout = .default + public var sections: [Section] + public var editMode: Bool = false + + // MARK: Internal variables modified by modifier functions + + internal var delegateInitialiser: (() -> ASCollectionViewDelegate) = ASCollectionViewDelegate.init + + internal var contentSizeTracker: ContentSizeTracker? + + internal var onScrollCallback: OnScrollCallback? + internal var onReachedBoundaryCallback: OnReachedBoundaryCallback? + + internal var backgroundColor: UIColor? + + internal var horizontalScrollIndicatorEnabled: Bool = true + internal var verticalScrollIndicatorEnabled: Bool = true + internal var contentInsets: UIEdgeInsets = .zero + + internal var onPullToRefresh: ((_ endRefreshing: @escaping (() -> Void)) -> Void)? + + internal var alwaysBounceVertical: Bool = false + internal var alwaysBounceHorizontal: Bool = false + + internal var scrollPositionSetter: Binding? + + internal var animateOnDataRefresh: Bool = true + + internal var maintainScrollPositionOnOrientationChange: Bool = true + + internal var shouldInvalidateLayoutOnStateChange: Bool = false + internal var shouldAnimateInvalidatedLayoutOnStateChange: Bool = false + + internal var shouldRecreateLayoutOnStateChange: Bool = false + internal var shouldAnimateRecreatedLayoutOnStateChange: Bool = false + + internal var dodgeKeyboard: Bool = true + + // MARK: Environment variables + + // SwiftUI environment + @Environment(\.invalidateCellLayout) var invalidateParentCellLayout // Call this if using content size binding (nested inside another ASCollectionView) + + public func makeUIViewController(context: Context) -> AS_CollectionViewController + { + context.coordinator.parent = self + + let delegate = delegateInitialiser() + delegate.coordinator = context.coordinator + + let collectionViewLayout = layout.makeLayout(withCoordinator: context.coordinator) + + let collectionViewController = AS_CollectionViewController(collectionViewLayout: collectionViewLayout) + collectionViewController.coordinator = context.coordinator + + context.coordinator.collectionViewController = collectionViewController + context.coordinator.delegate = delegate + + context.coordinator.setupDataSource(forCollectionView: collectionViewController.collectionView) + + return collectionViewController + } + + public func updateUIViewController(_ collectionViewController: AS_CollectionViewController, context: Context) + { + context.coordinator.parent = self + context.coordinator.updateCollectionViewSettings(collectionViewController.collectionView) + context.coordinator.updateContent(collectionViewController.collectionView, transaction: context.transaction) + context.coordinator.updateLayout() + context.coordinator.configureRefreshControl(for: collectionViewController.collectionView) + context.coordinator.setupKeyboardObservers() +#if DEBUG + debugOnly_checkHasUniqueSections() +#endif + } + + public func makeCoordinator() -> Coordinator + { + Coordinator(self) + } + +#if DEBUG + func debugOnly_checkHasUniqueSections() + { + var sectionIDs: Set = [] + var conflicts: Set = [] + sections.forEach + { + let (inserted, _) = sectionIDs.insert($0.id) + if !inserted + { + conflicts.insert($0.id) + } + } + if !conflicts.isEmpty + { + print("ASCOLLECTIONVIEW: The following section IDs are used more than once, please use unique section IDs to avoid unexpected behaviour:", conflicts) + } + } +#endif + + // MARK: Coordinator Class + + public class Coordinator: ASCollectionViewCoordinator + { + var parent: ASCollectionView + var delegate: ASCollectionViewDelegate? + + weak var collectionViewController: AS_CollectionViewController? + + var dataSource: ASDiffableDataSourceCollectionView? + + let cellReuseID = UUID().uuidString + let supplementaryReuseID = UUID().uuidString + + // MARK: Private tracking variables + + private var hasDoneInitialSetup = false + private var shouldAnimateScrollPositionSet = false + + private var hasFiredBoundaryNotificationForBoundary: Set = [] + private var haveRegisteredForSupplementaryOfKind: Set = [] + + private var selectedIndexPaths: Set = [] + + typealias Cell = ASCollectionViewCell + + init(_ parent: ASCollectionView) + { + self.parent = parent + } + + deinit + { + NotificationCenter.default.removeObserver(self) + } + + func sectionID(fromSectionIndex sectionIndex: Int) -> SectionID? + { + parent.sections[safe: sectionIndex]?.id + } + + func section(forItemID itemID: ASCollectionViewItemUniqueID) -> Section? + { + parent.sections + .first(where: { $0.id.hashValue == itemID.sectionIDHash }) + } + + func supplementaryKinds() -> Set + { + parent.sections.reduce(into: Set()) + { result, section in + result.formUnion(section.supplementaryKinds) + } + } + + func registerSupplementaries(forCollectionView cv: UICollectionView) + { + supplementaryKinds().subtracting(haveRegisteredForSupplementaryOfKind).forEach + { kind in + cv.register(ASCollectionViewSupplementaryView.self, forSupplementaryViewOfKind: kind, withReuseIdentifier: supplementaryReuseID) + self.haveRegisteredForSupplementaryOfKind.insert(kind) // We don't need to register this kind again now. + } + } + + func updateCollectionViewSettings(_ collectionView: UICollectionView) + { + assignIfChanged(collectionView, \.backgroundColor, newValue: parent.backgroundColor) + assignIfChanged(collectionView, \.dragInteractionEnabled, newValue: true) + assignIfChanged(collectionView, \.alwaysBounceVertical, newValue: parent.alwaysBounceVertical) + assignIfChanged(collectionView, \.alwaysBounceHorizontal, newValue: parent.alwaysBounceHorizontal) + assignIfChanged(collectionView, \.allowsSelection, newValue: true) + assignIfChanged(collectionView, \.allowsMultipleSelection, newValue: true) + assignIfChanged(collectionView, \.showsVerticalScrollIndicator, newValue: parent.verticalScrollIndicatorEnabled) + assignIfChanged(collectionView, \.showsHorizontalScrollIndicator, newValue: parent.horizontalScrollIndicatorEnabled) + assignIfChanged(collectionView, \.keyboardDismissMode, newValue: .interactive) + updateCollectionViewContentInsets(collectionView) + } + + func updateCollectionViewContentInsets(_ collectionView: UICollectionView) + { + assignIfChanged(collectionView, \.contentInsetAdjustmentBehavior, newValue: delegate?.collectionViewContentInsetAdjustmentBehavior ?? .automatic) + assignIfChanged(collectionView, \.contentInset, newValue: adaptiveContentInsets) + } + + func isIndexPathSelected(_ indexPath: IndexPath) -> Bool + { + collectionViewController?.collectionView.indexPathsForSelectedItems?.contains(indexPath) ?? false + } + + func setupDataSource(forCollectionView cv: UICollectionView) + { + cv.delegate = delegate + cv.dragDelegate = delegate + cv.dropDelegate = delegate + + cv.register(Cell.self, forCellWithReuseIdentifier: cellReuseID) + + dataSource = .init(collectionView: cv) + { [weak self] collectionView, indexPath, itemID in + guard let self = self else { return nil } + + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.cellReuseID, for: indexPath) as? Cell + else { return nil } + + guard let section = self.parent.sections[safe: indexPath.section] else { return cell } + + cell.collectionViewController = self.collectionViewController + + // Self Sizing Settings + let selfSizingContext = ASSelfSizingContext(cellType: .content, indexPath: indexPath) + cell.selfSizingConfig = + section.dataSource.getSelfSizingSettings(context: selfSizingContext) + ?? self.delegate?.collectionViewSelfSizingSettings(forContext: selfSizingContext) + ?? (collectionView.collectionViewLayout as? ASCollectionViewLayoutProtocol)?.selfSizingConfig + ?? ASSelfSizingConfig() + + cell.isSelected = self.isIndexPathSelected(indexPath) + + cell.setContent(itemID: itemID, content: section.dataSource.content(forItemID: itemID)) + + cell.disableSwiftUIDropInteraction = section.dataSource.dropEnabled + cell.disableSwiftUIDragInteraction = section.dataSource.dragEnabled + + cell.hostingController.invalidateCellLayoutCallback = { [weak self] animated in + self?.invalidateLayoutOnNextUpdate = true // Queue for after updated data passed to ASCollectionView + self?.invalidateLayout(animated: animated) // Do immediately in-case the change is in state within the cell + } + cell.hostingController.collectionViewScrollToCellCallback = { [weak self] position in + self?.scrollToItem(indexPath: indexPath, position: position) + } + + return cell + } + dataSource?.supplementaryViewProvider = { [weak self] cv, kind, indexPath in + guard let self = self else { return nil } + + guard self.supplementaryKinds().contains(kind) + else + { + return nil + } + guard let reusableView = cv.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: self.supplementaryReuseID, for: indexPath) as? ASCollectionViewSupplementaryView + else { return nil } + + guard let section = self.parent.sections[safe: indexPath.section] else { reusableView.setAsEmpty(supplementaryID: nil); return reusableView } + let supplementaryID = ASSupplementaryCellID(sectionIDHash: section.id.hashValue, supplementaryKind: kind) + reusableView.supplementaryID = supplementaryID + + // Self Sizing Settings + let selfSizingContext = ASSelfSizingContext(cellType: .supplementary(kind), indexPath: indexPath) + reusableView.selfSizingConfig = + section.dataSource.getSelfSizingSettings(context: selfSizingContext) + ?? ASSelfSizingConfig() + + reusableView.setContent(supplementaryID: supplementaryID, content: section.dataSource.content(supplementaryID: supplementaryID)) + + return reusableView + } + setupPrefetching() + } + + func populateDataSource(animated: Bool = true, transaction: Transaction? = nil) + { + guard hasDoneInitialSetup else { return } + collectionViewController.map { registerSupplementaries(forCollectionView: $0.collectionView) } // New sections might involve new types of supplementary... + let snapshot = ASDiffableDataSourceSnapshot(sections: + parent.sections.map + { + ASDiffableDataSourceSnapshot.Section(id: $0.id, elements: $0.itemIDs) + } + ) + if invalidateLayoutOnNextUpdate + { + collectionViewController?.collectionViewLayout.invalidateLayout() + invalidateLayoutOnNextUpdate = false + } + dataSource?.applySnapshot(snapshot, animated: animated) + shouldAnimateScrollPositionSet = animated + + refreshVisibleCells(transaction: transaction, updateAll: false) + } + + func updateContent(_ cv: UICollectionView, transaction: Transaction?) + { + guard hasDoneInitialSetup else { return } + + let transactionAnimationEnabled = (transaction?.animation != nil) && !(transaction?.disablesAnimations ?? false) + populateDataSource( + animated: parent.animateOnDataRefresh && transactionAnimationEnabled, + transaction: transaction) + + updateSelection(cv, transaction: transaction) + } + + func refreshVisibleCells(transaction: Transaction? = nil, updateAll: Bool = true) + { + guard let cv = collectionViewController?.collectionView else { return } + for cell in cv.visibleCells + { + refreshCell(cell, forceUpdate: updateAll) + } + + supplementaryKinds().forEach + { kind in + for indexPath in cv.indexPathsForVisibleSupplementaryElements(ofKind: kind) + { + guard let supplementaryView = (cv.supplementaryView(forElementKind: kind, at: indexPath) as? ASCollectionViewSupplementaryView) else { continue } + guard let section = parent.sections[safe: indexPath.section] else { continue } + + // Get cachedHC + let supplementaryID = ASSupplementaryCellID(sectionIDHash: section.id.hashValue, supplementaryKind: kind) + // Update hostingController + supplementaryView.setContent(supplementaryID: supplementaryID, content: section.dataSource.content(supplementaryID: supplementaryID)) + } + } + } + + func refreshCell(_ cell: UICollectionViewCell, forceUpdate: Bool = false) + { + guard + let cell = cell as? Cell, + let itemID = cell.itemID, + let section = section(forItemID: itemID) + else { return } +// if cell.skipNextRefresh, !forceUpdate +// { +// cell.skipNextRefresh = false +// } +// else +// { + cell.setContent(itemID: itemID, content: section.dataSource.content(forItemID: itemID)) + cell.disableSwiftUIDropInteraction = section.dataSource.dropEnabled + cell.disableSwiftUIDragInteraction = section.dataSource.dragEnabled +// } + } + + func onMoveToParent() + { + guard !hasDoneInitialSetup else { return } + + hasDoneInitialSetup = true + populateDataSource(animated: false) + } + + func onMoveFromParent() {} + + var invalidateLayoutOnNextUpdate: Bool = false + func invalidateLayout(animated: Bool) + { + CATransaction.begin() + if !animated + { + CATransaction.setDisableActions(true) + } + collectionViewController?.collectionViewLayout.invalidateLayout() + CATransaction.commit() + } + + var areKeyboardObserversSetUp: Bool = false + func setupKeyboardObservers() + { + if parent.dodgeKeyboard + { + guard !areKeyboardObserversSetUp else { return } + areKeyboardObserversSetUp = true + NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil) + } + else if areKeyboardObserversSetUp + { + NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) + NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) + } + } + + var keyboardFrame: CGRect? + { + didSet + { + collectionViewController.map + { + updateCollectionViewContentInsets($0.collectionView) + } + } + } + + var keyboardOverlap: CGFloat + { + guard + let cv = collectionViewController?.collectionView, + let cvFrameInWindow = cv.superview?.convert(cv.frame, to: nil), + let intersection = keyboardFrame?.intersection(cvFrameInWindow) + else { return .zero } + return intersection.height + } + + var extraKeyboardSpacing: CGFloat = 25 + + var adaptiveContentInsets: UIEdgeInsets + { + UIEdgeInsets( + top: parent.contentInsets.top, + left: parent.contentInsets.left, + bottom: parent.contentInsets.bottom + (parent.dodgeKeyboard ? keyboardOverlap : 0), + right: parent.contentInsets.right) + } + + func containsFirstResponder() -> Bool + { + collectionViewController?.collectionView.findFirstResponder != nil + } + + @objc func keyBoardWillShow(notification: Notification) + { + guard containsFirstResponder() + else + { + keyboardFrame = nil + return + } + + keyboardFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue + + // Do our own adjustment of contentOffset + if let cv = collectionViewController?.collectionView, + let firstResponder = cv.findFirstResponder() + { + let firstResponderFrame = firstResponder.convert(firstResponder.bounds, to: cv) + let newContentOffset = CGPoint( + x: cv.contentOffset.x, + y: cv.adjustedContentInset.top + firstResponderFrame.maxY + keyboardOverlap + extraKeyboardSpacing - cv.frame.height) + if newContentOffset.y > cv.contentOffset.y + { + cv.contentOffset = newContentOffset + } + } + } + + @objc func keyBoardWillHide(notification _: Notification) + { + keyboardFrame = nil + collectionViewController?.collectionView.layoutIfNeeded() + } + + func configureRefreshControl(for cv: UICollectionView) + { + guard parent.onPullToRefresh != nil + else + { + if cv.refreshControl != nil + { + cv.refreshControl = nil + } + return + } + if cv.refreshControl == nil + { + let refreshControl = UIRefreshControl() + refreshControl.addTarget(self, action: #selector(collectionViewDidPullToRefresh), for: .valueChanged) + cv.refreshControl = refreshControl + } + } + + @objc + public func collectionViewDidPullToRefresh() + { + guard let collectionView = collectionViewController?.collectionView else { return } + let endRefreshing: (() -> Void) = { [weak collectionView] in + collectionView?.refreshControl?.endRefreshing() + } + parent.onPullToRefresh?(endRefreshing) + } + + // MARK: Functions for determining scroll position (on appear, and also on orientation change) + + func scrollToItem(indexPath: IndexPath, position: UICollectionView.ScrollPosition = []) + { + CATransaction.begin() + collectionViewController?.collectionView.scrollToItem(at: indexPath, at: position, animated: true) + CATransaction.commit() + } + + func applyScrollPosition(animated: Bool) + { + if let scrollPositionToSet = parent.scrollPositionSetter?.wrappedValue + { + scrollToPosition(scrollPositionToSet, animated: animated) + DispatchQueue.main.async + { + self.parent.scrollPositionSetter?.wrappedValue = nil + } + } + } + + func scrollToPosition(_ scrollPosition: ASCollectionViewScrollPosition, animated: Bool = false) + { + switch scrollPosition + { + case .top, .left: + collectionViewController?.collectionView.setContentOffset(.zero, animated: animated) + case .bottom: + guard let maxOffset = collectionViewController?.collectionView.maxContentOffset else { return } + collectionViewController?.collectionView.setContentOffset(.init(x: 0, y: maxOffset.y), animated: animated) + case .right: + guard let maxOffset = collectionViewController?.collectionView.maxContentOffset else { return } + collectionViewController?.collectionView.setContentOffset(.init(x: maxOffset.x, y: 0), animated: animated) + case let .indexPath(indexPath, positionOnScreen, extraOffset): + collectionViewController?.collectionView.scrollToItem(at: indexPath, at: positionOnScreen, animated: animated) + collectionViewController?.collectionView.contentOffset.x += extraOffset.x + collectionViewController?.collectionView.contentOffset.y += extraOffset.y + } + } + + func prepareForOrientationChange() + { + guard let collectionView = collectionViewController?.collectionView else { return } + + if parent.maintainScrollPositionOnOrientationChange + { + // Get centremost cell + if let indexPath = collectionView.indexPathForItem(at: CGPoint(x: collectionView.bounds.midX, y: collectionView.bounds.midY)) + { + // Item at centre + transitionCentralIndexPath = indexPath + } + else if let visibleCells = collectionViewController?.collectionView.indexPathsForVisibleItems, !visibleCells.isEmpty + { + // Approximate item at centre + transitionCentralIndexPath = visibleCells[visibleCells.count / 2] + } + else + { + transitionCentralIndexPath = nil + } + } + } + + var transitionCentralIndexPath: IndexPath? + func getContentOffsetForOrientationChange() -> CGPoint? + { + if parent.maintainScrollPositionOnOrientationChange + { + guard let currentOffset = collectionViewController?.collectionView.contentOffset, currentOffset.x > 0, currentOffset.y > 0 else { return nil } + return transitionCentralIndexPath.flatMap(getContentOffsetToCenterCell) + } + else + { + return nil + } + } + + func completedOrientationChange() + { + transitionCentralIndexPath = nil + } + + func getContentOffsetToCenterCell(at indexPath: IndexPath) -> CGPoint? + { + guard + let collectionView = collectionViewController?.collectionView, + let centerCellFrame = collectionView.layoutAttributesForItem(at: indexPath)?.frame + else { return nil } + let maxOffset = collectionView.maxContentOffset + let newOffset = CGPoint( + x: max(0, min(maxOffset.x, centerCellFrame.midX - (collectionView.bounds.width / 2))), + y: max(0, min(maxOffset.y, centerCellFrame.midY - (collectionView.bounds.height / 2)))) + return newOffset + } + + // MARK: Functions for updating layout + + func updateLayout() + { + guard + hasDoneInitialSetup, + let collectionViewController = collectionViewController else { return } + // Configure any custom layout + parent.layout.configureLayout(layoutObject: collectionViewController.collectionView.collectionViewLayout) + + // If enabled, recreate the layout + if parent.shouldRecreateLayoutOnStateChange + { + let newLayout = parent.layout.makeLayout(withCoordinator: self) + collectionViewController.collectionView.setCollectionViewLayout(newLayout, animated: parent.shouldAnimateRecreatedLayoutOnStateChange && hasDoneInitialSetup) + } + // If enabled, invalidate the layout + else if parent.shouldInvalidateLayoutOnStateChange + { + let changes = { + collectionViewController.collectionViewLayout.invalidateLayout() + } + if parent.shouldAnimateInvalidatedLayoutOnStateChange, hasDoneInitialSetup + { + changes() + } + else + { + CATransaction.begin() + CATransaction.setDisableActions(true) + changes() + CATransaction.commit() + } + } + } + + // MARK: CollectionViewDelegate functions + + // NOTE: These are not called directly, but rather forwarded to the Coordinator by the ASCollectionViewDelegate class + + public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) + { + currentlyPrefetching.remove(indexPath) + parent.sections[safe: indexPath.section]?.dataSource.onAppear(indexPath) + queuePrefetch.send() + } + + public func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) + { + guard !indexPath.isEmpty else { return } + parent.sections[safe: indexPath.section]?.dataSource.onDisappear(indexPath) + } + + public func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) + {} + + public func collectionView(_ collectionView: UICollectionView, didEndDisplayingSupplementaryView view: UICollectionReusableView, forElementOfKind elementKind: String, at indexPath: IndexPath) + {} + + public func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool + { + parent.sections[safe: indexPath.section]?.dataSource.shouldHighlight(indexPath) ?? true + } + + public func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) + { + parent.sections[safe: indexPath.section]?.dataSource.highlightIndex(indexPath.item) + } + + public func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) + { + parent.sections[safe: indexPath.section]?.dataSource.unhighlightIndex(indexPath.item) + } + + public func collectionView(_ collectionView: UICollectionView, willSelectItemAt indexPath: IndexPath) -> IndexPath? + { + self.collectionView(collectionView, shouldSelectItemAt: indexPath) ? indexPath : nil + } + + public func collectionView(_ collectionView: UICollectionView, willDeselectItemAt indexPath: IndexPath) -> IndexPath? + { + self.collectionView(collectionView, shouldDeselectItemAt: indexPath) ? indexPath : nil + } + + public func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool + { + parent.sections[safe: indexPath.section]?.dataSource.shouldSelect(indexPath) ?? true + } + + public func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool + { + parent.sections[safe: indexPath.section]?.dataSource.shouldDeselect(indexPath) ?? true + } + + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) + { + updateSelection(collectionView) + parent.sections[safe: indexPath.section]?.dataSource.didSelect(indexPath) + } + + public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) + { + updateSelection(collectionView) + } + + func updateSelection(_ collectionView: UICollectionView, transaction: Transaction? = nil) + { + let selectedInDataSource = selectedIndexPathsInDataSource + let selectedInCollectionView = Set(collectionView.indexPathsForSelectedItems ?? []) + guard selectedInDataSource != selectedInCollectionView else { return } + + let newSelection = threeWayMerge(base: selectedIndexPaths, dataSource: selectedInDataSource, collectionView: selectedInCollectionView) + let (toDeselect, toSelect) = selectionDifferences(oldSelectedIndexPaths: selectedInCollectionView, newSelectedIndexPaths: newSelection) + + selectedIndexPaths = newSelection + updateSelectionBindings(newSelection) + updateSelectionInCollectionView(collectionView, indexPathsToDeselect: toDeselect, indexPathsToSelect: toSelect, transaction: transaction) + } + + private var selectedIndexPathsInDataSource: Set + { + parent.sections.enumerated().reduce(Set()) + { (selectedIndexPaths, section) -> Set in + guard let indexes = section.element.dataSource.selectedIndicesBinding?.wrappedValue else { return selectedIndexPaths } + let indexPaths = indexes.map { IndexPath(item: $0, section: section.offset) } + return selectedIndexPaths.union(indexPaths) + } + } + + private func threeWayMerge(base: Set, dataSource: Set, collectionView: Set) -> Set + { + // In case the data source and collection view are both different from base, default to the collection view + base == collectionView ? dataSource : collectionView + } + + private func selectionDifferences(oldSelectedIndexPaths: Set, newSelectedIndexPaths: Set) -> (toDeselect: Set, toSelect: Set) + { + let toDeselect = oldSelectedIndexPaths.subtracting(newSelectedIndexPaths) + let toSelect = newSelectedIndexPaths.subtracting(oldSelectedIndexPaths) + return (toDeselect: toDeselect, toSelect: toSelect) + } + + private func updateSelectionBindings(_ selectedIndexPaths: Set) + { + let selectionBySection = Dictionary(grouping: selectedIndexPaths) { $0.section } + .mapValues + { + Set($0.map(\.item)) + } + parent.sections.enumerated().forEach + { offset, section in + section.dataSource.updateSelection(with: selectionBySection[offset] ?? []) + } + } + + private func updateSelectionInCollectionView(_ collectionView: UICollectionView, indexPathsToDeselect: Set, indexPathsToSelect: Set, transaction: Transaction? = nil) + { + let isAnimated = (transaction?.animation != nil) && !(transaction?.disablesAnimations ?? false) + indexPathsToDeselect.forEach { collectionView.deselectItem(at: $0, animated: isAnimated) } + indexPathsToSelect.forEach { collectionView.selectItem(at: $0, animated: isAnimated, scrollPosition: []) } + } + + func canDrop(at indexPath: IndexPath) -> Bool + { + guard !indexPath.isEmpty else { return false } + return parent.sections[safe: indexPath.section]?.dataSource.dropEnabled ?? false + } + + func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] + { + guard !indexPath.isEmpty else { return [] } + guard let dragItem = parent.sections[safe: indexPath.section]?.dataSource.getDragItem(for: indexPath) else { return [] } + return [dragItem] + } + + func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal + { + if collectionView.hasActiveDrag + { + if let destination = destinationIndexPath + { + guard canDrop(at: destination) + else + { + return UICollectionViewDropProposal(operation: .cancel) + } + } + return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) + } + else + { + return UICollectionViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath) + } + } + + func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) + { + guard + let destinationIndexPath = coordinator.destinationIndexPath, + !destinationIndexPath.isEmpty, + let destinationSection = parent.sections[safe: destinationIndexPath.section] + else { return } + + guard canDrop(at: destinationIndexPath) else { return } + + guard let oldSnapshot = dataSource?.currentSnapshot else { return } + var dragSnapshot = oldSnapshot + + switch coordinator.proposal.operation + { + case .move: + guard destinationSection.dataSource.reorderingEnabled else { return } + + let itemsBySourceSection = Dictionary(grouping: coordinator.items) + { item -> Int? in + if let sourceIndex = item.sourceIndexPath, !sourceIndex.isEmpty, + destinationSection.dataSource.supportsMove(from: sourceIndex, to: destinationIndexPath) + { + return sourceIndex.section + } + else + { + return nil + } + } + + let sourceSections = itemsBySourceSection.keys.sorted + { a, b in + guard let a = a else { return false } + guard let b = b else { return true } + return a < b + } + + var itemsToInsert: [UICollectionViewDropItem] = [] + + for sourceSectionIndex in sourceSections + { + guard let items = itemsBySourceSection[sourceSectionIndex] else { continue } + + if + let sourceSectionIndex = sourceSectionIndex, + let sourceSection = parent.sections[safe: sourceSectionIndex] + { + guard sourceSection.dataSource.reorderingEnabled else { continue } + + let sourceIndices = items.compactMap { $0.sourceIndexPath?.item } + + // Remove from source section + dragSnapshot.removeItems(fromSectionIndex: sourceSectionIndex, atOffsets: IndexSet(sourceIndices)) + sourceSection.dataSource.applyRemove(atOffsets: IndexSet(sourceIndices)) + } + + // Add to insertion array (regardless whether sourceSection is nil) + itemsToInsert.append(contentsOf: items) + } + + let itemsToInsertIDs: [ASCollectionViewItemUniqueID] = itemsToInsert.compactMap + { item in + if let sourceIndexPath = item.sourceIndexPath + { + return oldSnapshot.sections[sourceIndexPath.section].elements[sourceIndexPath.item].differenceIdentifier + } + else + { + return destinationSection.dataSource.getItemID(for: item.dragItem, withSectionID: destinationSection.id) + } + } + if destinationSection.dataSource.applyInsert(items: itemsToInsert.map(\.dragItem), at: destinationIndexPath.item) + { + dragSnapshot.insertItems(itemsToInsertIDs, atSectionIndex: destinationIndexPath.section, atOffset: destinationIndexPath.item) + } + + case .copy: + _ = destinationSection.dataSource.applyInsert(items: coordinator.items.map(\.dragItem), at: destinationIndexPath.item) + + default: break + } + + if let dragItem = coordinator.items.first, let destination = coordinator.destinationIndexPath + { + if dragItem.sourceIndexPath != nil + { + coordinator.drop(dragItem.dragItem, toItemAt: destination) + } + } + dataSource?.applySnapshot(dragSnapshot, animated: true) + refreshVisibleCells() + } + + func typeErasedDataForItem(at indexPath: IndexPath) -> Any? + { + guard !indexPath.isEmpty else { return nil } + return parent.sections[safe: indexPath.section]?.dataSource.getTypeErasedData(for: indexPath) + } + + // MARK: Functions for updating contentSize binding + + var lastContentSize: CGSize = .zero + func didUpdateContentSize(_ size: CGSize) + { + guard let cv = collectionViewController?.collectionView, cv.contentSize.width != .zero, cv.contentSize.height != .zero else { return } + + if cv.contentSize != lastContentSize + { + let firstSize = lastContentSize == .zero + lastContentSize = cv.contentSize + parent.contentSizeTracker?.contentSize = size + + DispatchQueue.main.async + { + self.parent.invalidateParentCellLayout?(!firstSize) + } + applyScrollPosition(animated: shouldAnimateScrollPositionSet) + } + } + + // MARK: Variables used for the custom prefetching implementation + + private let queuePrefetch = PassthroughSubject() + private var prefetchSubscription: AnyCancellable? + private var currentlyPrefetching: Set = [] + } +} + +// MARK: OnScroll/OnReachedBoundary support + +extension ASCollectionView.Coordinator +{ + func scrollViewDidScroll(_ scrollView: UIScrollView) + { + parent.onScrollCallback?(scrollView.contentOffset, scrollView.contentSizePlusInsets) + checkIfReachedBoundary(scrollView) + } + + func checkIfReachedBoundary(_ scrollView: UIScrollView) + { + let scrollableHorizontally = scrollView.contentSizePlusInsets.width > scrollView.frame.size.width + let scrollableVertically = scrollView.contentSizePlusInsets.height > scrollView.frame.size.height + + for boundary in Boundary.allCases + { + let hasReachedBoundary: Bool = { + switch boundary + { + case .left: + return scrollableHorizontally && scrollView.contentOffset.x <= 0 + case .top: + return scrollableVertically && scrollView.contentOffset.y <= -scrollView.adjustedContentInset.top + case .right: + return scrollableHorizontally && (scrollView.contentSizePlusInsets.width - scrollView.contentOffset.x) <= scrollView.frame.size.width + case .bottom: + return scrollableVertically && (scrollView.contentSizePlusInsets.height - scrollView.contentOffset.y) <= scrollView.frame.size.height + } + }() + + if hasReachedBoundary + { + // If we haven't already fired the notification, send it now + if !hasFiredBoundaryNotificationForBoundary.contains(boundary) + { + hasFiredBoundaryNotificationForBoundary.insert(boundary) + parent.onReachedBoundaryCallback?(boundary) + } + } + else + { + // No longer at this boundary, reset so it can fire again if needed + hasFiredBoundaryNotificationForBoundary.remove(boundary) + } + } + } +} + +// MARK: Context Menu Support + +public extension ASCollectionView.Coordinator +{ + func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? + { + guard !indexPath.isEmpty else { return nil } + return parent.sections[safe: indexPath.section]?.dataSource.getContextMenu(for: indexPath) + } +} + +// MARK: Coordinator Protocol + +internal protocol ASCollectionViewCoordinator: AnyObject +{ + func typeErasedDataForItem(at indexPath: IndexPath) -> Any? + func prepareForOrientationChange() + func getContentOffsetForOrientationChange() -> CGPoint? + func completedOrientationChange() + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) + func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) + func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) + func collectionView(_ collectionView: UICollectionView, didEndDisplayingSupplementaryView view: UICollectionReusableView, forElementOfKind elementKind: String, at indexPath: IndexPath) + func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool + func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) + func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) + func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool + func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) + func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) + func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? + func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] + func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal + func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) + func didUpdateContentSize(_ size: CGSize) + func scrollViewDidScroll(_ scrollView: UIScrollView) + func onMoveToParent() + func onMoveFromParent() +} + +// MARK: Custom Prefetching Implementation + +extension ASCollectionView.Coordinator +{ + func setupPrefetching() + { + let numberToPreload = 5 + prefetchSubscription = queuePrefetch + .collect(.byTime(DispatchQueue.main, 0.1)) // .throttle CRASHES on 13.1, fixed from 13.3 but still using .collect for 13.1 compatibility + .compactMap + { [weak collectionViewController] _ in + collectionViewController?.collectionView.indexPathsForVisibleItems + } + .receive(on: DispatchQueue.global(qos: .background)) + .map + { [weak self] visibleIndexPaths -> [Int: [IndexPath]] in + guard let self = self else { return [:] } + let visibleIndexPathsBySection = Dictionary(grouping: visibleIndexPaths) { $0.section }.compactMapValues + { (indexPaths) -> (section: Int, first: Int, last: Int)? in + guard let first = indexPaths.min(), let last = indexPaths.max() else { return nil } + return (section: first.section, first: first.item, last: last.item) + } + var toPrefetch: [Int: [IndexPath]] = visibleIndexPathsBySection.compactMapValues + { item in + guard let sectionIndexPaths = self.parent.sections[safe: item.section]?.dataSource.getIndexPaths(withSectionIndex: item.section) else { return nil } + let nextItemsInSection: ArraySlice = { + guard (item.last + 1) < sectionIndexPaths.endIndex else { return [] } + return sectionIndexPaths[(item.last + 1) ..< min(item.last + numberToPreload + 1, sectionIndexPaths.endIndex)] + }() + let previousItemsInSection: ArraySlice = { + guard (item.first - 1) >= sectionIndexPaths.startIndex else { return [] } + return sectionIndexPaths[max(sectionIndexPaths.startIndex, item.first - numberToPreload) ..< item.first] + }() + return Array(nextItemsInSection) + Array(previousItemsInSection) + } + // CHECK IF THERES AN EARLIER SECTION TO PRELOAD + if + let firstSection = toPrefetch.keys.min(), // FIND THE EARLIEST VISIBLE SECTION + (firstSection - 1) >= self.parent.sections.startIndex, // CHECK THERE IS A SECTION BEFORE THIS + let firstIndex = visibleIndexPathsBySection[firstSection]?.first, firstIndex < numberToPreload // CHECK HOW CLOSE TO THIS SECTION WE ARE + { + let precedingSection = firstSection - 1 + toPrefetch[precedingSection] = self.parent.sections[precedingSection].dataSource.getIndexPaths(withSectionIndex: precedingSection).suffix(numberToPreload) + } + // CHECK IF THERES A LATER SECTION TO PRELOAD + if + let lastSection = toPrefetch.keys.max(), // FIND THE LAST VISIBLE SECTION + (lastSection + 1) < self.parent.sections.endIndex, // CHECK THERE IS A SECTION AFTER THIS + let lastIndex = visibleIndexPathsBySection[lastSection]?.last, + let lastSectionEndIndex = self.parent.sections[lastSection].dataSource.getIndexPaths(withSectionIndex: lastSection).last?.item, + (lastSectionEndIndex - lastIndex) < numberToPreload // CHECK HOW CLOSE TO THIS SECTION WE ARE + { + let nextSection = lastSection + 1 + toPrefetch[nextSection] = Array(self.parent.sections[nextSection].dataSource.getIndexPaths(withSectionIndex: nextSection).prefix(numberToPreload)) + } + return toPrefetch + } + .sink + { [weak self] prefetch in + prefetch.forEach + { sectionIndex, toPrefetch in + if !toPrefetch.isEmpty + { + self?.parent.sections[safe: sectionIndex]?.dataSource.prefetch(toPrefetch) + } + if + let toCancel = self?.currentlyPrefetching.filter({ $0.section == sectionIndex }).subtracting(toPrefetch), + !toCancel.isEmpty + { + self?.parent.sections[safe: sectionIndex]?.dataSource.cancelPrefetch(Array(toCancel)) + } + } + + self?.currentlyPrefetching = Set(prefetch.flatMap(\.value)) + } + } +} + +public enum ASCollectionViewScrollPosition +{ + case top + case bottom + case left + case right + case indexPath(_: IndexPath, positionOnScreen: UICollectionView.ScrollPosition = .centeredVertically, extraOffset: CGPoint = .zero) +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Implementation/ASHostingController.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Implementation/ASHostingController.swift new file mode 100644 index 0000000..cf306dc --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Implementation/ASHostingController.swift @@ -0,0 +1,231 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI + +internal struct ASHostingControllerWrapper: View, ASHostingControllerWrapperProtocol +{ + var invalidateCellLayoutCallback: ((_ animated: Bool) -> Void)? + var collectionViewScrollToCellCallback: ((UICollectionView.ScrollPosition) -> Void)? + + var content: Content + var body: some View + { + content + .environment(\.invalidateCellLayout, invalidateCellLayoutCallback) + .environment(\.collectionViewScrollToCell, collectionViewScrollToCellCallback) + } +} + +protocol ASHostingControllerWrapperProtocol +{ + var invalidateCellLayoutCallback: ((_ animated: Bool) -> Void)? { get set } + var collectionViewScrollToCellCallback: ((UICollectionView.ScrollPosition) -> Void)? { get set } +} + +internal protocol ASHostingControllerProtocol: AnyObject, ASHostingControllerWrapperProtocol +{ + var viewController: UIViewController { get } + func sizeThatFits(in size: CGSize, maxSize: ASOptionalSize, selfSizeHorizontal: Bool, selfSizeVertical: Bool) -> CGSize +} + +internal class ASHostingController: ASHostingControllerProtocol +{ + init(_ view: ViewType) + { + uiHostingController = .init(rootView: ASHostingControllerWrapper(content: view)) + } + + private let uiHostingController: AS_UIHostingController> + var viewController: UIViewController + { + uiHostingController.view.backgroundColor = .clear + uiHostingController.view.insetsLayoutMarginsFromSafeArea = false + return uiHostingController as UIViewController + } + + var disableSwiftUIDropInteraction: Bool + { + get { uiHostingController.shouldDisableDrop } + set { uiHostingController.shouldDisableDrop = newValue } + } + + var disableSwiftUIDragInteraction: Bool + { + get { uiHostingController.shouldDisableDrag } + set { uiHostingController.shouldDisableDrag = newValue } + } + + var hostedView: ViewType + { + get + { + uiHostingController.rootView.content + } + set + { + uiHostingController.rootView.content = newValue + } + } + + var invalidateCellLayoutCallback: ((_ animated: Bool) -> Void)? + { + get + { + uiHostingController.rootView.invalidateCellLayoutCallback + } + set + { + uiHostingController.rootView.invalidateCellLayoutCallback = newValue + } + } + + var collectionViewScrollToCellCallback: ((UICollectionView.ScrollPosition) -> Void)? + { + get + { + uiHostingController.rootView.collectionViewScrollToCellCallback + } + set + { + uiHostingController.rootView.collectionViewScrollToCellCallback = newValue + } + } + + func setView(_ view: ViewType) + { + hostedView = view + } + + func sizeThatFits(in size: CGSize, maxSize: ASOptionalSize, selfSizeHorizontal: Bool, selfSizeVertical: Bool) -> CGSize + { + guard selfSizeHorizontal || selfSizeVertical + else + { + return size.applyMaxSize(maxSize) + } + viewController.view.layoutIfNeeded() + let fittingSize = CGSize( + width: selfSizeHorizontal ? maxSize.width ?? .greatestFiniteMagnitude : size.width.applyOptionalMaxBound(maxSize.width), + height: selfSizeVertical ? maxSize.height ?? .greatestFiniteMagnitude : size.height.applyOptionalMaxBound(maxSize.height)) + + // Find the desired size + var desiredSize = uiHostingController.sizeThatFits(in: fittingSize) + + // Accounting for 'greedy' swiftUI views that take up as much space as they can + switch (desiredSize.width, desiredSize.height) + { + case (.greatestFiniteMagnitude, .greatestFiniteMagnitude): + desiredSize = uiHostingController.sizeThatFits(in: size.applyMaxSize(maxSize)) + case (.greatestFiniteMagnitude, _): + desiredSize = uiHostingController.sizeThatFits(in: CGSize( + width: size.width.applyOptionalMaxBound(maxSize.width), + height: fittingSize.height)) + case (_, .greatestFiniteMagnitude): + desiredSize = uiHostingController.sizeThatFits(in: CGSize( + width: fittingSize.width, + height: size.height.applyOptionalMaxBound(maxSize.height))) + default: break + } + + // Ensure correct dimensions in non-self sizing axes + if !selfSizeHorizontal { desiredSize.width = size.width } + if !selfSizeVertical { desiredSize.height = size.height } + + return desiredSize.applyMaxSize(maxSize) + } +} + +private class AS_UIHostingController: UIHostingController +{ + var shouldDisableDrop: Bool = false + { + didSet + { + if shouldDisableDrop != oldValue + { + disableInteractionsIfNeeded() + } + } + } + + var shouldDisableDrag: Bool = false + { + didSet + { + if shouldDisableDrag != oldValue + { + disableInteractionsIfNeeded() + } + } + } + + func disableInteractionsIfNeeded() + { + guard let view = viewIfLoaded else { return } + if shouldDisableDrop + { + if let dropInteraction = view.interactions.first(where: { + $0.isKind(of: UIDropInteraction.self) + }) as? UIDropInteraction + { + view.removeInteraction(dropInteraction) + } + } + if shouldDisableDrag + { + if let contextInteraction = view.interactions.first(where: { + $0.isKind(of: UIDragInteraction.self) + }) as? UIDragInteraction + { + view.removeInteraction(contextInteraction) + } + } + } + + func disableSafeArea() + { + guard let viewClass = object_getClass(view) else { return } + + let viewSubclassName = String(cString: class_getName(viewClass)).appending("_IgnoreSafeArea") + if let viewSubclass = NSClassFromString(viewSubclassName) + { + object_setClass(view, viewSubclass) + } + else + { + guard let viewClassNameUtf8 = (viewSubclassName as NSString).utf8String else { return } + guard let viewSubclass = objc_allocateClassPair(viewClass, viewClassNameUtf8, 0) else { return } + + if let method = class_getInstanceMethod(UIView.self, #selector(getter: UIView.safeAreaInsets)) + { + let safeAreaInsets: @convention(block) (AnyObject) -> UIEdgeInsets = { _ in + .zero + } + class_addMethod(viewSubclass, #selector(getter: UIView.safeAreaInsets), imp_implementationWithBlock(safeAreaInsets), method_getTypeEncoding(method)) + } + + if let method2 = class_getInstanceMethod(viewClass, NSSelectorFromString("keyboardWillShowWithNotification:")) + { + let keyboardWillShow: @convention(block) (AnyObject, AnyObject) -> Void = { _, _ in } + class_addMethod(viewSubclass, NSSelectorFromString("keyboardWillShowWithNotification:"), imp_implementationWithBlock(keyboardWillShow), method_getTypeEncoding(method2)) + } + + objc_registerClassPair(viewSubclass) + object_setClass(view, viewSubclass) + } + } + + override init(rootView: Content) + { + super.init(rootView: rootView) + disableSafeArea() + disableInteractionsIfNeeded() + } + + @available(*, unavailable) + @objc dynamic required init?(coder aDecoder: NSCoder) + { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Implementation/ASSection.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Implementation/ASSection.swift new file mode 100644 index 0000000..5f639e3 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Implementation/ASSection.swift @@ -0,0 +1,74 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI + +public struct ASCollectionViewStaticContent: Identifiable +{ + public var index: Int + var view: AnyView + + public var id: Int { index } +} + +public struct ASCollectionViewItemUniqueID: Hashable +{ + var sectionIDHash: Int + var itemIDHash: Int + init(sectionID: SectionID, itemID: ItemID) + { + sectionIDHash = sectionID.hashValue + itemIDHash = itemID.hashValue + } +} + +public typealias ASCollectionViewSection = ASSection + +public struct ASSection +{ + public var id: SectionID + + internal var dataSource: ASSectionDataSourceProtocol + + public var itemIDs: [ASCollectionViewItemUniqueID] + { + dataSource.getUniqueItemIDs(withSectionID: id) + } +} + +// MARK: SUPPLEMENTARY VIEWS - INTERNAL + +internal extension ASCollectionViewSection +{ + mutating func setHeaderView(_ view: Content?) + { + setSupplementaryView(view, ofKind: UICollectionView.elementKindSectionHeader) + } + + mutating func setFooterView(_ view: Content?) + { + setSupplementaryView(view, ofKind: UICollectionView.elementKindSectionFooter) + } + + mutating func setSupplementaryView(_ view: Content?, ofKind kind: String) + { + guard let view = view + else + { + dataSource.supplementaryViews.removeValue(forKey: kind) + return + } + + dataSource.supplementaryViews[kind] = AnyView(view) + } + + var supplementaryKinds: Set + { + Set(dataSource.supplementaryViews.keys) + } + + func supplementary(ofKind kind: String) -> AnyView? + { + dataSource.supplementaryViews[kind] + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Implementation/ASSectionDataSource.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Implementation/ASSectionDataSource.swift new file mode 100644 index 0000000..543859c --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Implementation/ASSectionDataSource.swift @@ -0,0 +1,433 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI + +internal protocol ASSectionDataSourceProtocol +{ + var endIndex: Int { get } + func getIndexPaths(withSectionIndex sectionIndex: Int) -> [IndexPath] + func getItemID(for index: Int, withSectionID sectionID: SectionID) -> ASCollectionViewItemUniqueID? + func getUniqueItemIDs(withSectionID sectionID: SectionID) -> [ASCollectionViewItemUniqueID] + func content(forItemID itemID: ASCollectionViewItemUniqueID) -> AnyView + func content(supplementaryID: ASSupplementaryCellID) -> AnyView? + var supplementaryViews: [String: AnyView] { get set } + func getTypeErasedData(for indexPath: IndexPath) -> Any? + func onAppear(_ indexPath: IndexPath) + func onDisappear(_ indexPath: IndexPath) + func prefetch(_ indexPaths: [IndexPath]) + func cancelPrefetch(_ indexPaths: [IndexPath]) + func willAcceptDropItem(from dragItem: UIDragItem) -> Bool + func getDragItem(for indexPath: IndexPath) -> UIDragItem? + func getItemID(for dragItem: UIDragItem, withSectionID sectionID: SectionID) -> ASCollectionViewItemUniqueID? + func supportsMove(_ indexPath: IndexPath) -> Bool + func supportsMove(from sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) -> Bool + func applyMove(from: IndexPath, to: IndexPath) -> Bool + func applyRemove(atOffsets offsets: IndexSet) + func applyInsert(items: [UIDragItem], at index: Int) -> Bool + func supportsDelete(at indexPath: IndexPath) -> Bool + func onDelete(indexPath: IndexPath) -> Bool + func getContextMenu(for indexPath: IndexPath) -> UIContextMenuConfiguration? + func getSelfSizingSettings(context: ASSelfSizingContext) -> ASSelfSizingConfig? + + func isHighlighted(index: Int) -> Bool + func highlightIndex(_ index: Int) + func unhighlightIndex(_ index: Int) + func shouldHighlight(_ indexPath: IndexPath) -> Bool + + var selectedIndicesBinding: Binding>? { get } + func isSelected(index: Int) -> Bool + func shouldSelect(_ indexPath: IndexPath) -> Bool + func shouldDeselect(_ indexPath: IndexPath) -> Bool + func didSelect(_ indexPath: IndexPath) + func updateSelection(with indices: Set) + + var dragEnabled: Bool { get } + var dropEnabled: Bool { get } + var reorderingEnabled: Bool { get } + + mutating func setSelfSizingConfig(config: @escaping SelfSizingConfig) +} + +protocol ASDataSourceConfigurableCell +{ + func setContent(itemID: ASCollectionViewItemUniqueID, content: Content) + var hostingController: ASHostingController { get } + var disableSwiftUIDropInteraction: Bool { get set } + var disableSwiftUIDragInteraction: Bool { get set } +} + +protocol ASDataSourceConfigurableSupplementary +{ + func setContent(supplementaryID: ASSupplementaryCellID, content: Content?) + func setAsEmpty(supplementaryID: ASSupplementaryCellID?) +} + +public enum ASSectionSelectionMode +{ + case none + case highlighting(Binding>) + case selectSingle((Int) -> Void) + case selectMultiple(Binding>) +} + +internal struct ASSectionDataSource: ASSectionDataSourceProtocol where DataID: Hashable, Content: View, Container: View, DataCollection.Index == Int +{ + typealias Data = DataCollection.Element + var data: DataCollection + var dataIDKeyPath: KeyPath + var container: (Content, ASCellContext) -> Container + var content: (DataCollection.Element, ASCellContext) -> Content + + var selectionMode: ASSectionSelectionMode = .none + var shouldAllowHighlight: ((_ index: Int) -> Bool)? + var shouldAllowSelection: ((_ index: Int) -> Bool)? + var shouldAllowDeselection: ((_ index: Int) -> Bool)? + + var onCellEvent: OnCellEvent? + var dragDropConfig: ASDragDropConfig + var shouldAllowSwipeToDelete: ShouldAllowSwipeToDelete? + var onSwipeToDelete: OnSwipeToDelete? + var contextMenuProvider: ContextMenuProvider? + var selfSizingConfig: (SelfSizingConfig)? + + var supplementaryViews: [String: AnyView] = [:] + + var dragEnabled: Bool { dragDropConfig.dragEnabled } + var dropEnabled: Bool { dragDropConfig.dropEnabled } + var reorderingEnabled: Bool { dragDropConfig.reorderingEnabled } + + var endIndex: Int { data.endIndex } + + func getIndex(of itemID: ASCollectionViewItemUniqueID) -> Int? + { + data.firstIndex(where: { $0[keyPath: dataIDKeyPath].hashValue == itemID.itemIDHash }) + } + + func cellContext(for index: Int) -> ASCellContext + { + ASCellContext( + isHighlighted: isHighlighted(index: index), + isSelected: isSelected(index: index), + index: index, + isFirstInSection: index == data.startIndex, + isLastInSection: index == data.endIndex - 1) + } + + func content(forItemID itemID: ASCollectionViewItemUniqueID) -> AnyView + { + guard let content = getContent(forItemID: itemID) + else + { + return AnyView(EmptyView().id(itemID)) + } + return AnyView(content.id(itemID)) + } + + func content(supplementaryID: ASSupplementaryCellID) -> AnyView? + { + guard let content = supplementaryViews[supplementaryID.supplementaryKind] else { return nil } + return AnyView(content.id(supplementaryID)) + } + + func getContent(forItemID itemID: ASCollectionViewItemUniqueID) -> Container? + { + guard let itemIndex = getIndex(of: itemID) else { return nil } + let item = data[itemIndex] + let context = cellContext(for: itemIndex) + let view = content(item, context) + return container(view, context) + } + + func getTypeErasedData(for indexPath: IndexPath) -> Any? + { + data[safe: indexPath.item] + } + + func getIndexPaths(withSectionIndex sectionIndex: Int) -> [IndexPath] + { + data.indices.map { IndexPath(item: $0, section: sectionIndex) } + } + + func getItemID(for index: Int, withSectionID sectionID: SectionID) -> ASCollectionViewItemUniqueID? + { + data[safe: index].map { getItemID(for: $0, withSectionID: sectionID) } + } + + func getItemID(for item: Data, withSectionID sectionID: SectionID) -> ASCollectionViewItemUniqueID + { + ASCollectionViewItemUniqueID(sectionID: sectionID, itemID: item[keyPath: dataIDKeyPath]) + } + + func getUniqueItemIDs(withSectionID sectionID: SectionID) -> [ASCollectionViewItemUniqueID] + { + data.map + { + ASCollectionViewItemUniqueID(sectionID: sectionID, itemID: $0[keyPath: dataIDKeyPath]) + } + } + + func onAppear(_ indexPath: IndexPath) + { + guard let item = data[safe: indexPath.item] else { return } + onCellEvent?(.onAppear(item: item)) + } + + func onDisappear(_ indexPath: IndexPath) + { + guard let item = data[safe: indexPath.item] else { return } + onCellEvent?(.onDisappear(item: item)) + } + + func prefetch(_ indexPaths: [IndexPath]) + { + let dataToPrefetch: [Data] = indexPaths.compactMap + { + data[safe: $0.item] + } + onCellEvent?(.prefetchForData(data: dataToPrefetch)) + } + + func cancelPrefetch(_ indexPaths: [IndexPath]) + { + let dataToCancelPrefetch: [Data] = indexPaths.compactMap + { + data[safe: $0.item] + } + onCellEvent?(.cancelPrefetchForData(data: dataToCancelPrefetch)) + } + + func supportsDelete(at indexPath: IndexPath) -> Bool + { + guard onSwipeToDelete != nil else { return false } + return shouldAllowSwipeToDelete?(indexPath.item) ?? true + } + + func onDelete(indexPath: IndexPath) -> Bool + { + guard let item = data[safe: indexPath.item], let onDelete = onSwipeToDelete else { return false } + let didDelete = onDelete(indexPath.item, item) + return didDelete + } + + func getDragItem(for indexPath: IndexPath) -> UIDragItem? + { + guard dragEnabled, + dragDropConfig.canDragItem?(indexPath) ?? true + else { return nil } + guard let item = data[safe: indexPath.item] else { return nil } + + let itemProvider: NSItemProvider = dragDropConfig.dragItemProvider?(item) ?? NSItemProvider() + let dragItem = UIDragItem(itemProvider: itemProvider) + dragItem.localObject = item + return dragItem + } + + func willAcceptDropItem(from dragItem: UIDragItem) -> Bool + { + getDropItem(from: dragItem) != nil + } + + func getDropItem(from dragItem: UIDragItem) -> Data? + { + guard dropEnabled else { return nil } + + let sourceItem = dragItem.localObject as? Data + return dragDropConfig.dropItemProvider?(sourceItem, dragItem) ?? sourceItem + } + + func getItemID(for dragItem: UIDragItem, withSectionID sectionID: SectionID) -> ASCollectionViewItemUniqueID? + { + guard let item = getDropItem(from: dragItem) else { return nil } + return getItemID(for: item, withSectionID: sectionID) + } + + func supportsMove(_ indexPath: IndexPath) -> Bool + { + dragDropConfig.reorderingEnabled && (dragDropConfig.canDragItem?(indexPath) ?? true) + } + + func supportsMove(from sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) -> Bool + { + dragDropConfig.reorderingEnabled && (dragDropConfig.canMoveItem?(sourceIndexPath, destinationIndexPath) ?? true) + } + + func applyMove(from: IndexPath, to: IndexPath) -> Bool + { + // dragDropConfig.dataBinding?.wrappedValue.move(fromOffsets: [from], toOffset: to) //This is not behaving as expected + // NOTE: Binding seemingly not updated until next runloop. Any change must be done in one move; hence the var array + guard from != to, + dragDropConfig.canMoveItem?(from, to) ?? true + else { return false } + if let binding = dragDropConfig.dataBinding + { + var array = binding.wrappedValue + let value = array.remove(at: from.item) + array.insert(value, at: to.item) + binding.wrappedValue = array + return true + } + else + { + return dragDropConfig.onMoveItem?(from.item, to.item) ?? false + } + } + + func applyRemove(atOffsets offsets: IndexSet) + { + if let binding = dragDropConfig.dataBinding + { + binding.wrappedValue.remove(atOffsets: offsets) + } + else + { + _ = dragDropConfig.onDeleteOrRemoveItems?(offsets) + } + } + + func applyInsert(items: [UIDragItem], at index: Int) -> Bool + { + let actualItems = items.compactMap(getDropItem(from:)) + if let binding = dragDropConfig.dataBinding + { + let allDataIDs = Set(binding.wrappedValue.map { $0[keyPath: dataIDKeyPath] }) + let noDuplicates = actualItems.filter { !allDataIDs.contains($0[keyPath: dataIDKeyPath]) } +#if DEBUG + // Notify during debug build if IDs are not unique (programmer error) + if noDuplicates.count != actualItems.count { print("ASCOLLECTIONVIEW/ASTABLEVIEW: Attempted to insert an item with the same ID as one already in the section. This may cause unexpected behaviour.") } +#endif + binding.wrappedValue.insert(contentsOf: noDuplicates, at: index) + return !noDuplicates.isEmpty + } + else + { + return dragDropConfig.onInsertItems?(index, actualItems) ?? false + } + } + + func getContextMenu(for indexPath: IndexPath) -> UIContextMenuConfiguration? + { + guard + let menuProvider = contextMenuProvider, + let item = data[safe: indexPath.item] + else { return nil } + + return menuProvider(indexPath.item, item) + } + + func getSelfSizingSettings(context: ASSelfSizingContext) -> ASSelfSizingConfig? + { + selfSizingConfig?(context) + } + + var highlightedIndicesBinding: Binding>? + { + switch selectionMode + { + case let .highlighting(highlighted): + return highlighted + default: + return nil + } + } + + var selectedIndicesBinding: Binding>? + { + switch selectionMode + { + case let .selectMultiple(selected): + return selected + default: + return nil + } + } + + func isHighlighted(index: Int) -> Bool + { + (highlightedIndicesBinding?.wrappedValue.contains(index) ?? false) || (selectedIndicesBinding?.wrappedValue.contains(index) ?? false) + } + + func highlightIndex(_ index: Int) + { + switch selectionMode + { + case .none, .selectSingle: return + case .highlighting, .selectMultiple: + DispatchQueue.main.async + { + self.highlightedIndicesBinding?.wrappedValue = highlightedIndicesBinding?.wrappedValue.union([index]) ?? [] + } + } + } + + func unhighlightIndex(_ index: Int) + { + DispatchQueue.main.async + { + self.highlightedIndicesBinding?.wrappedValue = highlightedIndicesBinding?.wrappedValue.subtracting([index]) ?? [] + } + } + + func shouldHighlight(_ indexPath: IndexPath) -> Bool + { + guard data.containsIndex(indexPath.item) else { return false } + switch selectionMode + { + case .none: return false + case .highlighting: + return shouldAllowHighlight?(indexPath.item) ?? true + case .selectSingle, .selectMultiple: + return shouldSelect(indexPath) && (shouldAllowHighlight?(indexPath.item) ?? true) + } + } + + func isSelected(index: Int) -> Bool + { + selectedIndicesBinding?.wrappedValue.contains(index) ?? false + } + + func shouldSelect(_ indexPath: IndexPath) -> Bool + { + guard data.containsIndex(indexPath.item) else { return false } + switch selectionMode + { + case .none, .highlighting: return false + case .selectSingle, .selectMultiple: + return shouldAllowSelection?(indexPath.item) ?? true + } + } + + func shouldDeselect(_ indexPath: IndexPath) -> Bool + { + guard data.containsIndex(indexPath.item) else { return false } + return shouldAllowDeselection?(indexPath.item) ?? true + } + + func didSelect(_ indexPath: IndexPath) + { + switch selectionMode + { + case let .selectSingle(closure): + closure(indexPath.item) + default: break + } + } + + func updateSelection(with indices: Set) + { + DispatchQueue.main.async + { + self.selectedIndicesBinding?.wrappedValue = indices + } + } +} + + +internal extension ASSectionDataSource +{ +// MARK: SELF SIZING MODIFIERS - INTERNAL + mutating func setSelfSizingConfig(config: @escaping SelfSizingConfig) + { + selfSizingConfig = config + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Layout/ASCollectionViewLayout.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Layout/ASCollectionViewLayout.swift new file mode 100644 index 0000000..a14b779 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Layout/ASCollectionViewLayout.swift @@ -0,0 +1,362 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI +import UIKit + +/// If building a custom layout, you can conform to this protocol to tell ASCollectionLayout which dimensions should be self-sized (default is both) +public protocol ASCollectionViewLayoutProtocol +{ + var selfSizingConfig: ASSelfSizingConfig { get } +} + +// MARK: Public Typealias for layout closures + +public typealias CompositionalLayout = ((_ sectionID: SectionID) -> ASCollectionLayoutSection) + +public typealias CompositionalLayoutIgnoringSections = (() -> ASCollectionLayoutSection) + +public struct ASCollectionLayout +{ + enum LayoutType + { + case compositional(CompositionalLayout, interSectionSpacing: CGFloat, scrollDirection: UICollectionView.ScrollDirection) + case custom(UICollectionViewLayout) + } + + var layout: LayoutType + var configureLayout: ((UICollectionViewLayout) -> Void)? + var decorationTypes: [(elementKind: String, ViewType: UICollectionReusableView.Type)] = [] + + public init( + scrollDirection: UICollectionView.ScrollDirection = .vertical, + interSectionSpacing: CGFloat = 10, + layoutPerSection: @escaping CompositionalLayout) + { + layout = .compositional(layoutPerSection, interSectionSpacing: interSectionSpacing, scrollDirection: scrollDirection) + } + + public init( + scrollDirection: UICollectionView.ScrollDirection = .vertical, + interSectionSpacing: CGFloat = 10, + layout: @escaping CompositionalLayoutIgnoringSections) + { + self.layout = .compositional({ _ in layout() }, interSectionSpacing: interSectionSpacing, scrollDirection: scrollDirection) + } + + public init(customLayout: () -> UICollectionViewLayout) + { + layout = .custom(customLayout()) + } + + public init(createCustomLayout: () -> LayoutClass, configureCustomLayout: ((LayoutClass) -> Void)?) + { + layout = .custom(createCustomLayout()) + configureLayout = configureCustomLayout.map + { configuration in + { layoutObject in + guard let layoutObject = layoutObject as? LayoutClass else { return } + configuration(layoutObject) + } + } + } + + public func makeLayout(withCoordinator coordinator: ASCollectionView.Coordinator) -> UICollectionViewLayout + { + switch layout + { + case let .custom(layout): + registerDecorationViews(layout) + configureLayout?(layout) + return layout + case let .compositional(layoutClosure, interSectionSpacing, scrollDirection): + let config = UICollectionViewCompositionalLayoutConfiguration() + config.scrollDirection = scrollDirection + config.interSectionSpacing = interSectionSpacing + + let sectionProvider: UICollectionViewCompositionalLayoutSectionProvider = { [weak coordinator] sectionIndex, layoutEnvironment -> NSCollectionLayoutSection in + guard let sectionID = coordinator?.sectionID(fromSectionIndex: sectionIndex) else { return NSCollectionLayoutSection.emptyPlaceholder(environment: layoutEnvironment, primaryScrollDirection: scrollDirection) } + + return layoutClosure(sectionID).makeLayoutSection(environment: layoutEnvironment, primaryScrollDirection: scrollDirection) + } + + let cvLayout = UICollectionViewCompositionalLayout(sectionProvider: sectionProvider, configuration: config) + registerDecorationViews(cvLayout) + return cvLayout + } + } + + public func configureLayout(layoutObject: UICollectionViewLayout) + { + configureLayout?(layoutObject) + } + + public static var `default`: ASCollectionLayout + { + ASCollectionLayout + { + .list() + } + } + + func registerDecorationViews(_ layout: UICollectionViewLayout) + { + decorationTypes.forEach + { elementKind, ViewType in + layout.register(ViewType, forDecorationViewOfKind: elementKind) + } + } +} + +private extension NSCollectionLayoutSection +{ + static func emptyPlaceholder(environment: NSCollectionLayoutEnvironment, primaryScrollDirection: UICollectionView.ScrollDirection) -> NSCollectionLayoutSection + { + // Used to avoid a crash when UICollectionViewCompositionalLayout requests a NSCollectionLayoutSection for a section that no longer exists + ASCollectionLayoutSection.list().makeLayoutSection(environment: environment, primaryScrollDirection: primaryScrollDirection) + } +} + +public extension ASCollectionLayout +{ + func decorationView(_ viewType: Content.Type, forDecorationViewOfKind elementKind: String) -> Self + { + var layout = self + layout.decorationTypes.append((elementKind, ASCollectionViewDecoration.self)) + return layout + } +} + +public struct ASCollectionLayoutSection +{ + public init(_ sectionLayout: @escaping () -> NSCollectionLayoutSection) + { + layoutSectionClosure = { _, _ in + sectionLayout() + } + } + + public init(_ sectionLayout: @escaping (_ environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection) + { + layoutSectionClosure = { environment, _ in + sectionLayout(environment) + } + } + + init(_ sectionLayout: @escaping (_ environment: NSCollectionLayoutEnvironment, _ primaryScrollDirection: UICollectionView.ScrollDirection) -> NSCollectionLayoutSection) + { + layoutSectionClosure = sectionLayout + } + + var layoutSectionClosure: (_ environment: NSCollectionLayoutEnvironment, _ primaryScrollDirection: UICollectionView.ScrollDirection) -> NSCollectionLayoutSection + + func makeLayoutSection(environment: NSCollectionLayoutEnvironment, primaryScrollDirection: UICollectionView.ScrollDirection) -> NSCollectionLayoutSection + { + layoutSectionClosure(environment, primaryScrollDirection) + } +} + +public extension ASCollectionLayoutSection +{ + static func list( + itemSize: NSCollectionLayoutDimension = .estimated(200), + spacing: CGFloat = 5, + sectionInsets: NSDirectionalEdgeInsets = .zero, + insetSupplementaries: Bool = true, + stickyHeader: Bool = false, + stickyFooter: Bool = false) -> ASCollectionLayoutSection + { + self.init + { (_, primaryScrollDirection) -> NSCollectionLayoutSection in + let itemLayoutSize: NSCollectionLayoutSize + let groupSize: NSCollectionLayoutSize + let supplementarySize: NSCollectionLayoutSize + + switch primaryScrollDirection + { + case .horizontal: + itemLayoutSize = NSCollectionLayoutSize(widthDimension: itemSize, heightDimension: .fractionalHeight(1.0)) + groupSize = NSCollectionLayoutSize(widthDimension: itemSize, heightDimension: .fractionalHeight(1.0)) + supplementarySize = NSCollectionLayoutSize(widthDimension: .estimated(50), heightDimension: .fractionalHeight(1.0)) + case .vertical: fallthrough + @unknown default: + itemLayoutSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: itemSize) + groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: itemSize) + supplementarySize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(50)) + } + let item = NSCollectionLayoutItem(layoutSize: itemLayoutSize) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1) + + let section = NSCollectionLayoutSection(group: group) + + section.contentInsets = sectionInsets + section.interGroupSpacing = spacing + section.visibleItemsInvalidationHandler = { visibleItems, contentOffset, layoutEnvironment in + } // If this isn't defined, there is a bug in UICVCompositional Layout that will fail to update sizes of cells + + let headerSupplementary = NSCollectionLayoutBoundarySupplementaryItem( + layoutSize: supplementarySize, + elementKind: UICollectionView.elementKindSectionHeader, + alignment: (primaryScrollDirection == .vertical) ? .top : .leading) + headerSupplementary.pinToVisibleBounds = stickyHeader + let footerSupplementary = NSCollectionLayoutBoundarySupplementaryItem( + layoutSize: supplementarySize, + elementKind: UICollectionView.elementKindSectionFooter, + alignment: (primaryScrollDirection == .vertical) ? .bottom : .trailing) + footerSupplementary.pinToVisibleBounds = stickyFooter + + section.supplementariesFollowContentInsets = insetSupplementaries + section.boundarySupplementaryItems = [headerSupplementary, footerSupplementary] + return section + } + } +} + +public extension ASCollectionLayoutSection +{ + enum GridLayoutMode + { + case fixedNumberOfColumns(Int) + case adaptive(withMinItemSize: CGFloat) + } + + static func grid( + layoutMode: GridLayoutMode = .fixedNumberOfColumns(2), + itemSpacing: CGFloat = 5, + lineSpacing: CGFloat = 5, + itemSize: NSCollectionLayoutDimension = .estimated(150), + sectionInsets: NSDirectionalEdgeInsets = .init(top: 0, leading: 20, bottom: 0, trailing: 20)) -> ASCollectionLayoutSection + { + self.init + { (layoutEnvironment, primaryScrollDirection) -> NSCollectionLayoutSection in + let count: Int = { + switch layoutMode + { + case let .fixedNumberOfColumns(count): + return count + case let .adaptive(minItemSize): + let containerSize = (primaryScrollDirection == .horizontal) ? layoutEnvironment.container.effectiveContentSize.height : layoutEnvironment.container.effectiveContentSize.width + return max(1, Int(containerSize / minItemSize)) + } + }() + + let itemLayoutSize: NSCollectionLayoutSize + let groupSize: NSCollectionLayoutSize + let supplementarySize: NSCollectionLayoutSize + + switch primaryScrollDirection + { + case .horizontal: + itemLayoutSize = NSCollectionLayoutSize(widthDimension: itemSize, heightDimension: .fractionalHeight(1.0)) + groupSize = NSCollectionLayoutSize(widthDimension: itemSize, heightDimension: .fractionalHeight(1.0)) + supplementarySize = NSCollectionLayoutSize(widthDimension: .estimated(50), heightDimension: .fractionalHeight(1.0)) + case .vertical: fallthrough + @unknown default: + itemLayoutSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: itemSize) + groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: itemSize) + supplementarySize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(50)) + } + let item = NSCollectionLayoutItem(layoutSize: itemLayoutSize) + + let group: NSCollectionLayoutGroup + if primaryScrollDirection == .horizontal + { + group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitem: item, count: count) + } + else + { + group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: count) + } + group.interItemSpacing = .fixed(itemSpacing) + + let section = NSCollectionLayoutSection(group: group) + section.interGroupSpacing = lineSpacing + section.contentInsets = sectionInsets + section.visibleItemsInvalidationHandler = { _, _, _ in } // If this isn't defined, there is a bug in UICVCompositional Layout that will fail to update sizes of cells + + let headerSupplementary = NSCollectionLayoutBoundarySupplementaryItem( + layoutSize: supplementarySize, + elementKind: UICollectionView.elementKindSectionHeader, + alignment: (primaryScrollDirection == .vertical) ? .top : .leading) + let footerSupplementary = NSCollectionLayoutBoundarySupplementaryItem( + layoutSize: supplementarySize, + elementKind: UICollectionView.elementKindSectionFooter, + alignment: (primaryScrollDirection == .vertical) ? .bottom : .trailing) + section.boundarySupplementaryItems = [headerSupplementary, footerSupplementary] + return section + } + } +} + +public extension ASCollectionLayoutSection +{ + static func orthogonalGrid( + gridSize: Int = 2, + itemDimension: NSCollectionLayoutDimension = .fractionalWidth(0.9), + sectionDimension: NSCollectionLayoutDimension = .fractionalHeight(0.8), + orthogonalScrollingBehavior: UICollectionLayoutSectionOrthogonalScrollingBehavior = .groupPagingCentered, + gridSpacing: CGFloat = 5, + itemInsets: NSDirectionalEdgeInsets = .zero, + sectionInsets: NSDirectionalEdgeInsets = .zero) -> ASCollectionLayoutSection + { + self.init + { (_, primaryScrollDirection) -> NSCollectionLayoutSection in + let orthogonalScrollDirection: UICollectionView.ScrollDirection = (primaryScrollDirection == .vertical) ? .horizontal : .vertical + + let itemSize: NSCollectionLayoutSize + let groupSize: NSCollectionLayoutSize + let supplementarySize: NSCollectionLayoutSize + + switch primaryScrollDirection + { + case .horizontal: + supplementarySize = NSCollectionLayoutSize(widthDimension: .estimated(50), heightDimension: .fractionalHeight(1.0)) + case .vertical: fallthrough + @unknown default: + supplementarySize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(50)) + } + switch orthogonalScrollDirection + { + case .horizontal: + itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)) + groupSize = NSCollectionLayoutSize(widthDimension: itemDimension, heightDimension: sectionDimension) + case .vertical: fallthrough + @unknown default: + itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)) + groupSize = NSCollectionLayoutSize(widthDimension: sectionDimension, heightDimension: itemDimension) + } + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + let group: NSCollectionLayoutGroup + if orthogonalScrollDirection == .horizontal + { + group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitem: item, count: gridSize) + } + else + { + group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: gridSize) + } + group.interItemSpacing = .fixed(gridSpacing) + group.contentInsets = itemInsets + + let section = NSCollectionLayoutSection(group: group) + section.orthogonalScrollingBehavior = orthogonalScrollingBehavior + section.contentInsets = sectionInsets + section.visibleItemsInvalidationHandler = { _, _, _ in } // If this isn't defined, there is a bug in UICVCompositional Layout that will fail to update sizes of cells + + let headerSupplementary = NSCollectionLayoutBoundarySupplementaryItem( + layoutSize: supplementarySize, + elementKind: UICollectionView.elementKindSectionHeader, + alignment: (primaryScrollDirection == .vertical) ? .top : .leading) + let footerSupplementary = NSCollectionLayoutBoundarySupplementaryItem( + layoutSize: supplementarySize, + elementKind: UICollectionView.elementKindSectionFooter, + alignment: (primaryScrollDirection == .vertical) ? .bottom : .trailing) + headerSupplementary.contentInsets = itemInsets + footerSupplementary.contentInsets = itemInsets + + section.boundarySupplementaryItems = [headerSupplementary, footerSupplementary] + return section + } + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/ASIndexedDictionary.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/ASIndexedDictionary.swift new file mode 100644 index 0000000..7ea6dac --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/ASIndexedDictionary.swift @@ -0,0 +1,76 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation + +struct ASIndexedDictionary: BidirectionalCollection +{ + private var dictionary: [Key: Int] = [:] + private var array: [Value] = [] + + mutating func append(_ item: (key: Key, value: Value)) + { + if let index = dictionary[item.key] + { + array.remove(at: index) + } + array.append(item.value) + dictionary[item.key] = array.endIndex - 1 + } + + mutating func append(_ items: [(key: Key, value: Value)]) + { + items.forEach { append($0) } + } + + mutating func removeAll() + { + dictionary.removeAll() + array.removeAll() + } + + var startIndex: Int { array.startIndex } + + var endIndex: Int { array.endIndex } + + var lastIndex: Int { Swift.max(startIndex, endIndex - 1) } + + func index(before i: Int) -> Int + { + array.index(before: i) + } + + func index(after i: Int) -> Int + { + array.index(after: i) + } + + subscript(index: Int) -> Value + { + array[index] + } + + subscript(_ key: Key) -> Value? + { + get + { + dictionary[key].map { array[$0] } + } + set + { + guard let newValue = newValue + else + { + _ = dictionary[key].map { array.remove(at: $0) } + return + } + if let index = dictionary[key] + { + array[index] = newValue + } + else + { + append((key, value: newValue)) + } + } + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/ASOptionalSize.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/ASOptionalSize.swift new file mode 100644 index 0000000..ec44992 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/ASOptionalSize.swift @@ -0,0 +1,53 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import CoreGraphics + +struct ASOptionalSize +{ + let width: CGFloat? + let height: CGFloat? + + init(width: CGFloat? = nil, height: CGFloat? = nil) + { + self.width = width + self.height = height + } + + init(_ size: CGSize) + { + width = size.width + height = size.height + } + + static let none = ASOptionalSize() +} + +extension CGFloat +{ + func applyOptionalMinBound(_ optionalMinBound: CGFloat?) -> CGFloat + { + optionalMinBound.map { Swift.max($0, self) } ?? self + } + + func applyOptionalMaxBound(_ optionalMaxBound: CGFloat?) -> CGFloat + { + optionalMaxBound.map { Swift.min($0, self) } ?? self + } +} + +extension CGSize +{ + func applyMinSize(_ minSize: ASOptionalSize) -> CGSize + { + CGSize( + width: width.applyOptionalMinBound(minSize.width), + height: height.applyOptionalMinBound(minSize.height)) + } + + func applyMaxSize(_ maxSize: ASOptionalSize) -> CGSize + { + CGSize( + width: width.applyOptionalMaxBound(maxSize.width), + height: height.applyOptionalMaxBound(maxSize.height)) + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/ASPriorityCache.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/ASPriorityCache.swift new file mode 100644 index 0000000..dcd4904 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/ASPriorityCache.swift @@ -0,0 +1,118 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation + +public final class ASPriorityCache +{ + var maxSize: Int? = 50 + + private var nodes: [Key: Node] = [:] + private var head: Node? + private var tail: Node? + + private class Node + { + var key: Key + var value: Value + var next: Node? + weak var previous: Node? + + init(key: Key, value: Value) + { + self.key = key + self.value = value + } + } + + private func appendNode(_ node: Node) + { + nodes[node.key] = node + if let oldTail = tail + { + oldTail.next = node + node.previous = oldTail + tail = node + } + else + { + head = node + tail = node + } + maxSize.map + { + if nodes.count > $0 + { + removeFirstNode() + } + } + } + + private func removeNode(_ node: Node) + { + node.previous?.next = node.next + node.next?.previous = node.previous + if node === head + { + head = node.next + } + if node === tail + { + tail = node.previous + } + nodes[node.key] = nil + } + + private func removeFirstNode() + { + head.map(removeNode) + } + + private func removeLastNode() + { + tail.map(removeNode) + } + + private func moveNodeToLast(_ node: Node) + { + guard node !== tail else { return } + removeNode(node) + appendNode(node) + } +} + +public extension ASPriorityCache +{ + var first: Value? + { + head?.value + } + + var last: Value? + { + tail?.value + } + + subscript(_ key: Key) -> Value? + { + get { nodes[key]?.value } + set + { + guard let newValue = newValue + else + { + nodes[key].map(removeNode) + return + } + if let node = nodes[key] + { + node.value = newValue + moveNodeToLast(node) + } + else + { + let node = Node(key: key, value: newValue) + appendNode(node) + } + } + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/ASSelfSizingSettings.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/ASSelfSizingSettings.swift new file mode 100644 index 0000000..830372f --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/ASSelfSizingSettings.swift @@ -0,0 +1,31 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation + +public struct ASSelfSizingContext +{ + public enum CellType + { + case content + case supplementary(String) + } + + public let cellType: CellType + public let indexPath: IndexPath +} + +public struct ASSelfSizingConfig +{ + public init(selfSizeHorizontally: Bool? = nil, selfSizeVertically: Bool? = nil, canExceedCollectionWidth: Bool = true, canExceedCollectionHeight: Bool = true) + { + self.selfSizeHorizontally = selfSizeHorizontally + self.selfSizeVertically = selfSizeVertically + self.canExceedCollectionWidth = canExceedCollectionWidth + self.canExceedCollectionHeight = canExceedCollectionHeight + } + + var selfSizeHorizontally: Bool? + var selfSizeVertically: Bool? + var canExceedCollectionWidth: Bool + var canExceedCollectionHeight: Bool +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/Binding+Sequence.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/Binding+Sequence.swift new file mode 100644 index 0000000..30e07a2 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/Binding+Sequence.swift @@ -0,0 +1,16 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI + +public extension Binding where Value == [Int: Set] +{ + subscript(index: Int) -> Binding> + { + Binding>(get: { + self.wrappedValue[index] ?? [] + }, set: { + self.wrappedValue[index] = $0 + }) + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/GlobalConvenienceFunctions.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/GlobalConvenienceFunctions.swift new file mode 100644 index 0000000..5fa1120 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/GlobalConvenienceFunctions.swift @@ -0,0 +1,11 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation + +@discardableResult +func assignIfChanged(_ object: Object, _ keyPath: ReferenceWritableKeyPath, newValue: T) -> Bool +{ + guard newValue != object[keyPath: keyPath] else { return false } + object[keyPath: keyPath] = newValue + return true +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/RandomAccessCollection+Safe.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/RandomAccessCollection+Safe.swift new file mode 100644 index 0000000..c7bf086 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/RandomAccessCollection+Safe.swift @@ -0,0 +1,17 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation + +extension RandomAccessCollection +{ + public func containsIndex(_ index: Index) -> Bool + { + indices.contains(index) + } + + subscript(safe index: Index) -> Element? + { + guard containsIndex(index) else { return nil } + return self[index] + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/ShrinkToFitWrapper.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/ShrinkToFitWrapper.swift new file mode 100644 index 0000000..8296064 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/Support/ShrinkToFitWrapper.swift @@ -0,0 +1,76 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import SwiftUI + +protocol ContentSize +{ + var contentSizeTracker: ContentSizeTracker? { get set } +} + +public enum ShrinkDimension +{ + case horizontal + case vertical + + var shrinkVertical: Bool + { + self == .vertical + } + + var shrinkHorizontal: Bool + { + self == .horizontal + } +} + +struct SelfSizingWrapper: View +{ + @State var contentSizeTracker = ContentSizeTracker() + + var content: Content + var shrinkDirection: ShrinkDimension + var isEnabled: Bool = true + var expandToFitMode: Bool = false + + var modifiedContent: Content + { + var content = self.content + content.contentSizeTracker = contentSizeTracker + return content + } + + var body: some View + { + SubWrapper(contentSizeTracker: contentSizeTracker, content: modifiedContent, shrinkDirection: shrinkDirection, isEnabled: isEnabled, expandToFitMode: expandToFitMode) + } +} + +struct SubWrapper: View +{ + @ObservedObject + var contentSizeTracker: ContentSizeTracker + + var content: Content + var shrinkDirection: ShrinkDimension + var isEnabled: Bool + var expandToFitMode: Bool + + var body: some View + { + content + .frame( + minWidth: isEnabled && expandToFitMode && shrinkDirection.shrinkHorizontal ? contentSizeTracker.contentSize?.width : nil, + idealWidth: isEnabled && shrinkDirection.shrinkHorizontal ? contentSizeTracker.contentSize?.width : nil, + maxWidth: expandToFitMode ? .infinity : (isEnabled && shrinkDirection.shrinkHorizontal ? contentSizeTracker.contentSize?.width : nil), + minHeight: isEnabled && expandToFitMode && shrinkDirection.shrinkVertical ? contentSizeTracker.contentSize?.height : nil, + idealHeight: isEnabled && shrinkDirection.shrinkVertical ? contentSizeTracker.contentSize?.height : nil, + maxHeight: expandToFitMode ? .infinity : (isEnabled && shrinkDirection.shrinkVertical ? contentSizeTracker.contentSize?.height : nil), + alignment: .topLeading) + } +} + +class ContentSizeTracker: ObservableObject +{ + @Published + var contentSize: CGSize? +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/UIKit/AS_UICollectionView.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/UIKit/AS_UICollectionView.swift new file mode 100644 index 0000000..68b356a --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/UIKit/AS_UICollectionView.swift @@ -0,0 +1,99 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import SwiftUI + +public class AS_CollectionViewController: UIViewController +{ + weak var coordinator: ASCollectionViewCoordinator? + { + didSet + { + collectionView.coordinator = coordinator + } + } + + var collectionViewLayout: UICollectionViewLayout + lazy var collectionView: AS_UICollectionView = { + let cv = AS_UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + cv.coordinator = coordinator + return cv + }() + + public init(collectionViewLayout layout: UICollectionViewLayout) + { + collectionViewLayout = layout + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) + { + fatalError("init(coder:) has not been implemented") + } + + override public func loadView() + { + view = collectionView + } + + override public func viewDidLoad() + { + super.viewDidLoad() + view.backgroundColor = .clear + } + + override public func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) + { + // Get current central cell + self.coordinator?.prepareForOrientationChange() + + super.viewWillTransition(to: size, with: coordinator) + // The following is a workaround to fix the interface rotation animation under SwiftUI + view.frame = CGRect(origin: view.frame.origin, size: size) + + coordinator.animate(alongsideTransition: { _ in + self.view.setNeedsLayout() + self.view.layoutIfNeeded() + if + let desiredOffset = self.coordinator?.getContentOffsetForOrientationChange(), + self.collectionView.contentOffset != desiredOffset + { + self.collectionView.contentOffset = desiredOffset + } + }) + { _ in + // Completion + self.coordinator?.completedOrientationChange() + } + } + + override public func viewSafeAreaInsetsDidChange() + { + super.viewSafeAreaInsetsDidChange() + // The following is a workaround to fix the interface rotation animation under SwiftUI + collectionViewLayout.invalidateLayout() + } + + override public func viewDidLayoutSubviews() + { + super.viewDidLayoutSubviews() + coordinator?.didUpdateContentSize(collectionView.contentSize) + } +} + +class AS_UICollectionView: UICollectionView +{ + weak var coordinator: ASCollectionViewCoordinator? + override func didMoveToWindow() + { + if window != nil + { + coordinator?.onMoveToParent() + } + else + { + coordinator?.onMoveFromParent() + } + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/UIKitExtensions/UICollectionView+Convenience.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/UIKitExtensions/UICollectionView+Convenience.swift new file mode 100644 index 0000000..9f07361 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/UIKitExtensions/UICollectionView+Convenience.swift @@ -0,0 +1,81 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import UIKit + +extension UICollectionView +{ + var allSections: Range? + { + let sectionCount = dataSource?.numberOfSections?(in: self) ?? 1 + guard sectionCount > 0 else { return nil } + return (0 ..< sectionCount) + } + + func allIndexPaths(inSection section: Int) -> [IndexPath] + { + guard let itemCount = dataSource?.collectionView(self, numberOfItemsInSection: section), itemCount > 0 else { return [] } + return (0 ..< itemCount).map + { item in + IndexPath(item: item, section: section) + } + } + + func allIndexPaths() -> [IndexPath] + { + guard let allSections = allSections else { return [] } + return allSections.flatMap + { section -> [IndexPath] in + allIndexPaths(inSection: section) + } + } + + func allIndexPaths(after afterIndexPath: IndexPath) -> [IndexPath] + { + guard let sectionCount = dataSource?.numberOfSections?(in: self), sectionCount > 0 else { return [] } + return (afterIndexPath.section ..< sectionCount).flatMap + { section -> [IndexPath] in + guard let itemCount = dataSource?.collectionView(self, numberOfItemsInSection: section), itemCount > 0 else { return [] } + let startIndex: Int + if section == afterIndexPath.section + { + startIndex = afterIndexPath.item + 1 + } + else + { + startIndex = 0 + } + guard startIndex < itemCount else { return [] } + return (startIndex ..< itemCount).map + { item in + IndexPath(item: item, section: section) + } + } + } + + var firstIndexPath: IndexPath? + { + guard + let sectionCount = dataSource?.numberOfSections?(in: self), sectionCount > 0, + let itemCount = dataSource?.collectionView(self, numberOfItemsInSection: 0), itemCount > 0 + else { return nil } + return IndexPath(item: 0, section: 0) + } + + var lastIndexPath: IndexPath? + { + guard + let sectionCount = dataSource?.numberOfSections?(in: self), sectionCount > 0, + let itemCount = dataSource?.collectionView(self, numberOfItemsInSection: sectionCount - 1), itemCount > 0 + else { return nil } + return IndexPath(item: itemCount - 1, section: sectionCount - 1) + } +} + +public enum Boundary: CaseIterable +{ + case left + case right + case top + case bottom +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/UIKitExtensions/UIScrollView+Convenience.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/UIKitExtensions/UIScrollView+Convenience.swift new file mode 100644 index 0000000..93cb54d --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/UIKitExtensions/UIScrollView+Convenience.swift @@ -0,0 +1,20 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import UIKit + +extension UIScrollView +{ + var contentSizePlusInsets: CGSize + { + CGSize( + width: contentSize.width + adjustedContentInset.left + adjustedContentInset.right, + height: contentSize.height + adjustedContentInset.bottom + adjustedContentInset.top) + } + + var maxContentOffset: CGPoint + { + CGPoint( + x: max(0, contentSizePlusInsets.width - bounds.width), + y: max(-adjustedContentInset.top, contentSizePlusInsets.height - bounds.height)) + } +} diff --git a/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/UIKitExtensions/UIView+Convenience.swift b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/UIKitExtensions/UIView+Convenience.swift new file mode 100644 index 0000000..8741a70 --- /dev/null +++ b/PlaygroundBook/Modules/ASCollectionView.playgroundmodule/Sources/UIKitExtensions/UIView+Convenience.swift @@ -0,0 +1,26 @@ +// ASCollectionView. Created by Apptek Studios 2019 + +import Foundation +import UIKit + +extension UIView +{ + func findFirstResponder() -> UIView? + { + if isFirstResponder + { + return self + } + else + { + for subview in subviews + { + if let found = subview.findFirstResponder() + { + return found + } + } + } + return nil + } +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Base/BaseLiveViewController.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Base/BaseLiveViewController.swift new file mode 100644 index 0000000..798a104 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Base/BaseLiveViewController.swift @@ -0,0 +1,60 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit +import PlaygroundSupport + +import SwiftUI + +public class BaseLiveViewController: UIViewController, PlaygroundLiveViewMessageHandler, PlaygroundLiveViewSafeAreaContainer { + + private var rootController: UIViewController + private var adjustsWidthToConnection: Bool + + init(rootController: UIViewController, adjustsWidthToConnection: Bool = false) { + self.rootController = rootController + self.adjustsWidthToConnection = adjustsWidthToConnection + super.init(nibName: nil, bundle: nil) + } + + public override func viewDidLoad() { + super.viewDidLoad() + + addChild(rootController) { + $0.view.translatesAutoresizingMaskIntoConstraints = false + + view.addSubview($0.view) + + NSLayoutConstraint.activate([ + $0.view.topAnchor.constraint(equalTo: view.topAnchor), + $0.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + $0.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + $0.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) + ]) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func liveViewMessageConnectionOpened() { + if adjustsWidthToConnection { + PlaygroundPage.current.wantsFullScreenLiveView = true + } + } + + public func liveViewMessageConnectionClosed() { + if adjustsWidthToConnection { + PlaygroundPage.current.wantsFullScreenLiveView = false + } + } + + public func receive(_ message: PlaygroundValue) { + // Implement this method to receive messages sent from the process running Contents.swift. + // This method is *required* by the PlaygroundLiveViewMessageHandler protocol. + // Use this method to decode any messages sent as PlaygroundValue values and respond accordingly. + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Base/LiveViewSupport.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Base/LiveViewSupport.swift new file mode 100644 index 0000000..f343699 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Base/LiveViewSupport.swift @@ -0,0 +1,53 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit +import SwiftUI +import PlaygroundSupport + +public enum LiveViewChapter: String { + + case introductionToUnicode + case codePointsBlocksAndPlanes + case encodingWithUtf +// case compositing + case emoji + case fancyTexts +// case layingOutWithControlCharacters +// case privateAreas + +} + +public func instantiateLiveViewController(withChapter chapter: LiveViewChapter) -> PlaygroundLiveViewable { + App.setup() + let liveViewController: UIViewController + switch chapter { + case .introductionToUnicode: + let hostingController = UIHostingController( + rootView: IntroductionToUnicodeView() + ) + liveViewController = BaseLiveViewController(rootController: hostingController) + case .codePointsBlocksAndPlanes: + let hostingController = UIHostingController( + rootView: CodePointsBlocksAndPlanesView() + .environmentObject(Store(reduce: chapterTwoReduce)) + ) + liveViewController = BaseLiveViewController(rootController: hostingController, adjustsWidthToConnection: true) + case .encodingWithUtf: + let hostingController = UIHostingController( + rootView: EncodeWithUtfView() + .environmentObject(Store(reduce: chapterThreeReduce)) + ) + liveViewController = BaseLiveViewController(rootController: hostingController, adjustsWidthToConnection: true) + case .emoji: + let hostingController = UIHostingController( + rootView: EmojiView() + .environmentObject(Store(reduce: chapterFourReduce)) + ) + liveViewController = BaseLiveViewController(rootController: hostingController, adjustsWidthToConnection: true) + default: + liveViewController = UIViewController() + } + return liveViewController +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/01-Welcome/Views/IntroductionToUnicodeView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/01-Welcome/Views/IntroductionToUnicodeView.swift new file mode 100644 index 0000000..51b09cf --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/01-Welcome/Views/IntroductionToUnicodeView.swift @@ -0,0 +1,21 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct IntroductionToUnicodeView: View { + + var body: some View { + Text("Hello, World!") + } + +} + +struct IntroductionToUnicodeView_Previews: PreviewProvider { + + static var previews: some View { + IntroductionToUnicodeView() + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/States/ChapterTwoAction.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/States/ChapterTwoAction.swift new file mode 100644 index 0000000..7d8c567 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/States/ChapterTwoAction.swift @@ -0,0 +1,17 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation + +enum ChapterTwoAction: ActionType { + + case changePlane(number: Int) + case changeBlock(key: String?) + case selectCharacter(codePoint: UInt32?) + + case handleLink(link: String) + + case setBlockDescription(description: String?) + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/States/ChapterTwoReduce.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/States/ChapterTwoReduce.swift new file mode 100644 index 0000000..e3adedf --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/States/ChapterTwoReduce.swift @@ -0,0 +1,76 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation + +func chapterTwoReduce(state: ChapterTwoState, action: ChapterTwoAction) -> (ChapterTwoState, ChapterTwoAction?) { + var appState = state + + var nextAction: ChapterTwoAction? + + switch action { + case .changePlane(let number): + appState.selectedPlaneNumber = number + appState.filterMode = .ordered + appState.bottomViewModel = nil + appState.showPlaneIntroduction = false + appState.showBlockIntroduction = false + appState.xrayShowsHex = true + if !dataProvider.common.planes[number].blocks.isEmpty { + nextAction = .changeBlock(key: dataProvider.common.planes[number].blocks[0]) + } else { + nextAction = .changeBlock(key: nil) + } + + case .changeBlock(let key): + appState.selectedBlockKey = key + appState.blockDescription = "" + if let key = key { + let predicate = NSPredicate(format: "key == %@", key) + do { + let res = try BlockDescription.findOrFetch(in: App.moc, matching: predicate) + nextAction = .setBlockDescription(description: res?.content) + } catch { + DebugAlert.showOnKeyWindow(message: "Failed to fetch block description with error: \(error)") + } + } else { + appState.blockDescription = nil + } + + case .selectCharacter(let codePoint): + guard let codePoint = codePoint else { + appState.bottomViewModel = nil + break + } + if let bottomViewModel = CharacterBottomViewModel.fromCodepoint(codePoint) { + appState.bottomViewModel = bottomViewModel + } + + case .setBlockDescription(let description): + appState.blockDescription = description + if description == nil { + appState.showBlockIntroduction = false + } + + case .handleLink(let link): + if link.starts(with: "unicode-book://block/") { + let blockKey = link.replacingOccurrences(of: "unicode-book://block/", with: "") + var found = false + for (idx, plane) in dataProvider.common.planes.enumerated() { + if plane.blocks.contains(blockKey) { + found = true + if state.selectedPlaneNumber != idx { + nextAction = .changePlane(number: idx) + } + break + } + } + if found { + nextAction = .changeBlock(key: blockKey) + } + } + } + + return (appState, nextAction) +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/States/ChapterTwoState.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/States/ChapterTwoState.swift new file mode 100644 index 0000000..ce4c564 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/States/ChapterTwoState.swift @@ -0,0 +1,32 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation + +struct ChapterTwoState: StateType { + + typealias Action = ChapterTwoAction + + enum FilterMode: Int { + case ordered + case categorized + } + + var selectedPlaneNumber: Int = 0 + var selectedBlockKey: String? + var bottomViewModel: CharacterBottomViewModel? = nil + + var blockDescription: String? = nil + + var xrayShowsHex = true + var showPlaneIntroduction = false + var showBlockIntroduction = false + + var filterMode: FilterMode = .ordered + + static var initialActions: [ChapterTwoAction] { + return [.changePlane(number: 0)] + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/Info/BlockInfo/BlockInfoView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/Info/BlockInfo/BlockInfoView.swift new file mode 100644 index 0000000..f7b707f --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/Info/BlockInfo/BlockInfoView.swift @@ -0,0 +1,81 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct BlockInfoView: View { + + @EnvironmentObject var store: Store + + var body: some View { + let selectedBlock: Block? = { + if let key = store.state.selectedBlockKey { + return dataProvider.common.blocks[key] + } else { + return nil + } + }() + + VStack(alignment: .leading) { + HStack { + Text(selectedBlock?.name ?? "") + .font(.system(size: 16, weight: .bold)) + .foregroundColor(Colors.text) + Spacer() + if store.state.blockDescription != nil { + Button(action: { + store.state.showBlockIntroduction.toggle() + }) { + Image(systemName: store.state.showBlockIntroduction ? "list.bullet.below.rectangle" : "info.circle") + } + } + } + + if store.state.showBlockIntroduction { + let description = store.state.blockDescription ?? "No description for this block." + RichTextDescriptionView(descriptionText: description) { link in + store.dispatch(.handleLink(link: link.absoluteString)) + } + } else { + ScrollView { + VStack(alignment: .leading, spacing: 4) { + if let selectedBlock = selectedBlock { + Text("Range: \(selectedBlock.formattedRange )") + Text("Quantity of characters: \(selectedBlock.characterCount)") + + if let blockType = selectedBlock.type, !blockType.isEmpty { + Text("Type: \(blockType)") + } + + if let languages = selectedBlock.languages, !languages.isEmpty { + Text("Languages: \(languages)") + } + + if let key = store.state.selectedBlockKey { + MapView( + countries: dataProvider.common.blocks[key]?.countries ?? [] + ) + .padding(.top, 8) + } + } + } + .font(.system(size: 14, weight: .semibold)) + .foregroundColor(Colors.text) + } + } + } + } + +} + +struct BlockInfoView_Previews: PreviewProvider { + + static var previews: some View { + BlockInfoView() + .environmentObject(Store(reduce: chapterTwoReduce)) + .previewLayout(.fixed(width: 300, height: 300)) + .colorScheme(.light) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/Info/BlocksList/BlocksListItemView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/Info/BlocksList/BlocksListItemView.swift new file mode 100644 index 0000000..06c7d3d --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/Info/BlocksList/BlocksListItemView.swift @@ -0,0 +1,100 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct BlocksListItemView: View { + + struct Item: Identifiable { + var key: String + var text: String + var color: Color + var selected: Bool = false + + var id: String { + return key + } + } + + var item: Item + + var subItems: [Item] = [] + + var itemTap: ((Item) -> Void)? + var subItemTap: ((Item) -> Void)? + + var body: some View { + VStack(alignment: .leading, spacing: 4) { + HStack(spacing: 8) { + Circle() + .frame(width: 16, height: 16) + .foregroundColor(item.color) + Text(item.text) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(Colors.text) + .lineLimit(1) + Spacer() + } + if !subItems.isEmpty { + VStack(alignment: .leading, spacing: 0) { + ForEach(subItems) { subItem in + HStack(alignment: .center, spacing: 12) { + Circle() + .frame(width: 8, height: 8) + .foregroundColor(subItem.color) + Text(subItem.text) + .font(.system(size: 14)) + .foregroundColor(subItem.selected ? Colors.selectionBackground : Colors.text) + .lineLimit(1) + .frame(maxWidth: .infinity, alignment: .leading) + .padding([.bottom], 2) + // to make extra area clickable + .background(Colors.cardBackground) + } + .padding(.leading, 4) + .onTapGesture { + subItemTap?(subItem) + } + } + } + } + } + .frame(maxWidth: .infinity) + .padding(.all, 12) + .background(Colors.listBackground) + .ifTrue(item.selected) { + $0.overlay( + RoundedRectangle(cornerRadius: 6) + .strokeBorder(Colors.selectionBackground, lineWidth: 4) + ) + } + .cornerRadius(6) + .shadow(radius: 3, x: 0, y: 2) + .onTapGesture { + itemTap?(item) + } + } + +} + +struct BlocksListItemView_Previews: PreviewProvider { + + static var previews: some View { + Group { + BlocksListItemView( + item: BlocksListItemView.Item(key: "baisc-latin", text: "Basic Latin", color: .blue, selected: true) + ) + BlocksListItemView( + item: BlocksListItemView.Item(key: "baisc-latin", text: "Basic Latin", color: .blue, selected: true), + subItems: [ + BlocksListItemView.Item(key: "sub-1", text: "Sub1", color: .blue), + BlocksListItemView.Item(key: "sub-1", text: "Sub2", color: .blue) + ] + ) + } + .previewLayout(.fixed(width: 300, height: 300)) + .colorScheme(.light) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/Info/BlocksList/BlocksListView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/Info/BlocksList/BlocksListView.swift new file mode 100644 index 0000000..bcfccd1 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/Info/BlocksList/BlocksListView.swift @@ -0,0 +1,133 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct BlocksListView: View { + + @EnvironmentObject var store: Store + + static var edgeShadowPadingH: CGFloat = 16 + static var edgeShadowPadingV: CGFloat = 4 + + var body: some View { + let selectedPlane = dataProvider.common.planes[store.state.selectedPlaneNumber] + let blocks = selectedPlane.blocks.compactMap({ + dataProvider.common.blocks[$0] + }) + + VStack(spacing: 16) { + Picker("", selection: $store.state.filterMode) { + Text("Ordered") + .tag(ChapterTwoState.FilterMode.ordered) + Text("Categorized") + .tag(ChapterTwoState.FilterMode.categorized) + } + .pickerStyle(SegmentedPickerStyle()) + + ScrollView(.vertical) { + ScrollViewReader { proxy in + switch store.state.filterMode { + case .ordered: + VStack { + ForEach(blocks) { block in + BlocksListItemView( + item: BlocksListItemView.Item( + key: block.key, + text: block.name, + color: Color(block.color), + selected: store.state.selectedBlockKey == block.key + ), + itemTap: { item in + store.dispatch(.changeBlock(key: item.key)) + } + ) + } + } + .onAppear() { + if let key = store.state.selectedBlockKey { + proxy.scrollTo(key, anchor: .none) + } + } + .onChange(of: store.state.selectedBlockKey) { val in + if let key = val { + proxy.scrollTo(key, anchor: .none) + } + } + case .categorized: + let selectedCategoryKey: String? = { + if let key = store.state.selectedBlockKey { + return dataProvider.common.blocks[key]?.categoryKey + } else { + return nil + } + }() + VStack { + ForEach(dataProvider.chapterTwo.categories) { category in + let subItems = blocks + .filter({ $0.categoryKey == category.key }) + .map { + BlocksListItemView.Item( + key: $0.key, + text: $0.name, + color: Color($0.color), + selected: $0.key == store.state.selectedBlockKey + ) + } + + if !subItems.isEmpty { + let isSelected = selectedCategoryKey == category.key + BlocksListItemView( + item: BlocksListItemView.Item( + key: category.key, + text: category.name, + color: Color(category.color), + selected: isSelected + ), + subItems: subItems, + itemTap: { _ in + if !subItems.isEmpty { + store.dispatch(.changeBlock(key: subItems[0].key)) + } + }, + subItemTap: { item in + store.dispatch(.changeBlock(key: item.key)) + } + ) + } + } + } + .onAppear() { + if let key = store.state.selectedBlockKey { + proxy.scrollTo(key, anchor: .none) + } + } + .onChange(of: store.state.selectedBlockKey) { val in + if let key = val { + proxy.scrollTo(key, anchor: .none) + } + } + } + } + .frame(maxWidth: .infinity) + .padding([.leading, .trailing], Self.edgeShadowPadingH) + .padding([.top, .bottom], Self.edgeShadowPadingV) + } + .padding([.leading, .trailing], -Self.edgeShadowPadingH) + .padding([.top, .bottom], -Self.edgeShadowPadingV) + } + } + +} + +struct BlocksListView_Previews: PreviewProvider { + + static var previews: some View { + BlocksListView() + .previewLayout(.fixed(width: 300, height: 400)) + .colorScheme(.light) + .environmentObject(Store(reduce: chapterTwoReduce)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/Info/PlaneIntroductionView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/Info/PlaneIntroductionView.swift new file mode 100644 index 0000000..41e5c02 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/Info/PlaneIntroductionView.swift @@ -0,0 +1,53 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct PlaneIntroductionView: View { + + @EnvironmentObject var store: Store + + var body: some View { + VStack(spacing: 12) { + GeometryReader { geometry in + let blocksListView = BlocksListView() + .cardStyle() + + let blockInfoView = BlockInfoView() + .cardStyle() + + if geometry.size.width > 480 { + HStack(spacing: 12) { + blocksListView + .frame(width: 240) + blockInfoView + } + } else { + VStack(spacing: 12) { + blocksListView + blockInfoView + } + } + } + } + } + +} + +struct PlanesBottomIntroduction_Previews: PreviewProvider { + + static var previews: some View { + Group { + PlaneIntroductionView() + .background(Color.white) + .previewLayout(.fixed(width: 600, height: 300)) + PlaneIntroductionView() + .background(Color.white) + .previewLayout(.fixed(width: 200, height: 300)) + } + .environmentObject(Store(reduce: chapterTwoReduce)) + .preferredColorScheme(.light) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/PlaneBottomView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/PlaneBottomView.swift new file mode 100644 index 0000000..3ff4038 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/PlaneBottomView.swift @@ -0,0 +1,41 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct PlaneBottomView: View { + + @EnvironmentObject var store: Store + + var body: some View { + Group { + GeometryReader { container in + let planeXrayView = PlaneIntroductionXrayView() + let planeIntroductionView = PlaneIntroductionView() + + if container.size.width >= container.size.height + 240 { + HStack { + planeXrayView + planeIntroductionView + } + } else { + planeIntroductionView + } + } + } + .padding(12) + .background(Colors.modalBackground) + } + +} + +struct PlaneBottom_Previews: PreviewProvider { + + static var previews: some View { + PlaneBottomView() + .environmentObject(Store(reduce: chapterTwoReduce)) + .previewLayout(.fixed(width: 1024, height: 400)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/Xray/PlaneIntroductionXrayView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/Xray/PlaneIntroductionXrayView.swift new file mode 100644 index 0000000..3830ad4 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/BottomView/Xray/PlaneIntroductionXrayView.swift @@ -0,0 +1,79 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct PlaneIntroductionXrayView: View { + + @EnvironmentObject var store: Store + + var body: some View { + let selectedPlane = dataProvider.common.planes[store.state.selectedPlaneNumber] + + VStack { + VStack(alignment: .leading, spacing: 8) { + ZStack { + PlaneXrayView( + plane: selectedPlane, + showsHex: store.state.xrayShowsHex, + selectedBlockKey: store.state.selectedBlockKey + ) + + if store.state.showPlaneIntroduction { + Color.black + .opacity(0.65) + .cornerRadius(4) + .overlay( + ScrollView(.vertical) { + Text(selectedPlane.introduction) + .font(.system(size: 14, weight: .semibold)) + .foregroundColor(.white) + .padding(8) + } + .frame(maxWidth: .infinity) + ) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .transition(.opacity) + } + } + .aspectRatio(1, contentMode: .fit) + .padding(4) + .background(Colors.modalBackground) + .cornerRadius(4) + } + .cardStyle() + + HStack { + Button(action: { + store.state.xrayShowsHex.toggle() + }) { + Image(systemName: "00.square") + } + + Spacer() + + Button(action: { + withAnimation(Animation.easeInOut(duration: 0.1)) { + store.state.showPlaneIntroduction.toggle() + } + }) { + Image(systemName: "info.circle") + } + } + } + .fixedSize(horizontal: true, vertical: false) + } + +} + +struct PlaneIntroductionXrayView_Previews: PreviewProvider { + + static var previews: some View { + PlaneIntroductionXrayView() + .colorScheme(.light) + .environmentObject(Store(reduce: chapterTwoReduce)) + .previewLayout(.fixed(width: 400, height: 300)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/Characters/CharactersCollectionHeaderView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/Characters/CharactersCollectionHeaderView.swift new file mode 100644 index 0000000..a6572bb --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/Characters/CharactersCollectionHeaderView.swift @@ -0,0 +1,26 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit + +final class CharactersCollectionHeaderView: UICollectionReusableView { + + @IBOutlet weak var bulletView: UIView! + @IBOutlet weak var titleLabel: UILabel! + + override func awakeFromNib() { + super.awakeFromNib() + + bulletView.clipsToBounds = true + bulletView.layer.cornerRadius = bulletView.bounds.width / 2 + + titleLabel.textColor = UIColor(Colors.text) + backgroundColor = UIColor(Colors.pageBackground) + } + +} + +extension CharactersCollectionHeaderView: NibInstantiable, ReusableIdentifiable { + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/Characters/CharactersCollectionViewCell.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/Characters/CharactersCollectionViewCell.swift new file mode 100644 index 0000000..50d273c --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/Characters/CharactersCollectionViewCell.swift @@ -0,0 +1,90 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit +import CoreText + +final class CharactersCollectionViewCell: UICollectionViewCell { + + @IBOutlet private weak var characterLabel: UILabel! + @IBOutlet private weak var codePointLabel: UILabel! + + @IBOutlet private weak var codePointLabelBottomConst: NSLayoutConstraint! + + override var isSelected: Bool { + didSet { + if isSelected { + contentView.backgroundColor = UIColor(Colors.modalBackground) + } else { + contentView.backgroundColor = UIColor(Colors.cardBackground) + } + } + } + + var codePoint: UInt32 = 0 { + didSet { + let isReserved = (UnicodeScalar(codePoint)?.properties.isReserverdOrNonCharacter ?? true) + characterLabel.text = String.character(fromCodepoint: codePoint) + codePointLabel.text = String.uPlusRepresentation(fromCodepoint: codePoint) + contentView.alpha = isReserved ? 0.5 : 1.0 + } + } + + override func awakeFromNib() { + super.awakeFromNib() + + contentView.backgroundColor = UIColor(Colors.cardBackground) + + characterLabel.textColor = UIColor(Colors.text) + codePointLabel.textColor = UIColor(Colors.secondaryText) + + clipsToBounds = false + contentView.clipsToBounds = true + + layer.shadowColor = UIColor.black.cgColor + layer.shadowRadius = 6 + layer.shadowOpacity = 0.075 + layer.shadowOffset = CGSize(width: 0, height: 4) + } + + override func layoutSubviews() { + super.layoutSubviews() + + let characterFontSize: CGFloat + let codePointFontSize: CGFloat + let codePointLabelBottom: CGFloat + + if bounds.width > 100 { + characterFontSize = 48 + codePointFontSize = 16 + codePointLabelBottom = 8 + } else if bounds.width > 70 { + characterFontSize = 24 + codePointFontSize = 14 + codePointLabelBottom = 4 + } else if bounds.width > 40 { + characterFontSize = 18 + codePointFontSize = 12 + codePointLabelBottom = 2 + } else { + characterFontSize = 12 + codePointFontSize = 6 + codePointLabelBottom = 1 + } + + let cornerRadius: CGFloat = bounds.width * 0.25 + + contentView.layer.cornerRadius = cornerRadius + layer.cornerRadius = cornerRadius + + characterLabel.font = UIFont.cachedbookFont(ofSize: characterFontSize) + codePointLabel.font = UIFont.systemFont(ofSize: codePointFontSize) + codePointLabelBottomConst.constant = codePointLabelBottom + } + +} + +extension CharactersCollectionViewCell: NibInstantiable, ReusableIdentifiable { + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/Characters/CharactersCollectionViewController.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/Characters/CharactersCollectionViewController.swift new file mode 100644 index 0000000..aa8242c --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/Characters/CharactersCollectionViewController.swift @@ -0,0 +1,332 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit +import SwiftUI + +final class CharactersCollectionViewController: UIViewController { + + let collectionView: UICollectionView + + var plane: Plane? = nil { + didSet { + if oldValue?.number != plane?.number { + collectionView.reloadData() + } + } + } + + var selectedCodePoint: UInt32? = nil { + didSet { + if oldValue != selectedCodePoint { + selectItem(forCodePoint: selectedCodePoint) + } + } + } + + var _currentBlockKey: String? = nil + + var blockChangeCallback: ((String) -> Void)? + var selectionChangeCallback: ((UInt32?) -> Void)? + + init() { + let layout = UICollectionViewFlowLayout() + layout.sectionHeadersPinToVisibleBounds = true + collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init?(coder: NSCoder) not implemented") + } + + override func viewDidLoad() { + collectionView.register( + CharactersCollectionViewCell.nib, + forCellWithReuseIdentifier: CharactersCollectionViewCell.reuseIdentifier + ) + collectionView.register( + CharactersCollectionHeaderView.nib, + forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, + withReuseIdentifier: CharactersCollectionHeaderView.reuseIdentifier + ) + + collectionView.delegate = self + collectionView.dataSource = self + + collectionView.translatesAutoresizingMaskIntoConstraints = false + + view.addSubview(collectionView) + + NSLayoutConstraint.activate([ + collectionView.topAnchor.constraint(equalTo: view.topAnchor), + collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + + collectionView.backgroundColor = UIColor(Colors.pageBackground) + } + + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + collectionView.collectionViewLayout.invalidateLayout() + } + + func updateSelectedBlockKey(_ key: String?, animantd: Bool, completion: (() -> Void)? = nil) { + if _currentBlockKey == key { + return + } + _currentBlockKey = key + if animantd { + if let key = key, let index = plane?.blocks.firstIndex(of: key) { + collectionView.scrollToItem( + at: IndexPath(item: 0, section: index), + at: .centeredVertically, + animated: true + ) + } + } + completion?() + } + + private func updateSelectedBlockForUserInteraction() { + guard + let firstCell = collectionView.visibleCells.first, + let indexPath = collectionView.indexPath(for: firstCell) + else { + return + } + if let blockKey = plane?.blocks[indexPath.section] { + updateSelectedBlockKey(blockKey, animantd: false) { [weak self] in + self?.blockChangeCallback?(blockKey) + } + } + } + + private func selectItem(forCodePoint codePoint: UInt32?) { + if let codePoint = codePoint { + if let plane = plane { + for (idx, key) in plane.blocks.enumerated() { + if + let block = dataProvider.common.blocks[key], + codePoint >= block.codePointStart && codePoint <= block.codePointEnd + { + collectionView.selectItem( + at: IndexPath(item: Int(codePoint - block.codePointStart), section: idx), + animated: true, + scrollPosition: [] + ) + break + } + } + } + } else { + collectionView.deselectAllItems() + } + } + + private func block(forIndexPath indexPath: IndexPath) -> Block? { + if let plane = plane, indexPath.section < plane.blocks.count { + return dataProvider.common.blocks[plane.blocks[indexPath.section]] + } + return nil + } + + private func codepoint(forIndexPath indexPath: IndexPath) -> UInt32? { + if let block = block(forIndexPath: indexPath) { + return block.codePointStart + UInt32(indexPath.row) + } + return nil + } + +} + +extension CharactersCollectionViewController: UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + guard + let plane = plane, + section < plane.blocks.count, + let block = dataProvider.common.blocks[plane.blocks[section]] + else { + return 0 + } + return Int(block.codePointEnd - block.codePointStart) + 1 + } + + func numberOfSections(in collectionView: UICollectionView) -> Int { + plane?.blocks.count ?? 0 + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard + let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: CharactersCollectionViewCell.reuseIdentifier, + for: indexPath + ) as? CharactersCollectionViewCell + else { + fatalError("Failed to deque apporpoate cell") + } + if let block = block(forIndexPath: indexPath) { + cell.codePoint = block.codePointStart + UInt32(indexPath.row) + } + return cell + } + + func collectionView( + _ collectionView: UICollectionView, + viewForSupplementaryElementOfKind kind: String, + at indexPath: IndexPath + ) -> UICollectionReusableView { + guard + let view = collectionView.dequeueReusableSupplementaryView( + ofKind: kind, + withReuseIdentifier: CharactersCollectionHeaderView.reuseIdentifier, + for: indexPath + ) as? CharactersCollectionHeaderView + else { + fatalError("Failed to deque apporpoate header") + } + if let block = block(forIndexPath: indexPath) { + view.bulletView.backgroundColor = block.color + view.titleLabel.text = block.name + } + return view + } + +} + +extension CharactersCollectionViewController: UICollectionViewDelegateFlowLayout { + + func collectionView( + _ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + sizeForItemAt indexPath: IndexPath + ) -> CGSize { + let size = calcProposedCellSize() + return CGSize(width: size, height: size) + } + + func collectionView( + _ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + minimumLineSpacingForSectionAt section: Int + ) -> CGFloat { + return calcMimimumSpacing() + } + + func collectionView( + _ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + minimumInteritemSpacingForSectionAt section: Int + ) -> CGFloat { + return calcMimimumSpacing() + } + + private func calcProposedCellSize() -> CGFloat { + let itemsPerRow = 8 + return (collectionView.bounds.width - CGFloat((itemsPerRow + 1) * 20)) / CGFloat(itemsPerRow) + } + + private func calcMimimumSpacing() -> CGFloat { + let proposedCellSize = calcProposedCellSize() + if proposedCellSize < 20 * 4 { + return proposedCellSize * 0.25 + } else { + return 20 + } + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { + return CGSize(width: collectionView.bounds.width, height: 64) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return UIEdgeInsets(top: 0, left: 20, bottom: 20, right: 20) + } + + func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) { + updateSelectedBlockForUserInteraction() + } + +// func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { +// +// } + + func scrollViewDidScrollToTop(_ scrollView: UIScrollView) { + updateSelectedBlockForUserInteraction() + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if let codePoint = codepoint(forIndexPath: indexPath) { + selectionChangeCallback?(codePoint) + } + } + + func collectionView( + _ collectionView: UICollectionView, + contextMenuConfigurationForItemAt indexPath: IndexPath, + point: CGPoint + ) -> UIContextMenuConfiguration? { + if let codepoint = codepoint(forIndexPath: indexPath) { + return CharacterBottomView.contextMenuConfiguration(forCodepoint: codepoint) + } + return nil + } + + func collectionView( + _ collectionView: UICollectionView, + willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, + animator: UIContextMenuInteractionCommitAnimating + ) { + if let codepoint = configuration.identifier as? NSNumber { + selectionChangeCallback?(codepoint.uint32Value) + } + } + +} + +struct CharactersView: UIViewControllerRepresentable { + + var plane: Plane + var selectedBlockKey: String? + var selectedCodePoint: UInt32? + var selectedBlockKeyChaged: ((String) -> Void)? + var selectedCodePointChaged: ((UInt32?) -> Void)? + + class Coornidator { + var parent: CharactersView + + init(_ parent: CharactersView) { + self.parent = parent + } + + func selectedBlockChanged(_ key: String) { + parent.selectedBlockKeyChaged?(key) + } + + func selectedCodePointChanged(_ codePoint: UInt32?) { + parent.selectedCodePointChaged?(codePoint) + } + } + + func makeCoordinator() -> Coornidator { + return Coornidator(self) + } + + func makeUIViewController(context: Context) -> CharactersCollectionViewController { + let controller = CharactersCollectionViewController() + controller.blockChangeCallback = context.coordinator.selectedBlockChanged(_:) + controller.selectionChangeCallback = context.coordinator.selectedCodePointChanged(_:) + return controller + } + + func updateUIViewController(_ uiViewController: CharactersCollectionViewController, context: Context) { + uiViewController.plane = plane + uiViewController.updateSelectedBlockKey(selectedBlockKey, animantd: true) + uiViewController.selectedCodePoint = selectedCodePoint + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/CodePointsBlocksAndPlanesView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/CodePointsBlocksAndPlanesView.swift new file mode 100644 index 0000000..db515d1 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/CodePointsBlocksAndPlanesView.swift @@ -0,0 +1,59 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct CodePointsBlocksAndPlanesView: View { + + @EnvironmentObject var store: Store + + var body: some View { + let selectedPlane = dataProvider.common.planes[store.state.selectedPlaneNumber] + + NavigationView { + PlaneListView() + .navigationBarTitle("Planes", displayMode: .inline) + + GeometryReader { container in + VStack(spacing: 0) { + CharactersView( + plane: selectedPlane, + selectedBlockKey: store.state.selectedBlockKey, + selectedCodePoint: store.state.bottomViewModel?.codePoint, + selectedBlockKeyChaged: { key in + store.dispatch(.changeBlock(key: key)) + }, + selectedCodePointChaged: { codePoint in + store.dispatch(.selectCharacter(codePoint: codePoint)) + } + ) + Group { + if let bottomViewModel = store.state.bottomViewModel { + CharacterBottomView(bottomViewModel: bottomViewModel) { + store.dispatch(.selectCharacter(codePoint: nil)) + } + } else { + PlaneBottomView() + } + } + .frame(height: container.size.height * 0.6) + } + } + .navigationBarTitle(selectedPlane.fullName, displayMode: .inline) + } + .navigationViewStyle(DefaultNavigationViewStyle()) + } + +} + +struct CodePointsBlocksAndPlanesView_Previews: PreviewProvider { + + static var previews: some View { + CodePointsBlocksAndPlanesView() + .previewLayout(.fixed(width: 400, height: 800)) + CodePointsBlocksAndPlanesView() + .previewLayout(.fixed(width: 800, height: 400)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/Planes/PlaneListItemView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/Planes/PlaneListItemView.swift new file mode 100644 index 0000000..e55784d --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/Planes/PlaneListItemView.swift @@ -0,0 +1,63 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct PlaneListItemView: View { + + var plane: Plane + var isSelected: Bool + + var body: some View { + ZStack(alignment: .trailing) { + PlaneXrayView(plane: plane, showsHex: false, selectedBlockKey: nil) + + Rectangle() + .foregroundColor(.clear) + .background( + LinearGradient( + gradient: Gradient( + colors: [ + Color.black + .opacity(0.5), + Color.clear + ] + ), + startPoint: .bottom , + endPoint: UnitPoint(x: 0.5, y: 0.5) + ) + ) + + VStack(alignment: .trailing) { + Spacer() + Text(plane.subIntro) + .font(.system(size: 14, weight: .semibold)) + Text(plane.fullName) + .font(.system(size: 18, weight: .bold)) + } + .foregroundColor(Color.white) + .multilineTextAlignment(.trailing) + .padding(8) + } + .ifTrue(isSelected) { + $0.border(Colors.selectionBackground, width: 4) + } + .aspectRatio(1, contentMode: .fit) + .padding(4) + .background(Colors.modalLevel1Background) + .cornerRadius(4) + .padding([.leading, .trailing], 8) + } + +} + +struct PlaneListItemView_Previews: PreviewProvider { + + static var previews: some View { + PlaneListItemView(plane: dataProvider.common.planes[0], isSelected: true) + .previewLayout(.fixed(width: 400, height: 200)) + .colorScheme(.light) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/Planes/PlaneListView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/Planes/PlaneListView.swift new file mode 100644 index 0000000..adb681c --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/02-CodePointsBlocksAndPlanes/Views/Planes/PlaneListView.swift @@ -0,0 +1,37 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct PlaneListView: View { + + @EnvironmentObject var store: Store + + var body: some View { + let planes = dataProvider.common.planes + ScrollView { + LazyVStack(alignment: .center, spacing: 8) { + ForEach(planes) { plane in + PlaneListItemView(plane: plane, isSelected: store.state.selectedPlaneNumber == plane.number) + .onTapGesture { + store.dispatch(.changePlane(number: plane.number)) + } + } + } + .background(Colors.pageBackground) + } + } + +} + +struct PlaneListView_Previews: PreviewProvider { + + static var previews: some View { + PlaneListView() + .environmentObject(Store(reduce: chapterTwoReduce)) + .previewLayout(.fixed(width: 400, height: 320)) + .colorScheme(.light) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/States/ChapterThreeState.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/States/ChapterThreeState.swift new file mode 100644 index 0000000..e4c01b3 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/States/ChapterThreeState.swift @@ -0,0 +1,44 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation + +enum ChapterThreeAction: ActionType { + + case sampleTextSelected(_: String) + +} + +struct ChapterThreeState: StateType { + + typealias Action = ChapterThreeAction + + enum UTF8ViewMode: String, CaseIterable { + case hex = "Hex" + case bin = "Bin" + } + + var encodedText: String = "Almost before we knew it, we had left the ground." + var selectedCodePoint: UInt32? = nil + var utf8ViewMode: UTF8ViewMode = .hex + var textSampleMode: TextSampleMode = .sentence + +} + +func chapterThreeReduce(state: ChapterThreeState, action: ChapterThreeState.Action) -> (ChapterThreeState, ChapterThreeState.Action?) { + var appState = state + +// var nextAction: ChapterThreeState.Action? = nil + + switch action { + case let .sampleTextSelected(text): + if appState.textSampleMode == .alphabet { + appState.encodedText = text.replacingOccurrences(of: " ", with: "") + } else { + appState.encodedText = text + } + } + + return (appState, nil) +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/EncodeWithUtfView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/EncodeWithUtfView.swift new file mode 100644 index 0000000..4c25dd3 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/EncodeWithUtfView.swift @@ -0,0 +1,48 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct EncodeWithUtfView: View { + + @State private var bottomViewHeight: CGFloat = 0 + + var body: some View { + NavigationView { + GeometryReader { container in + VStack(spacing: 0) { + ScrollView { + SliderGroup() + .modifier(ViewSizeKey()) + } + .onPreferenceChange(ViewSizeKey.self) { size in + bottomViewHeight = size.height > container.size.height * 0.6 ? + container.size.height * 0.4 : + container.size.height - size.height + } + TextInputView() + .frame(height: bottomViewHeight) + } + } + .navigationBarTitle("Encoding Text with Unicode", displayMode: .inline) + .background(Colors.pageBackground) + } + .navigationViewStyle(StackNavigationViewStyle()) + } + +} + +struct EncodeWithUtfView_Previews: PreviewProvider { + + static var previews: some View { + Group { + EncodeWithUtfView() + .previewLayout(.fixed(width: 600, height: 600)) + EncodeWithUtfView() + .previewLayout(.fixed(width: 600, height: 1000)) + } + .colorScheme(.light) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/SliderGroup/Slider/CodeSlider.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/SliderGroup/Slider/CodeSlider.swift new file mode 100644 index 0000000..9ca572d --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/SliderGroup/Slider/CodeSlider.swift @@ -0,0 +1,164 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct SliderCellGenerators { + + typealias Generator = (String) -> (String, [SliderCell.Content]) + + static let character: Generator = { text in + let subtitle = "\(text.count) characters" + let content = text.map { character -> SliderCell.Content in + .single(String(character)) + } + return (subtitle, content) + } + + static let utf8Hex: Generator = { text in + let byteCount = text.reduce(0) { (res, character) -> Int in + return res + character.utf8.count + } + let content = text.map { character -> SliderCell.Content in + let content = character.unicodeScalars.map { scalar -> String in + return scalar.utf8 + .map({ "\($0, radix: .hex, toWidth: 2)" }) + .joined(separator: " ") + } + return .grouped(content) + } + return ("\(byteCount) bytes", content) + } + + static let utf8Bin: Generator = { text in + let byteCount = text.reduce(0) { (res, character) -> Int in + return res + character.utf8.count + } + let content = text.map { character -> SliderCell.Content in + let content = character.unicodeScalars.map { scalar -> String in + return scalar.utf8 + .map({ "\($0, radix: .binary, toWidth: 8)" }) + .joined(separator: " ") + } + return .grouped(content) + } + return ("\(byteCount) bytes", content) + } + + static let utf16: Generator = { text in + let byteCount = text.reduce(0) { (res, character) -> Int in + return res + character.utf16.count * 2 + } + let content = text.map { character -> SliderCell.Content in + let content = character.unicodeScalars.map { scalar -> String in + return scalar.utf16 + .map({ "\($0, radix: .hex, toWidth: 4)" }) + .joined(separator: " ") + } + return .grouped(content) + } + return ("\(byteCount) bytes", content) + } + + static let utf32: Generator = { text in + let byteCount = text.reduce(0) { (res, character) -> Int in + return res + character.utf16.count * 4 + } + let content = text.map { character -> SliderCell.Content in + let content = character.unicodeScalars.map { scalar -> String in + return "\(scalar.value, radix: .hex, toWidth: 8)" + } + return .grouped(content) + } + return ("\(byteCount) bytes", content) + } + +} + +struct CodeSlider: View { + + let title: String + let byteCountBadgeColor: Color + let text: String + + let cellGenerator: SliderCellGenerators.Generator + + let rightContent: RightContent + + init( + title: String, + byteCountBadgeColor: Color, + text: String, + cellGenerator: @escaping SliderCellGenerators.Generator, + @ViewBuilder rightContent: () -> RightContent + ) { + self.title = title + self.byteCountBadgeColor = byteCountBadgeColor + self.text = text + self.cellGenerator = cellGenerator + self.rightContent = rightContent() + } + + var body: some View { + let (subtitle, content) = cellGenerator(text) + VStack(alignment: .leading, spacing: 12) { + HStack(spacing: 8) { + Text(title) + .foregroundColor(Colors.text) + .font(.system(size: 16, weight: .semibold)) + Badge(text: subtitle, backgroundColor: byteCountBadgeColor) + Spacer() + rightContent + } + ScrollView(.horizontal, showsIndicators: false) { + LazyHStack { + let characters = content + .enumerated() + .map({ ($0, $1) }) + ForEach(characters, id: \.0) { + SliderCell(content: $0.1) + } + } + .frame(height: 38) + } + .frame(maxWidth: .infinity) + } + .cardStyle(backgroundColor: Colors.cardBackground) + } + +} + +extension CodeSlider where RightContent == EmptyView { + + init( + title: String, + byteCountBadgeColor: Color, + text: String, + cellGenerator: @escaping SliderCellGenerators.Generator + ) { + self.init( + title: title, + byteCountBadgeColor: byteCountBadgeColor, + text: text, + cellGenerator: cellGenerator, + rightContent: { EmptyView() } + ) + } + +} + +struct CodeSlider_Previews: PreviewProvider { + + static var previews: some View { + CodeSlider( + title: "Text", + byteCountBadgeColor: Color.red, + text: "你好世界", + cellGenerator: SliderCellGenerators.utf8Bin + ) + .colorScheme(.light) + .previewLayout(.fixed(width: 400, height: 200)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/SliderGroup/Slider/SliderCell.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/SliderGroup/Slider/SliderCell.swift new file mode 100644 index 0000000..a07867b --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/SliderGroup/Slider/SliderCell.swift @@ -0,0 +1,51 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct SliderCell: View { + + enum Content: Hashable { + case single(_: String) + case grouped(_: [String]) + } + + var content: Content + + var body: some View { + Group { + switch content { + case let .single(text): + Text(text) + .padding(4) + case let .grouped(group): + HStack { + ForEach(group, id: \.self) { text in + Text(text) + .padding(4) + .background(Colors.modalLevel1Background) + .cornerRadius(4) + } + } + } + } + .font(.system(size: 14, weight: .medium)) + .foregroundColor(Colors.text) + .frame(minWidth: 18, minHeight: 18) + .padding(6) + .background(Colors.modalBackground) + .cornerRadius(6) + } + +} + +struct SliderCell_Previews: PreviewProvider { + + static var previews: some View { + SliderCell(content: .grouped(["A B", "C D"])) + .colorScheme(.light) + .previewLayout(.fixed(width: 400, height: 200)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/SliderGroup/SliderGroup.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/SliderGroup/SliderGroup.swift new file mode 100644 index 0000000..b970802 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/SliderGroup/SliderGroup.swift @@ -0,0 +1,64 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct SliderGroup: View { + + @EnvironmentObject var chapterThreeStore: Store + + var body: some View { + VStack(spacing: 8) { + let textContent = chapterThreeStore.state.encodedText + let utf8ViewMode = $chapterThreeStore.state.utf8ViewMode + CodeSlider( + title: "Text to Encode", + byteCountBadgeColor: Color(Colors.common[0]), + text: textContent, + cellGenerator: SliderCellGenerators.character + ) + CodeSlider( + title: "UTF-8", + byteCountBadgeColor: Color(Colors.common[1]), + text: textContent, + cellGenerator: utf8ViewMode.wrappedValue == .hex ? + SliderCellGenerators.utf8Hex : + SliderCellGenerators.utf8Bin + ) { + Picker("View Mode", selection: utf8ViewMode) { + ForEach(ChapterThreeState.UTF8ViewMode.allCases, id: \.self) { + Text($0.rawValue).tag($0) + } + } + .frame(maxWidth: 80) + .pickerStyle(SegmentedPickerStyle()) + } + CodeSlider( + title: "UTF-16", + byteCountBadgeColor: Color(Colors.common[2]), + text: textContent, + cellGenerator: SliderCellGenerators.utf16 + ) + CodeSlider( + title: "UTF-32", + byteCountBadgeColor: Color(Colors.common[3]), + text: textContent, + cellGenerator: SliderCellGenerators.utf32 + ) + } + .padding(8) + } + +} + +struct SliderGroup_Previews: PreviewProvider { + + static var previews: some View { + SliderGroup() + .environmentObject(Store(reduce: chapterThreeReduce)) + .colorScheme(.light) + .previewLayout(.fixed(width: 600, height: 600)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/TextInput/TextInputField.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/TextInput/TextInputField.swift new file mode 100644 index 0000000..e21c1af --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/TextInput/TextInputField.swift @@ -0,0 +1,60 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct TextInputField: View { + + var placeHolder: String + + @Binding var text: String + + var textSampleMode: Binding + + var body: some View { + GeometryReader { proxy in + let textField = + TextField(placeHolder, text: $text) + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(Colors.text) + .cardStyle() + let picker = + Picker("", selection: textSampleMode) { + ForEach(TextSampleMode.allCases, id: \.self) { + Text($0.rawValue).tag($0) + } + } + .pickerStyle(SegmentedPickerStyle()) + + if proxy.size.width > 320 { + HStack { + textField + picker.frame(maxWidth: 320) + } + } else { + VStack { + picker + .frame(maxWidth: .infinity) + textField + } + } + } + .frame(height: 54, alignment: .center) + } + +} + +struct TextInputField_Previews: PreviewProvider { + + static var previews: some View { + TextInputField( + placeHolder: "Placeholder", + text: .constant("A quick brown fox jumps over the lazy dog"), + textSampleMode: .constant(.sentence) + ) + .colorScheme(.light) + .previewLayout(.fixed(width: 400, height: 300)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/TextInput/TextInputOption.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/TextInput/TextInputOption.swift new file mode 100644 index 0000000..2daf3bb --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/TextInput/TextInputOption.swift @@ -0,0 +1,41 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct TextInputOption: View { + + var texts: [TextWithLanguage] + + var selectionHandler: ((String) -> Void)? + + var body: some View { + ScrollView { + VStack(alignment: .leading) { + EnumeratedForEach(texts) { idx, text in + let color = Color(Colors.commonColor(forIndex: idx)) + TextInputOptionItem(text: text, color: color) { text in + selectionHandler?(text) + } + } + } + } + } + +} + +struct TextInputOption_Previews: PreviewProvider { + static var previews: some View { + TextInputOption( + texts: [ + TextWithLanguage(lang: "hello", content: "A quick brown fox jumps over the lazy dog"), + TextWithLanguage(lang: "world", content: "你好世界"), + TextWithLanguage(lang: "hello", content: "12123"), + TextWithLanguage(lang: "hello", content: "😀😃😄") + ] + ) + .colorScheme(.light) + .previewLayout(.fixed(width: 400, height: 300)) + } +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/TextInput/TextInputOptionItem.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/TextInput/TextInputOptionItem.swift new file mode 100644 index 0000000..4de96db --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/TextInput/TextInputOptionItem.swift @@ -0,0 +1,42 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct TextInputOptionItem: View { + + var text: TextWithLanguage + var color: Color + var selectionHandler: ((String) -> Void)? + + var body: some View { + Button { + selectionHandler?(text.content) + } label: { + HStack { + Badge(text: text.lang, backgroundColor: color) + Text(text.content) + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(Colors.text) + Spacer() + } + } + .frame(maxWidth: .infinity) + .cardStyle(backgroundColor: Colors.modalLevel2Background) + } + +} + +struct TextInputOptionItem_Previews: PreviewProvider { + + static var previews: some View { + TextInputOptionItem( + text: TextWithLanguage(lang: "hello", content: "A quick brown fox jumps over the lazy dog"), + color: .red + ) + .colorScheme(.light) + .previewLayout(.fixed(width: 400, height: 300)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/TextInput/TextInputView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/TextInput/TextInputView.swift new file mode 100644 index 0000000..65126dc --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/03-EncodingsWithUtf/Views/TextInput/TextInputView.swift @@ -0,0 +1,41 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct TextInputView: View { + + @EnvironmentObject var store: Store + + var body: some View { + let sampleTexts = dataProvider.chapterThree.sampleText[store.state.textSampleMode] ?? [] + VStack { + TextInputField( + placeHolder: "Type something or select texts below to explore", + text: $store.state.encodedText, + textSampleMode: $store.state.textSampleMode + ) + TextInputOption(texts: sampleTexts) { val in + store.dispatch(.sampleTextSelected(val)) + } + .padding(8) + .background(Colors.modalLevel1Background) + .cornerRadius(8) + } + .padding(12) + .background(Colors.modalBackground) + } + +} + +struct TextInputView_Previews: PreviewProvider { + + static var previews: some View { + TextInputView() + .colorScheme(.light) + .previewLayout(.fixed(width: 400, height: 300)) + .environmentObject(Store(reduce: chapterThreeReduce)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/States/ChapterFourState.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/States/ChapterFourState.swift new file mode 100644 index 0000000..42f70b5 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/States/ChapterFourState.swift @@ -0,0 +1,82 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation + +enum ChapterFourAction: ActionType { + + case setEmojiSequence(_: String) + case inputItemTap(_: String) + case backSpace + +} + +struct ChapterFourState: StateType { + + enum ZwjInputMode { + case sequences + case components + } + + typealias Action = ChapterFourAction + + var inputMode: EmojiInputMode = .zwj + var zwjInputMode: ZwjInputMode = .sequences + + var currentEmojiSequence: String = "" + var shortName: String = "" + var keywords: String = "" + + static var initialActions: [ChapterFourAction] { + return [ + .setEmojiSequence("👩🏼‍💻") + ] + } + +} + +func chapterFourReduce(state: ChapterFourState, action: ChapterFourState.Action) -> (ChapterFourState, ChapterFourState.Action?) { + var appState = state + + var nextAction: ChapterFourState.Action? = nil + + switch action { + case let .setEmojiSequence(sequence): + appState.currentEmojiSequence = sequence + + guard sequence.unicodeScalars.count > 0 else { + appState.shortName = "" + appState.keywords = "" + break + } + + let predicate = NSPredicate(format: "character == %@", sequence.removingVariationSelectors()) + do { + appState.shortName = "No Name For This Emoji" + appState.keywords = "" + + if let res = try CLDRAnnotation.findOrFetch(in: App.moc, matching: predicate) { + appState.shortName = res.shortName.capitalized + appState.keywords = res.keywords + } + } catch { + DebugAlert.showOnKeyWindow(message: "Failed to fetch annotation with error: \(error)") + } + case let .inputItemTap(sequence): + if sequence.isEmojiComponent() { + let newSequence = appState.currentEmojiSequence + sequence.emojiCharactersToOriginal() + nextAction = .setEmojiSequence(newSequence) + } else { + nextAction = .setEmojiSequence(sequence) + } + case .backSpace: + var newSequence = appState.currentEmojiSequence.unicodeScalars + if newSequence.count > 0 { + newSequence.removeLast() + } + nextAction = .setEmojiSequence(String(newSequence)) + } + + return (appState, nextAction) +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/Components/EmojiCell.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/Components/EmojiCell.swift new file mode 100644 index 0000000..85d6b29 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/Components/EmojiCell.swift @@ -0,0 +1,48 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct EmojiCell: View { + + var text: String + var backgroundColor: Color = Colors.modalBackground + + var body: some View { + Text(text.toChapterEmojiCharacters()) + .font(Font(UIFont.cachedChapterEmojiFont(ofSize: 24))) + .foregroundColor(Colors.text) + .frame(minWidth: 36, minHeight: 36) + .background(backgroundColor) + .cornerRadius(6) +// .ifTrue(text.unicodeScalars.count == 1) { view in +// view.contextMenu { +// if +// let scalar = text.unicodeScalars.first, +// let model = CharacterBottomViewModel.fromCodepoint(scalar.value) +// { +// Button(action: { +// +// }) { +// CharacterBottomView( +// bottomViewModel: model, +// closeAction: nil +// ) +// } +// } else { +// EmptyView() +// } +// } +// } + } + +} + +struct EmojiCell_Previews: PreviewProvider { + + static var previews: some View { + EmojiCell(text: "A") + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/Components/EmojiFormulaView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/Components/EmojiFormulaView.swift new file mode 100644 index 0000000..acc5795 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/Components/EmojiFormulaView.swift @@ -0,0 +1,49 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct EmojiFormulaView: View { + + var text: String + + var body: some View { + let scalars = text.reduce([], { $0 + $1.unicodeScalars }) + + Group { + GeometryReader { proxy in + ScrollView(.horizontal) { + HStack(spacing: 2) { + EnumeratedForEach(scalars) { idx, scalar in + EmojiCell(text: String(Character(scalar))) + if idx < scalars.count - 1 { + Text("➕") + .font(.system(size: 24, weight: .semibold)) + } + } + } + .frame(minWidth: proxy.size.width, alignment: .center) + } + } + .frame(height: 36) + } + .padding(8) + .background(Colors.cardBackground) + .cornerRadius(8) + } + +} + +struct EmojiFfomulaView_Previews: PreviewProvider { + + static var previews: some View { + Group { + EmojiFormulaView(text: "👩🏼‍💻") + .previewLayout(.fixed(width: 600, height: 600)) + EmojiFormulaView(text: "👩🏼‍💻") + .previewLayout(.fixed(width: 20, height: 100)) + } + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/Components/EmojiLargeImputView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/Components/EmojiLargeImputView.swift new file mode 100644 index 0000000..d2e4ac4 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/Components/EmojiLargeImputView.swift @@ -0,0 +1,40 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct EmojiLargeImputView: View { + + var text: String + + var body: some View { + Group { + GeometryReader { proxy in + HStack(alignment: .center) { + ScrollView(.horizontal) { + Text(text) + .font(Font(UIFont.cachedChapterEmojiFont(ofSize: 128))) + .foregroundColor(Colors.text) + .frame(minWidth: proxy.size.width, alignment: .center) + } + } + .frame(maxHeight: .infinity) + } + .frame(height: 180) + } + .background(Colors.cardBackground) + .cornerRadius(12) + .padding(8) + } + +} + +struct EmojiLargeImputView_Previews: PreviewProvider { + + static var previews: some View { + EmojiLargeImputView(text: "A") + .previewLayout(.fixed(width: 600, height: 600)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiBottomView/EmojiBottomTitleView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiBottomView/EmojiBottomTitleView.swift new file mode 100644 index 0000000..5afbc2c --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiBottomView/EmojiBottomTitleView.swift @@ -0,0 +1,57 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct EmojiBottomTitleView: View { + + struct SectionConfiguration { + let inputMode: EmojiInputMode + let title: String + let buttonTitle: String + } + + static let sectionConfiguration: [SectionConfiguration] = [ + SectionConfiguration(inputMode: .zwj, title: "Compositing with ZWJ Character", buttonTitle: "\u{200D}"), + SectionConfiguration(inputMode: .modifier, title: "Modifiers", buttonTitle: "🏼"), + SectionConfiguration(inputMode: .flag, title: "Flag Composition", buttonTitle: "🏴"), + SectionConfiguration(inputMode: .keycap, title: "Keycap Sequcences", buttonTitle: "0️⃣"), + SectionConfiguration(inputMode: .variation, title: "Presentations with Variation Selector", buttonTitle: "\u{FE0F}") + ] + + @Binding var inputMode: EmojiInputMode + + var body: some View { + let currentSection = Self.sectionConfiguration.first(where: { $0.inputMode == inputMode }) + HStack { + Text(currentSection?.title ?? "") + .foregroundColor(Colors.text) + .font(.system(size: 14, weight: .semibold)) + .lineLimit(1) + Spacer() + ForEach(Self.sectionConfiguration, id: \.inputMode) { item in + Button(action: { + inputMode = item.inputMode + }) { + Text(item.buttonTitle.toChapterEmojiCharacters()) + .font(Font(UIFont.cachedChapterEmojiFont(ofSize: 14))) + .foregroundColor(Colors.text) + .frame(width: 28, height: 28) + .background(currentSection?.inputMode == item.inputMode ? Colors.modalLevel2Background : Colors.modalLevel1Background) + .cornerRadius(4) + } + } + } + } + +} + +struct EmojiBottomTitleView_Previews: PreviewProvider { + + static var previews: some View { + EmojiBottomTitleView(inputMode: .constant(.zwj)) + .previewLayout(.fixed(width: 600, height: 600)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiBottomView/EmojiBottomView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiBottomView/EmojiBottomView.swift new file mode 100644 index 0000000..886f739 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiBottomView/EmojiBottomView.swift @@ -0,0 +1,54 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct EmojiBottomView: View { + + @EnvironmentObject var store: Store + + var body: some View { + VStack(spacing: 12) { + EmojiBottomTitleView(inputMode: $store.state.inputMode) + Group { + switch store.state.inputMode { + case .zwj: + ZWJInputView() + case .flag: + EmojiGridInputView(inputSections: dataProvider.chapterFour.emojiInput["flags"]!) { text in + store.dispatch(.inputItemTap(text)) + } + case .keycap: + EmojiGridInputView(inputSections: dataProvider.chapterFour.emojiInput["keycaps"]!) { text in + store.dispatch(.inputItemTap(text)) + } + case .modifier: + EmojiGridInputView(inputSections: dataProvider.chapterFour.emojiInput["modifiers"]!) { text in + store.dispatch(.inputItemTap(text)) + } + case .variation: + EmojiGridInputView(inputSections: dataProvider.chapterFour.emojiInput["variations"]!) { text in + store.dispatch(.inputItemTap(text)) + } + } + } + .cardStyle() + } + .padding(12) + .background(Colors.modalBackground) + .frame(maxHeight: .infinity) + } + +} + +struct EmojiBottomView_Previews: PreviewProvider { + + static var previews: some View { + EmojiBottomView() + .previewLayout(.fixed(width: 600, height: 600)) + .environmentObject(Store(reduce: chapterFourReduce)) + .colorScheme(.light) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiBottomView/Views/EmojiGridInputView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiBottomView/Views/EmojiGridInputView.swift new file mode 100644 index 0000000..30d32f8 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiBottomView/Views/EmojiGridInputView.swift @@ -0,0 +1,98 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI +import ASCollectionView + +struct EmojiGridInputView: View { + + var inputSections: [EmojiInputSection] + var itemTap: ((String) -> Void)? + + var body: some View { + ASCollectionView( + sections: inputSections.enumerated().map { (offset, sectionData) -> ASCollectionViewSection in + ASCollectionViewSection( + id: offset, + data: sectionData.characters, + dataID: \.self, + contextMenuProvider: { _, item in + guard + item.unicodeScalars.count == 1, + let scalar = item.unicodeScalars.first + else { + return nil + } + return CharacterBottomView.contextMenuConfiguration(forCodepoint: scalar.value) + } + ) { item, _ in + Button(action: { + itemTap?(item) + }) { + EmojiCell(text: item) + } + } + .sectionHeader { + Text(sectionData.title) + .font(.system(size: 14, weight: .semibold)) + .foregroundColor(Colors.text) + .padding(4) + .frame(maxWidth: .infinity, alignment: .leading) + } + } + ) + .layout { + return .grid( + layoutMode: .adaptive(withMinItemSize: 44), + itemSpacing: 4, + lineSpacing: 4, + itemSize: .absolute(44), + sectionInsets: NSDirectionalEdgeInsets(top: 0, leading: 4, bottom: 0, trailing: 4) + ) + } + } + +// func contextMenuProvider(index: Int, post: Post) -> UIContextMenuConfiguration? { +// let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) +// { (_) -> UIMenu? in +// let testAction = UIAction(title: "Test") +// { _ in +// // +// } +// return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [testAction]) +// } +// return configuration +// } + +} + +struct EmojiGridInputView_Previews: PreviewProvider { + + static var previews: some View { + EmojiGridInputView(inputSections: []) + .previewLayout(.fixed(width: 600, height: 600)) + } + +} + +//func onCellEvent(_ event: CellEvent) +//{ +// switch event +// { +// case let .onAppear(item): +// ASRemoteImageManager.shared.load(item.url) +// case let .onDisappear(item): +// ASRemoteImageManager.shared.cancelLoad(for: item.url) +// case let .prefetchForData(data): +// for item in data +// { +// ASRemoteImageManager.shared.load(item.url) +// } +// case let .cancelPrefetchForData(data): +// for item in data +// { +// ASRemoteImageManager.shared.cancelLoad(for: item.url) +// } +// } +//} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiBottomView/Views/ZWJInputView/ZWJFomulaInputView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiBottomView/Views/ZWJInputView/ZWJFomulaInputView.swift new file mode 100644 index 0000000..cfe348d --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiBottomView/Views/ZWJInputView/ZWJFomulaInputView.swift @@ -0,0 +1,57 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct ZWJInputView: View { + + @EnvironmentObject var store: Store + + var body: some View { + VStack { + Picker("", selection: $store.state.zwjInputMode) { + Text("Sequences") + .tag(ChapterFourState.ZwjInputMode.sequences) + Text("Components") + .tag(ChapterFourState.ZwjInputMode.components) + } + .pickerStyle(SegmentedPickerStyle()) + + Group { + switch store.state.zwjInputMode { + case .sequences: + let items = dataProvider.chapterFour.emojiInput["zwj-sequences"]! + .flatMap({ $0.characters }) + ScrollView { + LazyVStack(spacing: 8) { + ForEach(items, id: \.self) { item in + ZWJInputListItem(text: item) { text in + store.dispatch(.inputItemTap(text)) + } + } + } + } + case .components: + EmojiGridInputView( + inputSections: dataProvider.chapterFour.emojiInput["zwj-sequence-components"]! + ) { text in + store.dispatch(.inputItemTap(text)) + } + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + +} + +struct ZWJInputView_Previews: PreviewProvider { + + static var previews: some View { + ZWJInputView() + .environmentObject(Store(reduce: chapterFourReduce)) + .previewLayout(.fixed(width: 600, height: 600)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiBottomView/Views/ZWJInputView/ZWJInputListItem.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiBottomView/Views/ZWJInputView/ZWJInputListItem.swift new file mode 100644 index 0000000..2428809 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiBottomView/Views/ZWJInputView/ZWJInputListItem.swift @@ -0,0 +1,41 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct ZWJInputListItem: View { + + var text: String + var itemSelectAction: ((String) -> Void)? + + var body: some View { + HStack(spacing: 8) { + Button(action: { + itemSelectAction?(text) + }) { + EmojiCell(text: text, backgroundColor: Colors.modalLevel2Background) + .padding(8) + .background(Colors.modalBackground) + .cornerRadius(8) + } + Button(action: { + itemSelectAction?(text) + }) { + EmojiFormulaView(text: String(text)) + } + } + .frame(maxWidth: .infinity) + } + +} + +struct ZWJInputListItem_Previews: PreviewProvider { + + static var previews: some View { + ZWJInputListItem(text: "👩🏼‍💻") + .previewLayout(.fixed(width: 600, height: 600)) + .background(Color.blue) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiTopView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiTopView.swift new file mode 100644 index 0000000..152891b --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiTopView/EmojiTopView.swift @@ -0,0 +1,61 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct EmojiTopView: View { + + var text: String + var annotation: String + var subannotation: String + + var onBackspaceTap: (() -> Void)? + + var body: some View { + VStack(alignment: .center, spacing: 12) { + EmojiLargeImputView(text: text) + .foregroundColor(Colors.text) + HStack(spacing: 8){ + EmojiCell(text: text) + .padding(8) + .background(Colors.cardBackground) + .cornerRadius(8) + EmojiFormulaView(text: text) + Button(action: { + onBackspaceTap?() + }) { + EmojiCell(text: "⌫") + .padding(8) + .background(Colors.cardBackground) + .cornerRadius(8) + } + } + + Text(annotation) + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(Colors.text) + + Text(subannotation) + .font(.system(size: 12, weight: .medium)) + .foregroundColor(Colors.text) + } + .padding([.leading, .trailing], 8) + .frame(maxWidth: .infinity) + } + +} + +struct EmojiTopView_Previews: PreviewProvider { + + static var previews: some View { + Group { + EmojiTopView(text: "👩🏼‍💻", annotation: "An Annotation", subannotation: "Sub Annotation") + .previewLayout(.fixed(width: 400, height: 800)) + EmojiTopView(text: "👩🏼‍💻", annotation: "An Annotation", subannotation: "Sub Annotation") + .previewLayout(.fixed(width: 400, height: 150)) + } + .background(Color.green) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiView.swift new file mode 100644 index 0000000..8481ec6 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/ATourOfUnicode/04-Emoji/Views/EmojiView.swift @@ -0,0 +1,47 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct EmojiView: View { + + @EnvironmentObject var store: Store + + var body: some View { + NavigationView { + VStack(alignment: .leading, spacing: 12) { + EmojiTopView( + text: store.state.currentEmojiSequence, + annotation: store.state.shortName, + subannotation: store.state.keywords, + onBackspaceTap: { + store.dispatch(.backSpace) + } + ) + EmojiBottomView() + } + .padding(.top, 12) + .navigationBarTitle("Emoji", displayMode: .inline) + .background(Colors.pageBackground) + .frame(maxHeight: .infinity) + } + .navigationViewStyle(StackNavigationViewStyle()) + } + +} + +struct EmojiView_Previews: PreviewProvider { + + static var previews: some View { + Group { + EmojiView() + .previewLayout(.fixed(width: 600, height: 600)) + EmojiView() + .previewLayout(.fixed(width: 600, height: 1000)) + } + .environmentObject(Store(reduce: chapterFourReduce)) + .colorScheme(.light) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Badge/Badge.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Badge/Badge.swift new file mode 100644 index 0000000..3aa0b4a --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Badge/Badge.swift @@ -0,0 +1,33 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct Badge: View { + + var text: String + var backgroundColor: Color + + var body: some View { + Text(text) + .lineLimit(1) + .foregroundColor(.white) + .font(.system(size: 10, weight: .semibold)) + .padding([.leading, .trailing], 4) + .padding([.top, .bottom], 2) + .background(backgroundColor) + .cornerRadius(4) + } + +} + +struct Badge_Previews: PreviewProvider { + + static var previews: some View { + Badge(text: "Hello", backgroundColor: .green) + .colorScheme(.light) + .previewLayout(.fixed(width: 300, height: 200)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/CharacterBottomView/Character3DView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/CharacterBottomView/Character3DView.swift new file mode 100644 index 0000000..43fef16 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/CharacterBottomView/Character3DView.swift @@ -0,0 +1,135 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI +import UIKit +import SceneKit + +final class Character3DUIView: UIView { + + private var sceneView = SCNView() + private var scene = SCNScene() + private var cameraNode = SCNNode() + private var geometryNode: SCNNode? + + var bottomModel: CharacterBottomViewModel? { + didSet { + if bottomModel?.codePoint != oldValue?.codePoint { + updateForCurrentModel() + } + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + setup() + } + + required init?(coder: NSCoder) { + fatalError("init?(coder: NSCoder) not implemented") + } + + private func setup() { + sceneView.allowsCameraControl = true + sceneView.autoenablesDefaultLighting = true + sceneView.scene = scene + sceneView.backgroundColor = UIColor(Colors.modalLevel1Background) + + let camera = SCNCamera() + camera.automaticallyAdjustsZRange = true + + cameraNode.camera = camera + cameraNode.position = SCNVector3(x: 0, y: 0, z: 256) + scene.rootNode.addChildNode(cameraNode) + + addSubview(sceneView) + + updateForCurrentModel() + } + + private func updateForCurrentModel() { + if let geometryNode = geometryNode { + geometryNode.removeFromParentNode() + self.geometryNode = nil + } + guard let mode = bottomModel?.renderingTrait?.mode else { + return + } + + var finalGeometry: SCNGeometry? = nil + + switch mode { + case let .bezierPath(path): + let geometry = SCNShape(path: path, extrusionDepth: 16) + + geometry.firstMaterial?.diffuse.contents = Colors.common.randomElement() + + finalGeometry = geometry + + case let.image(image): + guard let image = image else { + break + } + let size = image.size + let geometry = SCNBox(width: size.width, height: size.height, length: 16, chamferRadius: 8) + + let constructMaterialFromContent: (Any) -> SCNMaterial = { color in + let material = SCNMaterial() + material.diffuse.contents = color + return material + } + + let imageMaterial = constructMaterialFromContent( + image.opaqueBackground(color: UIColor(Colors.modalBackground)) + ) + let solidMaterial = constructMaterialFromContent( + UIColor(Colors.modalBackground) + ) + + geometry.materials = [ + imageMaterial, // front + solidMaterial, // right + imageMaterial, // back + solidMaterial, // left + solidMaterial, // top + solidMaterial // bottom + ] + + finalGeometry = geometry + } + + if let geometry = finalGeometry { + let geometryNode = SCNNode(geometry: geometry) + scene.rootNode.addChildNode(geometryNode) + + geometryNode.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2 * .pi, z: 0, duration: 15))) + self.geometryNode = geometryNode + + // Reset the camera + sceneView.pointOfView = cameraNode + } + } + + override func layoutSubviews() { + super.layoutSubviews() + sceneView.frame = bounds + } + +} + +struct Character3DView: UIViewRepresentable { + + var bottomModel: CharacterBottomViewModel + + func makeUIView(context: Context) -> Character3DUIView { + let view = Character3DUIView() + view.bottomModel = bottomModel + return view + } + + func updateUIView(_ uiView: Character3DUIView, context: Context) { + uiView.bottomModel = bottomModel + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/CharacterBottomView/CharacterBottomView+contextConfiguration.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/CharacterBottomView/CharacterBottomView+contextConfiguration.swift new file mode 100644 index 0000000..5534237 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/CharacterBottomView/CharacterBottomView+contextConfiguration.swift @@ -0,0 +1,27 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit +import SwiftUI + +extension CharacterBottomView { + + static func contextMenuConfiguration(forCodepoint codepoint: UInt32) -> UIContextMenuConfiguration? { + if let model = CharacterBottomViewModel.fromCodepoint(codepoint) { + return UIContextMenuConfiguration(identifier: codepoint as NSNumber) { + let characterController = UIHostingController( + rootView: CharacterBottomView( + bottomViewModel: model, + isInPreview: true, + closeAction: nil + ) + ) + characterController.preferredContentSize = CharacterBottomView.preferredContextMenuSize + return characterController + } + } + return nil + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/CharacterBottomView/CharacterBottomView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/CharacterBottomView/CharacterBottomView.swift new file mode 100644 index 0000000..bd991fd --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/CharacterBottomView/CharacterBottomView.swift @@ -0,0 +1,92 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct CharacterBottomView: View { + + static let preferredContextMenuSize = CGSize(width: 720, height: 320) + + static let minimumRightContentWidth: CGFloat = 200 + + var bottomViewModel: CharacterBottomViewModel + var isInPreview: Bool = false + var closeAction: (() -> Void)? + + var body: some View { + VStack(alignment: .leading) { + HStack { + CharacterBottomTitleView( + bottomViewModel: bottomViewModel, + isInPreview: isInPreview + ) + + if !isInPreview { + Button(action: { + closeAction?() + }) { + Image(systemName: "xmark") + .resizable() + .frame(width: 10, height: 10) + .foregroundColor(Colors.controlForeground) + .padding(6) + .aspectRatio(1, contentMode: .fit) + .background(Colors.controlBackground) + .cornerRadius(4) + } + } + } + GeometryReader { proxy in + let character3DView = + Character3DView( + bottomModel: bottomViewModel + ) + .background(Colors.modalLevel1Background) + .cornerRadius(12) + + let introductionView = + CharacterIntroductionView( + bottomModel: bottomViewModel + ) + + if proxy.size.width - proxy.size.height - 8 < Self.minimumRightContentWidth { + VStack(spacing: 8) { + character3DView + .frame(maxWidth: .infinity) + introductionView + } + } else { + HStack(alignment: .top, spacing: 8) { + character3DView + .aspectRatio(1, contentMode: .fit) + .layoutPriority(1) + introductionView + } + } + } + } + .padding(12) + .background(Colors.modalBackground) + } + +} + +struct CharacterBottomView_Previews: PreviewProvider { + + static var previews: some View { + Group { + CharacterBottomView( + bottomViewModel: CharacterBottomViewModel.preview + ) + .previewLayout(.fixed(width: 400, height: 320)) + + CharacterBottomView( + bottomViewModel: CharacterBottomViewModel.preview + ) + .previewLayout(.fixed(width: 800, height: 200)) + } + .colorScheme(.light) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/CharacterBottomView/CharacterIntroductionView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/CharacterBottomView/CharacterIntroductionView.swift new file mode 100644 index 0000000..5b24f70 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/CharacterBottomView/CharacterIntroductionView.swift @@ -0,0 +1,56 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct CharacterIntroductionView: View { + + var bottomModel: CharacterBottomViewModel + + var body: some View { + ScrollView { + VStack(alignment: .leading, spacing: 4) { + Text("CodePoint: \(bottomModel.uPlusRepresentation)") + if bottomModel.nameAlias != nil, let name = bottomModel.name { + Text("Previously Named: \(name)") + } + if + let blockKey = bottomModel.blockKey, + let blockName = dataProvider.common.blocks[blockKey]?.name + { + Text("Block: \(blockName)") + } + if let reneredFontNames = bottomModel.renderingTrait?.fontNames { + let names = reneredFontNames.map { name -> String in + if AppFonts(rawValue: name) != nil { + return "\(name) (Bundled)" + } else { + return "\(name) (System)" + } + } + Text("Renered Font: \(names.joined(separator: ", "))") + } +// Text("[TODO] Related characters") + } + .frame(maxWidth: .infinity, alignment: .leading) + } + .frame(maxWidth: .infinity) + .font(.system(size: 14, weight: .semibold)) + .foregroundColor(Colors.text) + .cardStyle() + } + +} + +struct CharacterIntroductionView_Previews: PreviewProvider { + + static var previews: some View { + CharacterIntroductionView( + bottomModel: CharacterBottomViewModel.preview + ) + .previewLayout(.fixed(width: 400, height: 320)) + .colorScheme(.light) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/CharacterBottomView/CharactersBottomTitleView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/CharacterBottomView/CharactersBottomTitleView.swift new file mode 100644 index 0000000..2d3c7ad --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/CharacterBottomView/CharactersBottomTitleView.swift @@ -0,0 +1,50 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct CharacterBottomTitleView: View { + + var bottomViewModel: CharacterBottomViewModel + var isInPreview: Bool = false + + var body: some View { + HStack { + Text(bottomViewModel.displayName) + .lineLimit(isInPreview ? 1 : 2) + .font(.system(size: 14, weight: .semibold)) + .foregroundColor(Colors.text) + if isInPreview { + Spacer() + } + Badge( + text: bottomViewModel.generalCategory.displayName, + backgroundColor: Color(bottomViewModel.generalCategory.color) + ) + .font(.system(size: 16, weight: .semibold)) + + if let age = bottomViewModel.age, let displayedAge = age.displayAge { + Badge( + text: displayedAge, + backgroundColor: Color(age.color) + ) + .font(.system(size: 16, weight: .semibold)) + } + Spacer() + } + } + +} + +struct CharacterBottomTitleView_Previews: PreviewProvider { + + static var previews: some View { + CharacterBottomView( + bottomViewModel: CharacterBottomViewModel.preview + ) + .previewLayout(.fixed(width: 400, height: 320)) + .colorScheme(.light) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/DebugAlert.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/DebugAlert.swift new file mode 100644 index 0000000..785e701 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/DebugAlert.swift @@ -0,0 +1,25 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit +import PlaygroundSupport + +struct DebugAlert { + + static func showOnKeyWindow(message: String) { + debugPrint(message) + + let alert = UIAlertController( + title: "Debug", + message: message, + preferredStyle: UIAlertController.Style.alert + ) + alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil)) + + let controller = UIViewController() + PlaygroundPage.current.liveView = controller + controller.present(alert, animated: true, completion: nil) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Map/MapDot.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Map/MapDot.swift new file mode 100644 index 0000000..457e9a3 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Map/MapDot.swift @@ -0,0 +1,52 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct MapDot: View { + + @State private var animateSmallCircle = false + @State private var animateLargeCircle = false + + var color: Color + + var body: some View { + ZStack { + GeometryReader { proxy in + let size = min(proxy.size.width, proxy.size.height) + Circle() + .frame(width: size, height: size) + .foregroundColor(color) + .scaleEffect(animateSmallCircle ? 1.0 : 1.1) + .animation(Animation.easeInOut(duration: 2).repeatForever(autoreverses: true)) + .onAppear() { + animateSmallCircle.toggle() + } + .overlay( + Circle() + .frame(width: size * 3, height: size * 3) + .foregroundColor(color) + .opacity(animateLargeCircle ? 0 : 0.5) + // set min val to 0.01 to prevent strange err: + // ignoring singular matrix: ProjectionTransform(m11: 5e-324, m12: 0.0, m13: 0.0, m21: 0.0, m22: 5e-324, m23: 0.0, m31: 7.0, m32: 7.25, m33: 1.0) + .scaleEffect(animateLargeCircle ? 1.2 : 0.01) + .animation(Animation.easeOut(duration: 2).delay(2).repeatForever(autoreverses: false)) + .onAppear() { + animateLargeCircle.toggle() + } + ) + } + } + } + +} + +struct MapDot_Previews: PreviewProvider { + + static var previews: some View { + MapDot(color: .blue) + .previewLayout(.fixed(width: 200, height: 200)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Map/MapView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Map/MapView.swift new file mode 100644 index 0000000..c599427 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Map/MapView.swift @@ -0,0 +1,53 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +struct MapView: View { + + let countries: [Country] + + static let mapRelSize = CGSize(width: 238, height: 99) + + var body: some View { + if !countries.isEmpty { + ZStack(alignment: .center) { + GeometryReader { proxy in + Group { + let dotSize = Self.mapRelSize.width * 0.020 + let ratio = CGSize( + width: proxy.size.width / Self.mapRelSize.width, + height: proxy.size.height / Self.mapRelSize.height + ) + Image("map") + .resizable() + .aspectRatio(contentMode: .fit) + ForEach(countries, id: \.name) { country in + MapDot(color: Colors.mapDot) + .frame(width: dotSize, height: dotSize) + .position( + x: CGFloat(country.x) * ratio.width, + y: CGFloat(country.y) * ratio.height + ) + } + } + } + } + .aspectRatio(Self.mapRelSize, contentMode: .fit) + } + } + +} + +struct MapView_Previews: PreviewProvider { + + static var previews: some View { + let plane = dataProvider.common.planes[0] + let block = dataProvider.common.blocks[plane.blocks[0]]! + MapView(countries: block.countries) + .colorScheme(.light) + .previewLayout(.fixed(width: 600, height: 400)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/RichTextDescription/RichTextDescriptionView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/RichTextDescription/RichTextDescriptionView.swift new file mode 100644 index 0000000..77ffa4d --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/RichTextDescription/RichTextDescriptionView.swift @@ -0,0 +1,94 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit +import SwiftUI + +final class RichTextDescriptionUIView: UITextView, UITextViewDelegate { + + var descriptionText: String = "" { + didSet { + if descriptionText != oldValue { + reformatLinks() + } + } + } + + override init(frame: CGRect, textContainer: NSTextContainer?) { + super.init(frame: frame, textContainer: textContainer) + commonInit() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + commonInit() + } + + private func commonInit() { + delegate = self + + // Make sure link is clickable + isEditable = false + isSelectable = true + + // Remove all paddings + textContainerInset = .zero + textContainer.lineFragmentPadding = 0 + + backgroundColor = .clear + + reformatLinks() + } + + private func reformatLinks() { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineBreakMode = NSLineBreakMode.byWordWrapping + attributedText = descriptionText.formatLinks( + baseAttributes: [ + .paragraphStyle: paragraphStyle, + .font: UIFont.systemFont(ofSize: 14, weight: .semibold) + ] + ) + } + +} + +struct RichTextDescriptionView: UIViewRepresentable { + + final class Coordiantor: NSObject, UITextViewDelegate { + + var parent: RichTextDescriptionView + var linkHander: ((URL) -> Void)? = nil + + init(_ parent: RichTextDescriptionView) { + self.parent = parent + } + + func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { +// print(URL) + linkHander?(URL) + return false + } + + } + + var descriptionText: String + var linkHander: ((URL) -> Void)? = nil + + func makeCoordinator() -> Coordiantor { + return Coordiantor(self) + } + + func makeUIView(context: Context) -> RichTextDescriptionUIView { + let view = RichTextDescriptionUIView() + view.delegate = context.coordinator + return view + } + + func updateUIView(_ uiView: RichTextDescriptionUIView, context: Context) { + uiView.descriptionText = descriptionText + context.coordinator.linkHander = linkHander + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/ViewSizeKey.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/ViewSizeKey.swift new file mode 100644 index 0000000..14acc46 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/ViewSizeKey.swift @@ -0,0 +1,29 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +extension ViewSizeKey: ViewModifier { + + func body(content: Content) -> some View { + return content.overlay( + GeometryReader { proxy in + Color.clear.preference(key: Self.self, value: proxy.size) + } + ) + } + +} + +struct ViewSizeKey: PreferenceKey { + + typealias Value = CGSize + + static var defaultValue: CGSize = .zero + + static func reduce(value: inout Value, nextValue: () -> Value) { + value = nextValue() + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Xray/Layers/PlaneXrayBlocksLayer.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Xray/Layers/PlaneXrayBlocksLayer.swift new file mode 100644 index 0000000..ebddf11 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Xray/Layers/PlaneXrayBlocksLayer.swift @@ -0,0 +1,175 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit + +final class PlaneXrayBlocksLayer: CALayer { + + var plane: Plane? + var showsHex: Bool + + private lazy var maskLayer = PlaneXrayMaskLayer() + + init(plane: Plane?, showsHex: Bool) { + self.plane = plane + self.showsHex = showsHex + super.init() + contentsScale = UIScreen.main.scale + mask = maskLayer + } + + required init?(coder: NSCoder) { + fatalError("init?(coder: NSCoder) not implemented") + } + + static func getBezierPathForBlock(block: Block, blockWidth: CGFloat) -> UIBezierPath { + let rowStart = CGFloat((block.codePointStart & 0xF000) >> 12) + let rowEnd = CGFloat((block.codePointEnd & 0xF000) >> 12) + let colStart = CGFloat(block.codePointStart & 0x0FFF) + let colEnd = CGFloat(block.codePointEnd & 0x0FFF) + + if rowStart == rowEnd { + let unitX = colStart / 0x100 + let unitY = rowStart + let unitWidth = (colEnd - colStart) / 0x100 + + // same row + // draw colStart - colEnd + let path = UIBezierPath( + rect: CGRect( + x: blockWidth * unitX, + y: blockWidth * unitY, + width: blockWidth * unitWidth, + height: blockWidth + ) + ) + return path + } else { + // different row + // draw three parts + + let path = UIBezierPath() + + // upper part + let firstLineX = colStart / 0x100 + let firstLineY = rowStart + let lastLineY = rowEnd + let lastLineX = colEnd / 0x100 + + /* + ooooxxxx + xxxxxxxx + xxxooooo + */ + let points = [ + CGPoint( + x: blockWidth * 16, + y: blockWidth * firstLineY + ), + CGPoint( + x: blockWidth * 16, + y: blockWidth * lastLineY + ), + CGPoint( + x: blockWidth * lastLineX, + y: blockWidth * lastLineY + ), + CGPoint( + x: blockWidth * lastLineX, + y: blockWidth * (lastLineY + 1) + ), + CGPoint( + x: 0, + y: blockWidth * (lastLineY + 1) + ), + CGPoint( + x: 0, + y: blockWidth * (firstLineY + 1) + ), + CGPoint( + x: blockWidth * firstLineX, + y: blockWidth * (firstLineY + 1) + ), + CGPoint( + x: blockWidth * firstLineX, + y: blockWidth * firstLineY + ) + ] + + path.move(to: points.last!) + points.forEach({ path.addLine(to: $0) }) + + return path + } + } + + override func draw(in ctx: CGContext) { + UIGraphicsPushContext(ctx) + + guard let plane = plane else { + return + } + + let drawableWidth = min(bounds.width, bounds.height) + let blockWidth = CGFloat(drawableWidth / 16) + + // i: draw background + + UIColor(Colors.modalBackground).setFill() + UIRectFill(bounds) + + // iii draw unicode blocks + + let blockGroup = plane.blocks.compactMap({ dataProvider.common.blocks[$0] }) + + for block in blockGroup { + block.color.setFill() + + let bezierPath = Self.getBezierPathForBlock(block: block, blockWidth: blockWidth) + bezierPath.fill() + } + + if showsHex { + // draw hex + for y in 0..<16 { + for x in 0..<16 { + let hex = Int(plane.codePointStart >> 8) + y * 16 + x + let hexStr: String + if hex >= 0x10000 { + hexStr = "\(hex, radix: .hex, toWidth: 3)" + } else { + hexStr = "\(hex, radix: .hex, toWidth: 2)" + } + let fontSize = blockWidth / CGFloat(hexStr.count) + + let attributes: [NSAttributedString.Key: Any] = [ + .font: UIFont.systemFont(ofSize: fontSize), + .foregroundColor: UIColor.white + ] + + let rectSize = (hexStr as NSString).size(withAttributes: attributes) + + (hexStr as NSString).draw( + at: CGPoint( + x: (CGFloat(x) + 0.5) * blockWidth - rectSize.width / 2, + y: (CGFloat(y) + 0.5) * blockWidth - rectSize.height / 2 + ), + withAttributes: attributes + ) + } + } + } + + UIGraphicsPopContext() + } + + override func layoutSublayers() { + super.layoutSublayers() + CATransaction.performWithoutImplicitAnimation { + maskLayer.frame = bounds + maskLayer.setNeedsDisplay() + } + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Xray/Layers/PlaneXrayUIViewMaskLayer.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Xray/Layers/PlaneXrayUIViewMaskLayer.swift new file mode 100644 index 0000000..5a0595f --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Xray/Layers/PlaneXrayUIViewMaskLayer.swift @@ -0,0 +1,42 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit + +final class PlaneXrayMaskLayer: CALayer { + + override init() { + super.init() + contentsScale = UIScreen.main.scale + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + contentsScale = UIScreen.main.scale + } + + override func draw(in ctx: CGContext) { + UIGraphicsPushContext(ctx) + + let drawableWidth = min(bounds.width, bounds.height) + let blockWidth = CGFloat(drawableWidth) / 16 + + for row in 0..<16 { + for col in 0..<16 { + let rect = CGRect( + x: blockWidth * CGFloat(col), + y: blockWidth * CGFloat(row), + width: blockWidth, + height: blockWidth + ).insetBy(dx: 1, dy: 1) + UIColor.black.setFill() + UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 4, height: 4)) + .fill() + } + } + + UIGraphicsPopContext() + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Xray/PlaneXrayUIView.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Xray/PlaneXrayUIView.swift new file mode 100644 index 0000000..a4c43f9 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Chapters/Components/Xray/PlaneXrayUIView.swift @@ -0,0 +1,117 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit +import SwiftUI + +final class PlaneXrayUIView: UIView { + + var plane: Plane? { + didSet { + if oldValue?.number != plane?.number { + blocksLayer.plane = plane + blocksLayer.setNeedsDisplay() + } + } + } + + var showsHex = false { + didSet { + if oldValue != showsHex { + blocksLayer.showsHex = showsHex + blocksLayer.setNeedsDisplay() + } + } + } + + var selectedBlockKey: String?{ + didSet { + redrawIndicator() + } + } + + private struct DrawableBlock { + var rowStart: CGFloat + var rowEnd: CGFloat + var colStart: CGFloat + var colEnd: CGFloat + var color: UIColor = .black + } + + private let blocksLayer: PlaneXrayBlocksLayer + + private lazy var indicatorLayer: CAShapeLayer = { + let layer = CAShapeLayer() + layer.strokeColor = UIColor(Colors.selectionBackground).cgColor + layer.lineWidth = 3.0 + layer.lineCap = .round + layer.fillColor = UIColor.black.withAlphaComponent(0.15).cgColor + + layer.shadowColor = UIColor.black.cgColor + layer.shadowRadius = 2 + layer.shadowOpacity = 0.35 + layer.shadowOffset = CGSize(width: 0, height: 4) + return layer + }() + + init(plane: Plane? = nil) { + self.plane = plane + self.blocksLayer = PlaneXrayBlocksLayer(plane: plane, showsHex: showsHex) + + super.init(frame: .zero) + + layer.addSublayer(blocksLayer) + layer.addSublayer(indicatorLayer) + } + + required init?(coder: NSCoder) { + fatalError("init?(coder: NSCoder) not implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + CATransaction.performWithoutImplicitAnimation { + blocksLayer.frame = bounds + blocksLayer.setNeedsDisplay() + indicatorLayer.frame = bounds + redrawIndicator() + } + } + + private func redrawIndicator() { + if + let key = selectedBlockKey, + let block = dataProvider.common.blocks[key] + { + let indicatorPath = PlaneXrayBlocksLayer.getBezierPathForBlock( + block: block, + blockWidth: bounds.width / 16 + ) + indicatorLayer.path = indicatorPath.cgPath + } else { + indicatorLayer.path = nil + } + } + +} + +struct PlaneXrayView: UIViewRepresentable { + + var plane: Plane + var showsHex: Bool + var selectedBlockKey: String? + + func makeUIView(context: Context) -> PlaneXrayUIView { + print("Plane Xray: make") + return PlaneXrayUIView(plane: plane) + } + + func updateUIView(_ uiView: PlaneXrayUIView, context: Context) { + print("Plane Xray: update") + uiView.plane = plane + uiView.showsHex = showsHex + uiView.selectedBlockKey = selectedBlockKey + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/App.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/App.swift new file mode 100644 index 0000000..b2270cb --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/App.swift @@ -0,0 +1,64 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation +import CoreData +import UIKit + +final class App { + + static var moc: NSManagedObjectContext! = nil + static var setupDone = false + + static func setup() { + precondition(setupDone == false, "App setup hase been performed before!") + setupDone = true + AppFonts.registerAllFonts() + moc = setupCoreData() + + NotificationCenter.default.addObserver( + self, + selector: #selector(didReceiveMemoryWarnings), + name: UIApplication.didReceiveMemoryWarningNotification, + object: nil + ) + } + + private static func setupCoreData() -> NSManagedObjectContext { + guard let modelURL = Bundle.main.url( + forResource: "UnicodeData", + withExtension: "momd" + ) else { + fatalError("Failed to find data model") + } + guard let mom = NSManagedObjectModel(contentsOf: modelURL) else { + fatalError("Failed to create model from file: \(modelURL)") + } + let psc = NSPersistentStoreCoordinator(managedObjectModel: mom) + + let fileURL = URL(fileURLWithPath: Bundle.main.path(forResource: "UnicodeData", ofType: "sqlite")!) + do { + try psc.addPersistentStore( + ofType: NSSQLiteStoreType, + configurationName: nil, + at: fileURL, + options: [NSReadOnlyPersistentStoreOption: true] + ) + } catch { + fatalError("Error configuring persistent store: \(error)") + } + let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) + moc.persistentStoreCoordinator = psc + return moc + } + + @objc private static func didReceiveMemoryWarnings() { + DebugAlert.showOnKeyWindow(message: "Received memory warning, cleaning up resources") + if let moc = moc { + moc.refreshAllObjects() + UIFont.purgeCache() + } + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/ChapterEmojiFont.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/ChapterEmojiFont.swift new file mode 100644 index 0000000..7392bbc --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/ChapterEmojiFont.swift @@ -0,0 +1,43 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation + +enum CharacterEmojiFont: String, CaseIterable { + + case zwj = "\u{E001}" + case vs15 = "\u{E002}" + case vs16 = "\u{E003}" + +} + +extension String { + + func toChapterEmojiCharacters() -> String { + switch self { + case "\u{200D}": + return CharacterEmojiFont.zwj.rawValue + case "\u{FE0E}": + return CharacterEmojiFont.vs15.rawValue + case "\u{FE0F}": + return CharacterEmojiFont.vs16.rawValue + default: + return self + } + } + + func emojiCharactersToOriginal() -> String { + switch self { + case CharacterEmojiFont.zwj.rawValue: + return "\u{200D}" + case CharacterEmojiFont.vs15.rawValue: + return "\u{FE0E}" + case CharacterEmojiFont.vs16.rawValue: + return "\u{FE0F}" + default: + return self + } + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/Colors.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/Colors.swift new file mode 100644 index 0000000..21b0722 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/Colors.swift @@ -0,0 +1,46 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation +import SwiftUI +import UIKit.UIColor + +struct Colors { + + static let text = Color("Text") + static let secondaryText = Color("SecondaryText") + static let cardBackground = Color("CardBackground") + static let pageBackground = Color("PageBackground") + static let modalBackground = Color("ModalBackground") + static let modalLevel1Background = Color("ModalLevel1Background") + static let modalLevel2Background = Color("ModalLevel2Background") + static let selectionBackground = Color("SelectionBackground") + static let controlForeground = Color("ControlForeground") + static let controlBackground = Color("ControlBackground") + static let listBackground = Color("ListBackground") + static let mapDot = Color("MapDot") + + static let common = [ + UIColor(hex: "#F59A9Bff")!, + UIColor(hex: "#E79C78ff")!, + UIColor(hex: "#DAAE39ff")!, + UIColor(hex: "#EBB6ECff")!, + UIColor(hex: "#E37540ff")!, + UIColor(hex: "#407896ff")!, + UIColor(hex: "#5D8765ff")!, + UIColor(hex: "#E71511ff")!, + UIColor(hex: "#F7B31Dff")!, + UIColor(hex: "#81CBE8ff")!, + UIColor(hex: "#056C8Fff")!, + UIColor(hex: "#FD958Aff")!, + UIColor(hex: "#4998FEff")!, + UIColor(hex: "#F5796Cff")!, + UIColor(hex: "#FDC64Dff")! + ] + + static func commonColor(forIndex idx: Int) -> UIColor { + return Colors.common[idx % Colors.common.count] + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/DataProvider.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/DataProvider.swift new file mode 100644 index 0000000..24dab3d --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/DataProvider.swift @@ -0,0 +1,165 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation +import CoreData +import UIKit.UIFont + +let dataProvider: DataProvider = { + do { + return try DataProvider() + } catch { + fatalError("Failed to initialize database with error: \(error)") + } +}() + +struct DataProvider { + + struct Common { + let planes: [Plane] + let blocks: [String: Block] + } + + struct ChapterTwo { + let categories: [LangCategory] + let categoryIndices: [String: Int] + } + + struct ChapterThree { + let sampleText: [TextSampleMode: [TextWithLanguage]] + } + + struct ChapterFour { + let emojiInput: [String: [EmojiInputSection]] + } + + let common: Common + let chapterTwo: ChapterTwo + let chapterThree: ChapterThree + let chapterFour: ChapterFour + + init() throws { + var planes: [Plane] = try FileHelper.loadBundledJSON(file: "planes") + let blocksArr: [Block] = try FileHelper.loadBundledJSON(file: "blocks") + var blocks: [String: Block] = blocksArr.reduce(into: [String: Block]()) { (blocks, block) in + blocks[block.key] = block + } + + var categories: [LangCategory] = try FileHelper.loadBundledJSON(file: "categories") + for idx in 0.. (String, Block)? { + return common.blocks.first { (_, block) -> Bool in + return codePoint >= block.codePointStart && codePoint <= block.codePointEnd + } + } + +// private func clacPlaneStatistic() { +// // Calc for allocation statuss +// for (idx, plane) in planes.enumerated() { +// print("Plane \(idx) ----") +// var totalChar = 0 +// for key in plane.blocks { +// let block = common.blocks[key]! +// totalChar += Int(block.codePointEnd - block.codePointStart) + 1 +// } +// print("Block cnt: \(plane.blocks.count)") +// print("Char cnt: \(totalChar)") +// print("Char rate: \(Double(totalChar) / 65536)") +// } +// } + + private static func calculateBlockColors( + planes: inout [Plane], + blocks: inout [String: Block], + categories: [LangCategory], + categoryIndices: [String: Int] + ) { + func generateColor(category: LangCategory, blockKeyMap: [String]) { + // calculate for colors + let mid = blockKeyMap.count / 2 + let maxVal: CGFloat = 0.75 + + for (idx, key) in blockKeyMap.enumerated() { + // max: 0.2 + let tmp = idx - mid + let color: UIColor + if tmp < 0 { + color = category.color.lighten( + by: (CGFloat(-tmp) / CGFloat(blockKeyMap.count)) * maxVal + ) + } else if tmp > 0 { + color = category.color.darken( + by: (CGFloat(tmp) / CGFloat(blockKeyMap.count)) * maxVal + ) + } else { + color = category.color + } + + blocks[key]?.color = color + } + } + + planes.forEach { plane in + var prevBlock: Block? = nil + var keysMap: [String] = [] + + plane.blocks.forEach { blockKey in + let block = blocks[blockKey]! + + if let prevBlockUnwrap = prevBlock { + if block.categoryKey == prevBlockUnwrap.categoryKey { + prevBlock = block + keysMap.append(blockKey) + } else { + let category = categories[categoryIndices[prevBlockUnwrap.categoryKey]!] + generateColor(category: category, blockKeyMap: keysMap) + + prevBlock = block + keysMap = [blockKey] + } + } else { + prevBlock = block + keysMap.append(blockKey) + } + } + + if let prevBlock = prevBlock { + let category = categories[categoryIndices[prevBlock.categoryKey]!] + generateColor(category: category, blockKeyMap: keysMap) + } + } + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/FileHelper.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/FileHelper.swift new file mode 100644 index 0000000..ca3bf9f --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/FileHelper.swift @@ -0,0 +1,30 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation + +enum FileHelper { + + static func loadBundledJSON(file: String) throws -> T { + guard let url = Bundle.main.url(forResource: file, withExtension: "json") else { + fatalError("Resource not found: \(file)") + } + return try loadJSON(from: url) + } + + static func loadJSON(from url: URL) throws -> T { + let data = try Data(contentsOf: url) + return try JSONDecoder().decode(T.self, from: data) + } + + static func loadJSON( + from directory: FileManager.SearchPathDirectory, + fileName: String + ) throws -> T + { + let url = FileManager.default.urls(for: directory, in: .userDomainMask).first! + return try loadJSON(from: url.appendingPathComponent(fileName)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/Fonts.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/Fonts.swift new file mode 100644 index 0000000..1595eb5 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Components/Fonts.swift @@ -0,0 +1,66 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation +import UIKit.UIFont + +enum AppFonts: String, CaseIterable { + + static let postScriptNames: [AppFonts: String] = [ + .notoEmojiRegular: "NotoEmoji" + ] + + case notoEmojiRegular = "NotoEmoji-Regular" + case notoMusicRegular = "NotoMusic-Regular" + case notoSansAnatolianHieroglyphsRegular = "NotoSansAnatolianHieroglyphs-Regular" + case notoSansCanadianAboriginalRegular = "NotoSansCanadianAboriginal-Regular" + case notoSansDeseretRegular = "NotoSansDeseret-Regular" + case notoSansElymaicRegular = "NotoSansElymaic-Regular" + case notoSansGeorgianRegular = "NotoSansGeorgian-Regular" + case notoSansGunjalaGondiRegular = "NotoSansGunjalaGondi-Regular" + case notoSansIndicSiyaqNumbersRegular = "NotoSansIndicSiyaqNumbers-Regular" + case notoSansLaoRegular = "NotoSansLao-Regular" + case notoSansMalayalamRegular = "NotoSansMalayalam-Regular" + case notoSansMasaramGondiRegular = "NotoSansMasaramGondi-Regular" + case notoSansMedefaidrinRegular = "NotoSansMedefaidrin-Regular" + case notoSansMongolianRegular = "NotoSansMongolian-Regular" + case notoSansNushuRegular = "NotoSansNushu-Regular" + case notoSansOldSogdianRegular = "NotoSansOldSogdian-Regular" + case notoSansSignWritingRegular = "NotoSansSignWriting-Regular" + case notoSansSinhalaRegular = "NotoSansSinhala-Regular" + case notoSansSogdianRegular = "NotoSansSogdian-Regular" + case notoSansSoyomboRegular = "NotoSansSoyombo-Regular" + case notoSansSymbolsRegular = "NotoSansSymbols-Regular" + case notoSansSymbols2Regular = "NotoSansSymbols2-Regular" +// case notoSansSyriacRegular = "NotoSansSyriac-Regular" + case notoSansZanabazarSquareRegular = "NotoSansZanabazarSquare-Regular" + + case notoSerifDograRegular = "NotoSerifDogra-Regular" + case notoSerifNyiakengPuachueHmongRegular = "NotoSerifNyiakengPuachueHmong-Regular" + case notoSerifTangutRegular = "NotoSerifTangut-Regular" + case notoSerifYezidiRegular = "NotoSerifYezidi-Regular" + + case chapterEmojiRegular = "ChapterEmoji-Regular" + + case lastResortRegular = "LastResort-Regular" + + static func registerAllFonts() { + for font in Self.allCases { + if let path = Bundle.main.path(forResource: font.rawValue, ofType: "ttf") { + UIFont.register(from: URL(fileURLWithPath: path)) + } else { + fatalError("No font file found for font: \(font.rawValue)") + } + } + } + + func cTFontDescriptor(withSize size: CGFloat) -> CTFontDescriptor { + var name = self.rawValue + if let postScriptName = Self.postScriptNames[self] { + name = postScriptName + } + return CTFontDescriptorCreateWithNameAndSize(name as CFString, size) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/DataFlow/Store.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/DataFlow/Store.swift new file mode 100644 index 0000000..15d1340 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/DataFlow/Store.swift @@ -0,0 +1,77 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Combine + +protocol StateType { + + associatedtype Action: ActionType + + static var initialActions: [Action] { get } + + init() + +} + +extension StateType { + + static var initialActions: [Action] { + return [] + } + +} + +protocol ActionType { + +} + +class Store: ObservableObject { + + @Published var state = State() + + typealias Reduce = ((State, State.Action) -> (State, State.Action?)) + + private(set) var reduce: Reduce + + private var disposeBag = Set() + + init(reduce: @escaping Reduce) { + self.reduce = reduce + dispatchInitialActions() + } + + private func dispatchInitialActions() { + for action in State.initialActions { + dispatch(action) + } + } + + // init() { + // setupObservers() + // } + // + // func setupObservers() { + // appState.settings.checker.isValid.sink { isValid in + // self.dispatch(.accountBehaviorButton(enabled: isValid)) + // }.store(in: &disposeBag) + // + // appState.settings.checker.isEmailValid.sink { isValid in + // self.dispatch(.emailValid(valid: isValid)) + // }.store(in: &disposeBag) + // } + + func dispatch(_ action: State.Action) { + var resState = state + var nextAction: State.Action? = action + + while let action = nextAction { + print("[ACTION]: \(action)") + let result = reduce(resState, action) + resState = result.0 + nextAction = result.1 + } + state = resState + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Block.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Block.swift new file mode 100644 index 0000000..fc1bbc3 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Block.swift @@ -0,0 +1,51 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit.UIColor + +final class Block: Codable { + + var key: String + var name: String + var type: String + var codePointStart: UInt32 + var codePointEnd: UInt32 + var categoryKey: String + var languages: String + var countries: [Country] + + var color: UIColor = .black + + private enum CodingKeys: String, CodingKey { + case key + case name + case type + case codePointStart + case codePointEnd + case categoryKey + case languages + case countries + } + +} + +extension Block: Identifiable { + + var id: String { + return key + } + +} + +extension Block { + + var characterCount: UInt32 { + return (codePointEnd - codePointStart) + 1 + } + + var formattedRange: String { + return "\(String.uPlusRepresentation(fromCodepoint: codePointStart))–\(String.uPlusRepresentation(fromCodepoint: codePointEnd))" + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Category.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Category.swift new file mode 100644 index 0000000..0f3d1a8 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Category.swift @@ -0,0 +1,40 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit.UIColor + +struct LangCategory: Codable { + + var key: String + var name: String + + private enum CodingKeys: String, CodingKey { + case key + case name + } + +// var blockKeys = [String]() + var color: UIColor = .black + +} + +//extension LangCategory { +// +// var blockCount: Int { +// return blockKeys.count +// } +// +// func indexForBlock(_ block: Block) -> Int? { +// return blockKeys.firstIndex(of: block.key) +// } +// +//} + +extension LangCategory: Identifiable { + + var id: String { + return key + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Country.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Country.swift new file mode 100644 index 0000000..db0696c --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Country.swift @@ -0,0 +1,19 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +struct Country: Codable { + + var name: String + var x: Int + var y: Int + +} + +extension Country: Identifiable { + + var id: String { + return name + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Language.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Language.swift new file mode 100644 index 0000000..63cc8c4 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Language.swift @@ -0,0 +1,10 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +struct Language: Codable { + + var id: String + var name: String + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Managed/BlockDescription.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Managed/BlockDescription.swift new file mode 100644 index 0000000..af332f3 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Managed/BlockDescription.swift @@ -0,0 +1,18 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation +import CoreData + +@objc(BlockDescription) +final class BlockDescription: NSManagedObject, Identifiable { + + @NSManaged var key: String + @NSManaged var content: String + +} + +extension BlockDescription: Managed { + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Managed/CLDRAnnotation.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Managed/CLDRAnnotation.swift new file mode 100644 index 0000000..c13129e --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Managed/CLDRAnnotation.swift @@ -0,0 +1,19 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation +import CoreData + +@objc(CLDRAnnotation) +final class CLDRAnnotation: NSManagedObject, Identifiable { + + @NSManaged var character: String + @NSManaged var shortName: String + @NSManaged var keywords: String + +} + +extension CLDRAnnotation: Managed { + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Managed/CharacterInformation.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Managed/CharacterInformation.swift new file mode 100644 index 0000000..00406e7 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Managed/CharacterInformation.swift @@ -0,0 +1,22 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation +import CoreData + +@objc(CharacterInformation) +final class CharacterInformation: NSManagedObject, Identifiable { + + @NSManaged var codepoint: NSNumber? + @NSManaged var codepointStart: NSNumber? + @NSManaged var codepointEnd: NSNumber? + @NSManaged var abbreviations: String + @NSManaged var controlNames: String + @NSManaged var correction: String + +} + +extension CharacterInformation: Managed { + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Plane.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Plane.swift new file mode 100644 index 0000000..ea975f6 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/DomainModel/Plane.swift @@ -0,0 +1,45 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +final class Plane: Codable { + + var number: Int + var name: String + var shortName: String + var introduction: String + var subIntro: String + var codePointStart: UInt32 + var codePointEnd: UInt32 { + return codePointStart + 0xFFFF + } + + var blocks = [String]() + + private enum CodingKeys: String, CodingKey { + case number + case name + case shortName + case introduction + case subIntro + case codePointStart + case blocks + } + +} + +extension Plane: Identifiable { + + var identifier: Int { + return number + } + +} + +extension Plane { + + var fullName: String { + return "\(name) (\(shortName))" + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/CharacterBottomViewModel+Types.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/CharacterBottomViewModel+Types.swift new file mode 100644 index 0000000..5293241 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/CharacterBottomViewModel+Types.swift @@ -0,0 +1,98 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation +import UIKit.UIColor + +extension CharacterBottomViewModel { + + enum Age: String, CaseIterable { + case v1_1 = "1.1" + case v2_0 = "2.0" + case v2_1 = "2.1" + case v3_0 = "3.0" + case v3_1 = "3.1" + case v3_2 = "3.2" + case v4_0 = "4.0" + case v4_1 = "4.1" + case v5_0 = "5.0" + case v5_1 = "5.1" + case v5_2 = "5.2" + case v6_0 = "6.0" + case v6_1 = "6.1" + case v6_2 = "6.2" + case v6_3 = "6.3" + case v7_0 = "7.0" + case v8_0 = "8.0" + case v9_0 = "9.0" + case v10_0 = "10.0" + case v11_0 = "11.0" + case v12_0 = "12.0" + case v12_1 = "12.1" + case v13_0 = "13.0" + + var year: String? { + switch self { + case .v1_1: + return "1993" + case .v2_0: + return "1996" + case .v2_1: + return "1998" + case .v3_0: + return "1999" + case .v3_1: + return "2001" + case .v3_2: + return "2002" + case .v4_0: + return "2003" + case .v4_1: + return "2005" + case .v5_0: + return "2006" + case .v5_1: + return "2008" + case .v5_2: + return "2009" + case .v6_0: + return "2010" + case .v6_1: + return "2012" + case .v6_2: + return "2012" + case .v6_3: + return "2013" + case .v7_0: + return "2014" + case .v8_0: + return "2015" + case .v9_0: + return "2016" + case .v10_0: + return "2017" + case .v11_0: + return "2018" + case .v12_0: + return "2019" + case .v12_1: + return "2019" + case .v13_0: + return "2020" + } + } + + var displayAge: String? { + if let year = self.year { + return "\(self.rawValue) (\(year))" + } + return nil + } + + var color: UIColor { + return Colors.commonColor(forIndex: caseIndex) + } + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/CharacterBottomViewModel.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/CharacterBottomViewModel.swift new file mode 100644 index 0000000..36bd26d --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/CharacterBottomViewModel.swift @@ -0,0 +1,180 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation +import UIKit.UIBezierPath +import CoreText + +struct CharacterBottomViewModel { + + var codePoint: UInt32 + + var blockKey: String? + + var abbreviations: [String] + var controlNames: [String] + + var renderingTrait: RenderingTrait? + + var scalar: Unicode.Scalar? { + return Unicode.Scalar(codePoint) + } + + var generalCategory: Unicode.GeneralCategory { + return scalar?.properties.generalCategory ?? .surrogate + } + + var name: String? { + return scalar?.properties.name + } + + var nameAlias: String? { + return scalar?.properties.nameAlias + } + + var isNotCharacter: Bool { + return scalar?.properties.isNoncharacterCodePoint ?? false + } + + var age: Age? { + if let age = scalar?.properties.age { + return Age(rawValue: "\(age.major).\(age.minor)") + } else { + return nil + } + } + + init( + codePoint: UInt32, + abbreviations: [String], + controlNames: [String] + ) { + self.codePoint = codePoint + self.abbreviations = abbreviations + self.controlNames = controlNames + + self.blockKey = dataProvider.blockforCodePoint(codePoint)?.0 + + if let scalar = self.scalar { + self.renderingTrait = Self.renderingTrait(forScalar: scalar, size: 16) + } + } + + init?(codepoint: UInt32, characterInformation: CharacterInformation) { + var coodpointCheckPass = false + if + let infoCodepoint = characterInformation.codepoint, + codepoint == infoCodepoint.uint32Value + { + coodpointCheckPass = true + } else if + let codepointStart = characterInformation.codepointStart, + let codepointEnd = characterInformation.codepointEnd, + codepoint >= codepointStart.uint32Value && codepoint <= codepointEnd.uint32Value + { + coodpointCheckPass = true + } + if !coodpointCheckPass { + return nil + } + + self.codePoint = codepoint + self.blockKey = dataProvider.blockforCodePoint(codepoint)?.0 + + self.abbreviations = characterInformation.abbreviations.isEmpty + ? [] + : characterInformation.abbreviations.components(separatedBy: ", ") + self.controlNames = characterInformation.controlNames.isEmpty + ? [] + : characterInformation.controlNames.components(separatedBy: ", ") + + if let scalar = self.scalar { + self.renderingTrait = Self.renderingTrait(forScalar: scalar, size: 128) + } + } + +} + +extension CharacterBottomViewModel { + + private static func renderingTrait(forScalar scalar: Unicode.Scalar, size: CGFloat) -> RenderingTrait? { + return String(scalar).renderingTrait(size: size, usesImageMode: scalar.properties.isEmojiPresentation) + } + +} + +extension CharacterBottomViewModel { + + var uPlusRepresentation: String { + String.uPlusRepresentation(fromCodepoint: codePoint) + } + + // 数据处理优化 + var displayName: String { + if let nameAlias = nameAlias { + // tested + return nameAlias//.capitalized + } else if let name = name { + // tested + return name//.capitalized + } else if !controlNames.isEmpty { + // tested + let name = controlNames.joined(separator: " / ").capitalized + let abbr = abbreviations.joined(separator: " / ") + return "\(name) (\(abbr))" + } else if generalCategory == .surrogate { + // TBT + return "Surrogate" + } else if isNotCharacter { + // TBT + return "Not a Character (\(uPlusRepresentation))" + } else if generalCategory == .unassigned { + // TBT + return "Reserved Character (\(uPlusRepresentation))" + } else if generalCategory == .privateUse { + // TBT + return "Private Use (\(uPlusRepresentation))" + } else if generalCategory == .control, !abbreviations.isEmpty { + // TBT + return "Control character (\(abbreviations.joined(separator: " / ")))" + } else { + assert(false) + return "Unnamed" + } + } + +} + +extension CharacterBottomViewModel { + + static let preview = CharacterBottomViewModel( + codePoint: 65, + abbreviations: [], + controlNames: [] + ) + +} + +extension CharacterBottomViewModel { + + static func fromCodepoint(_ codepoint: UInt32) -> Self? { + let predicate = NSPredicate( + format: """ +codepoint == %@ OR \ +((codepointStart != nil AND codepointEnd != nil) AND (%@ BETWEEN {codepointStart, codepointEnd})) +""", + codepoint as NSNumber, + codepoint as NSNumber + ) + do { + if let res = try CharacterInformation.findOrFetch(in: App.moc, matching: predicate) { + return self.init(codepoint: codepoint, characterInformation: res) + } + } catch { + print(error) + } + return nil + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/EmojiInput.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/EmojiInput.swift new file mode 100644 index 0000000..4fc4cd1 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/EmojiInput.swift @@ -0,0 +1,22 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation + +enum EmojiInputMode: String { + + case zwj + case flag + case keycap + case modifier + case variation + +} + +struct EmojiInputSection: Codable, Hashable { + + var title: String + var characters: [String] + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/Extensions/UnicodeGeneralCategory+Extensions.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/Extensions/UnicodeGeneralCategory+Extensions.swift new file mode 100644 index 0000000..e73e192 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/Extensions/UnicodeGeneralCategory+Extensions.swift @@ -0,0 +1,81 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation +import UIKit.UIColor + +extension Unicode.GeneralCategory { + + var displayName: String { + switch self { + case .uppercaseLetter: + return "Uppercase Letter" + case .lowercaseLetter: + return "Lowercase Letter" + case .titlecaseLetter: + return "Titlecase letter" + case .modifierLetter: + return "Modifier Letter" + case .otherLetter: + return "Other Letter" + case .nonspacingMark: + return "Nonspacing Mark" + case .spacingMark: + return "Spacing Mark" + case .enclosingMark: + return "Enclosing Mark" + case .decimalNumber: + return "Decimal Number" + case .letterNumber: + return "Letter Number" + case .otherNumber: + return "Other number" + case .connectorPunctuation: + return "Connector Punctuation" + case .dashPunctuation: + return "Dash Punctuation" + case .openPunctuation: + return "Open Punctuation" + case .closePunctuation: + return "Close Punctuation" + case .initialPunctuation: + return "Initial Punctuation" + case .finalPunctuation: + return "Final Punctuation" + case .otherPunctuation: + return "Other Punctuation" + case .mathSymbol: + return "Math Symbol" + case .currencySymbol: + return "Currency Symbol" + case .modifierSymbol: + return "Modifier Symbol" + case .otherSymbol: + return "Other Symbol" + case .spaceSeparator: + return "Space Separator" + case .lineSeparator: + return "Line Separator" + case .paragraphSeparator: + return "Paragraph" + case .control: + return "Control" + case .format: + return "Format" + case .surrogate: + return "Surrogate" + case .privateUse: + return "Private Use" + case .unassigned: + return "Not Assigned" + @unknown default: + return "Unknown Category" + } + } + + var color: UIColor { + return Colors.commonColor(forIndex: abs(hashValue)) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/RenderingTrait.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/RenderingTrait.swift new file mode 100644 index 0000000..49d7960 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/RenderingTrait.swift @@ -0,0 +1,18 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation + +import UIKit.UIBezierPath +import UIKit.UIImage + +struct RenderingTrait { + enum Mode { + case bezierPath(_: UIBezierPath?) + case image(_: UIImage?) + } + + var mode: Mode + var fontNames: [String] +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/SampleText.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/SampleText.swift new file mode 100644 index 0000000..f52da31 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Models/ViewModel/SampleText.swift @@ -0,0 +1,17 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation + +enum TextSampleMode: String, CaseIterable, Codable { + case sentence = "Sentence" + case paragraph = "Paragraph" + case alphabet = "Alphabet" + case symbol = "Symbol" +} + +struct TextWithLanguage: Codable, Hashable { + let lang: String + let content: String +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Traits/NibInstantiable.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Traits/NibInstantiable.swift new file mode 100644 index 0000000..30a093a --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Core/Traits/NibInstantiable.swift @@ -0,0 +1,38 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit.UINib + +protocol NibInstantiable { + + static var nibName: String { get } + static var nib: UINib { get } + +} + +protocol ReusableIdentifiable { + + static var reuseIdentifier: String { get } + +} + +extension NibInstantiable { + + static var nibName: String { + return String(describing: self) + } + + static var nib: UINib { + return UINib(nibName: nibName, bundle: nil) + } + +} + +extension ReusableIdentifiable where Self: NibInstantiable { + + static var reuseIdentifier: String { + return Self.nibName + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/CoreAnimation/CATransaction+Extensions.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/CoreAnimation/CATransaction+Extensions.swift new file mode 100644 index 0000000..0e40501 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/CoreAnimation/CATransaction+Extensions.swift @@ -0,0 +1,16 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import QuartzCore + +extension CATransaction { + + static func performWithoutImplicitAnimation(_ block: () -> Void) { + CATransaction.begin() + CATransaction.setDisableActions(true) + block() + CATransaction.commit() + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/CoreData/NSManagedObject+Extensions.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/CoreData/NSManagedObject+Extensions.swift new file mode 100644 index 0000000..7c7abfb --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/CoreData/NSManagedObject+Extensions.swift @@ -0,0 +1,69 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import CoreData + +protocol Managed: AnyObject, NSFetchRequestResult { + + static var entityName: String { get } + + static func fetch( + in context: NSManagedObjectContext, + configBlock: ((NSFetchRequest) -> Void)? + ) throws -> [Self] + + static func materializedObject( + in context: NSManagedObjectContext, + matching predicate: NSPredicate + ) -> Self? + + static func findOrFetch( + in context: NSManagedObjectContext, + matching predicate: NSPredicate + ) throws -> Self? + +} + +extension Managed where Self: NSManagedObject { + + static var entityName: String { return entity().name! } + + static func fetch( + in context: NSManagedObjectContext, + configBlock: ((NSFetchRequest) -> Void)? = nil + ) throws -> [Self] { + let request = NSFetchRequest(entityName: entityName) + configBlock?(request) + return try context.fetch(request) + } + + static func materializedObject( + in context: NSManagedObjectContext, + matching predicate: NSPredicate + ) -> Self? { + return context.registeredObjects + .filter({ !$0.isFault }) + .compactMap({ $0 as? Self }) + .first { object in + predicate.evaluate(with: object) + } + } + + static func findOrFetch( + in context: NSManagedObjectContext, + matching predicate: NSPredicate + ) throws -> Self? { + guard let object = materializedObject(in: context, matching: predicate) else { + print("[CoreData] fetched from store") + return try fetch(in: context) { request in + request.predicate = predicate + request.returnsObjectsAsFaults = false + request.fetchLimit = 1 + }.first + } + print("[CoreData] fetched from memory") + return object + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/Foundation/String+chapterEmoji.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/Foundation/String+chapterEmoji.swift new file mode 100644 index 0000000..8d7d07e --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/Foundation/String+chapterEmoji.swift @@ -0,0 +1,51 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation + +private extension Unicode.Scalar { + + func isEmojiVariationSelector() -> Bool { + return self == "\u{FE0E}" || self == "\u{FE0F}" + } + +} + +extension String { + + func removingVariationSelectors() -> String { + return Self(unicodeScalars.filter { scalar in + return !scalar.isEmojiVariationSelector() + }) + } + + func isEmojiComponent() -> Bool { + guard + self.count == 1, + let character = self.first + else { + return false + } + + if + character.unicodeScalars.count == 1, + let scalar = character.unicodeScalars.first + { + if + (0x1F9B0...0x1F9B3).contains(scalar.value) || // hair components + (0x1F1E6...0x1F1FF).contains(scalar.value) || // reginal indicator + (0x20E3 == scalar.value) // combining enclosing keycap + { + return true + } else { + return + scalar.properties.isJoinControl || + scalar.isEmojiVariationSelector() || + scalar.properties.isEmojiModifier + } + } + return false + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/Foundation/String+codePoint.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/Foundation/String+codePoint.swift new file mode 100644 index 0000000..b26629d --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/Foundation/String+codePoint.swift @@ -0,0 +1,62 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation + +extension String { + + static func character(fromCodepoint codePoint: UInt32) -> Self? { + if let scalar = Unicode.Scalar(codePoint) { + return String(Character(scalar)) + } else if codePoint >= 0xD800 && codePoint <= 0xDFFF { + let cc: [unichar] = [UInt16(codePoint)] + return NSString(characters: cc, length: cc.count) as String + } + return nil + } + + static func uPlusRepresentation(fromCodepoint codePoint: UInt32) -> String { + let hex: String + if codePoint < 0x10000 { + hex = "\(codePoint, radix: .hex, toWidth: 4)" + } else { + hex = "\(codePoint, radix: .hex, toWidth: 5)" + } + return "U+\(hex)" + } + +} + +// https://ericasadun.com/2018/12/14/more-fun-with-swift-5-string-interpolation-radix-formatting/ +extension String.StringInterpolation { + + /// Represents a single numeric radix + enum Radix: Int { + case binary = 2, octal = 8, decimal = 10, hex = 16 + + /// Returns a radix's optional prefix + var prefix: String { + return [.binary: "0b", .octal: "0o", .hex: "0x"][self, default: ""] + } + } + + /// Return padded version of the value using a specified radix + mutating func appendInterpolation(_ value: I, radix: Radix, prefix: Bool = false, toWidth width: Int = 0) { + // Values are uppercased, producing `FF` instead of `ff` + var string = String(value, radix: radix.rawValue).uppercased() + + // Strings are pre-padded with 0 to match target widths + if string.count < width { + string = String(repeating: "0", count: max(0, width - string.count)) + string + } + + // Prefixes use lower case, sourced from `String.StringInterpolation.Radix` + if prefix { + string = radix.prefix + string + } + + appendInterpolation(string) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/Foundation/String+formatLinks.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/Foundation/String+formatLinks.swift new file mode 100644 index 0000000..bf2fd5e --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/Foundation/String+formatLinks.swift @@ -0,0 +1,39 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation + +extension String { + + func formatLinks(baseAttributes: [NSAttributedString.Key: Any]) -> NSAttributedString { + // [xxx:xxx xxxxx] + let pattern = "\\[(.+?):(.+?) (.+?)\\]" + guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { + return NSAttributedString(string: self, attributes: baseAttributes) + } + + let attrString = NSMutableAttributedString(string: self, attributes: baseAttributes) + + while true { + let string = attrString.string + guard let match = regex.firstMatch(in: string, options: [], range: NSRange(0.. RenderingTrait.Mode + +} + +private struct PathTraitProcessingContext: TraitProcessingContext { + + private let path = CGMutablePath() + private let boundingBox: CGRect + + init(boundingBox: CGRect) { + self.boundingBox = boundingBox + } + + func process(run: CTRun, range: CFRange, font: CTFont) { + var position = CGPoint() + CTRunGetPositions(run, range, &position) + + var glyph = CGGlyph() + CTRunGetGlyphs(run, range, &glyph) + // Get PATH of outline + if let letter = CTFontCreatePathForGlyph(font, glyph, nil) { + path.addPath( + letter, + transform: CGAffineTransform(translationX: position.x, y: position.y) + ) + } + } + + func getTraitAndDestroy() -> RenderingTrait.Mode { + var transform = CGAffineTransform( + translationX: -boundingBox.width / 2 - boundingBox.origin.x, + y: -boundingBox.height / 2 - boundingBox.origin.y + ) + let finalPath = path.copy(using: &transform) + + var bezierPath: UIBezierPath? = nil + if let finalPath = finalPath { + bezierPath = UIBezierPath(cgPath: finalPath) + } + + return .bezierPath(bezierPath) + } + +} + +private struct ImageTraitProcessingContext: TraitProcessingContext { + + var context: CGContext! + var contextReady: Bool = false + + var boundingBox: CGRect + + init(boundingBox: CGRect) { + UIGraphicsBeginImageContextWithOptions(boundingBox.size, false, UIScreen.main.scale) + self.context = UIGraphicsGetCurrentContext() + self.contextReady = (context != nil) + self.boundingBox = boundingBox + if contextReady { + context.translateBy( + x: boundingBox.origin.x, + y: boundingBox.origin.y + boundingBox.height + ) + context.scaleBy(x: 1, y: -1) + } + } + + func process(run: CTRun, range: CFRange, font: CTFont) { + if contextReady { + CTRunDraw(run, context, range) + } + } + + func getTraitAndDestroy() -> RenderingTrait.Mode { + var image: UIImage? + if contextReady { + image = UIGraphicsGetImageFromCurrentImageContext() + } + + UIGraphicsEndImageContext() + + return .image(image) + } + +} + +extension String { + + func renderingTrait(size: CGFloat, usesImageMode: Bool) -> RenderingTrait { + var fontNames = [String]() + + let font = UIFont.cachedbookFont(ofSize: size) + + let attrString = NSAttributedString( + string: self, + attributes: [ + NSAttributedString.Key.font: font + ] + ) + + let line = CTLineCreateWithAttributedString(attrString as CFAttributedString) + let runArray = CTLineGetGlyphRuns(line) as! [CTRun] + + let boundingBox = CTLineGetBoundsWithOptions(line, []) + + let context: TraitProcessingContext = usesImageMode + ? ImageTraitProcessingContext(boundingBox: boundingBox) + : PathTraitProcessingContext(boundingBox: boundingBox) + + // for each RUN + for run in runArray { + let attributes = CTRunGetAttributes(run) as! [NSAttributedString.Key: Any] + let runFont = attributes[.font] as! CTFont + fontNames.append(CTFontCopyPostScriptName(runFont) as String) + + // for each GLYPH in run + for runGlyphIndex in 0..: View { + + let data: [ItemType] + let content: (Int, ItemType) -> ContentView + + init(_ data: [ItemType], @ViewBuilder content: @escaping (Int, ItemType) -> ContentView) { + self.data = data + self.content = content + } + + var body: some View { + ForEach(Array(self.data.enumerated()), id: \.offset) { idx, item in + self.content(idx, item) + } + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/SwiftUI/View+Modifiers.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/SwiftUI/View+Modifiers.swift new file mode 100644 index 0000000..e2761e9 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/SwiftUI/View+Modifiers.swift @@ -0,0 +1,16 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +extension View { + + func cardStyle(backgroundColor: Color = Colors.modalLevel1Background) -> some View { + return self + .padding(16) + .background(backgroundColor) + .cornerRadius(12) + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/SwiftUI/View+RoundedCorner.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/SwiftUI/View+RoundedCorner.swift new file mode 100644 index 0000000..d8d8d7c --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/SwiftUI/View+RoundedCorner.swift @@ -0,0 +1,24 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import Foundation +import SwiftUI + +struct RoundedCorner: Shape { + + var radius: CGFloat = .infinity + var corners: UIRectCorner = .allCorners + + func path(in rect: CGRect) -> Path { + let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) + return Path(path.cgPath) + } + +} + +extension View { + func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { + clipShape(RoundedCorner(radius: radius, corners: corners)) + } +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/SwiftUI/View+ifTrue.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/SwiftUI/View+ifTrue.swift new file mode 100644 index 0000000..f6557e0 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/SwiftUI/View+ifTrue.swift @@ -0,0 +1,17 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import SwiftUI + +extension View { + + func ifTrue(_ conditional: Bool, content: (Self) -> Content) -> some View { + if conditional { + return AnyView(content(self)) + } else { + return AnyView(self) + } + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/UIKit/UICollectionView+Extensions.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/UIKit/UICollectionView+Extensions.swift new file mode 100644 index 0000000..0ebb0ba --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/UIKit/UICollectionView+Extensions.swift @@ -0,0 +1,14 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit.UICollectionView + +extension UICollectionView { + + func deselectAllItems() { + indexPathsForSelectedItems? + .forEach { deselectItem(at: $0, animated: true) } + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/UIKit/UIColor+Extensions.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/UIKit/UIColor+Extensions.swift new file mode 100644 index 0000000..ab3bd71 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/UIKit/UIColor+Extensions.swift @@ -0,0 +1,67 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit.UIColor + +extension UIColor { + + static var random: UIColor { + UIColor( + red: .random(in: 0...1), + green: .random(in: 0...1), + blue: .random(in: 0...1), + alpha: 1.0 + ) + } + + convenience init?(hex: String) { + let r, g, b, a: CGFloat + if hex.hasPrefix("#") { + let start = hex.index(hex.startIndex, offsetBy: 1) + let hexColor = String(hex[start...]) + + if hexColor.count == 8 { + let scanner = Scanner(string: hexColor) + var hexNumber: UInt64 = 0 + + if scanner.scanHexInt64(&hexNumber) { + r = CGFloat((hexNumber & 0xff000000) >> 24) / 255 + g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255 + b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255 + a = CGFloat(hexNumber & 0x000000ff) / 255 + + self.init(red: r, green: g, blue: b, alpha: a) + return + } + } + } + return nil + } + + func mix(with color: UIColor, amount: CGFloat) -> Self { + var red1: CGFloat = 0 + var green1: CGFloat = 0 + var blue1: CGFloat = 0 + var alpha1: CGFloat = 0 + + var red2: CGFloat = 0 + var green2: CGFloat = 0 + var blue2: CGFloat = 0 + var alpha2: CGFloat = 0 + + getRed(&red1, green: &green1, blue: &blue1, alpha: &alpha1) + color.getRed(&red2, green: &green2, blue: &blue2, alpha: &alpha2) + + return Self( + red: red1 * CGFloat(1.0 - amount) + red2 * amount, + green: green1 * CGFloat(1.0 - amount) + green2 * amount, + blue: blue1 * CGFloat(1.0 - amount) + blue2 * amount, + alpha: alpha1 + ) + } + + func lighten(by amount: CGFloat = 0.2) -> Self { mix(with: .white, amount: amount) } + func darken(by amount: CGFloat = 0.2) -> Self { mix(with: .black, amount: amount) } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/UIKit/UIFont+Extensions.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/UIKit/UIFont+Extensions.swift new file mode 100644 index 0000000..e283c72 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/UIKit/UIFont+Extensions.swift @@ -0,0 +1,176 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit +import CoreText + +extension UIFont { + + class CacheKey: NSObject { + let name: String + let size: CGFloat + + init(name: String, size: CGFloat) { + self.name = name + self.size = size + } + + override func isEqual(_ object: Any?) -> Bool { + guard let other = object as? CacheKey else { + return false + } + return name == other.name && size == other.size + } + + override var hash: Int { + return name.hashValue ^ size.hashValue + } + } + + private static var fontCache: NSCache = { + let cache = NSCache() + cache.countLimit = 8 + return cache + }() + + static func register(from url: URL) { + if !FileManager.default.fileExists(atPath: url.path) { + fatalError("Font file not exists!") + } + + var cfError: Unmanaged? + if CTFontManagerRegisterFontsForURL(url as CFURL, .process, &cfError) != true { + let message = "Failed to register font: \(url) with error: \(String(describing: cfError))" + DebugAlert.showOnKeyWindow(message: message) + } + } + + static func cachedbookFont(ofSize size: CGFloat) -> UIFont { + if let font = Self.fontCache.object(forKey: CacheKey(name: "book", size: size)) { + return font + } + + if let font = UIFont.bookFont(ofSize: size) { + Self.fontCache.setObject(font, forKey: CacheKey(name: "book", size: size)) + return font + } else { + DebugAlert.showOnKeyWindow(message: "Failed to generate book font") + return UIFont.systemFont(ofSize: size) + } + } + + static func cachedChapterEmojiFont(ofSize size: CGFloat) -> UIFont { + if let font = Self.fontCache.object(forKey: CacheKey(name: "emoji", size: size)) { + return font + } + + if let font = UIFont.chapterEmojiFont(ofSize: size) { + Self.fontCache.setObject(font, forKey: CacheKey(name: "emoji", size: size)) + return font + } else { + DebugAlert.showOnKeyWindow(message: "Failed to generate emoji font") + return UIFont.systemFont(ofSize: size) + } + } + + private static func bookFont(ofSize size: CGFloat) -> UIFont? { + let ctFont = CTFontCreateUIFontForLanguage(.system, size, "en-US" as CFString)! + + guard + var modifiedCascadeList = CTFontCopyDefaultCascadeListForLanguages(ctFont, nil) as? [CTFontDescriptor] + else { + return nil + } + + let list = [ + AppFonts.notoSansAnatolianHieroglyphsRegular, + AppFonts.notoSansCanadianAboriginalRegular, + AppFonts.notoSansDeseretRegular, + AppFonts.notoSansElymaicRegular, + AppFonts.notoSansGeorgianRegular, + AppFonts.notoSansGunjalaGondiRegular, + AppFonts.notoSansIndicSiyaqNumbersRegular, + AppFonts.notoSansLaoRegular, + AppFonts.notoSansMalayalamRegular, + AppFonts.notoSansMasaramGondiRegular, + AppFonts.notoSansMedefaidrinRegular, + AppFonts.notoSansMongolianRegular, + AppFonts.notoSansNushuRegular, + AppFonts.notoSansOldSogdianRegular, + AppFonts.notoSansSignWritingRegular, + AppFonts.notoSansSinhalaRegular, + AppFonts.notoSansSogdianRegular, + AppFonts.notoSansSoyomboRegular, + AppFonts.notoSansSymbolsRegular, + AppFonts.notoSansSymbols2Regular, +// AppFonts.notoSansSyriacRegular, + AppFonts.notoSansZanabazarSquareRegular, + AppFonts.notoSerifDograRegular, + AppFonts.notoSerifNyiakengPuachueHmongRegular, + AppFonts.notoSerifTangutRegular, + AppFonts.notoSerifYezidiRegular, + AppFonts.notoMusicRegular, +// AppFonts.notoEmojiRegular, + AppFonts.lastResortRegular + ] + + modifiedCascadeList.append( + contentsOf: list.map({ $0.cTFontDescriptor(withSize: size) }) + ) + + let modifiedFontDescriptor = CTFontDescriptorCreateWithAttributes([ + kCTFontCascadeListAttribute: modifiedCascadeList + ] as CFDictionary) + + let finalFont = CTFontCreateWithFontDescriptor(modifiedFontDescriptor, size, nil) + + return finalFont as UIFont + } + + private static func chapterEmojiFont(ofSize size: CGFloat) -> UIFont? { + let ctFont = CTFontCreateUIFontForLanguage(.system, size, "en-US" as CFString)! + + guard + var modifiedCascadeList = CTFontCopyDefaultCascadeListForLanguages(ctFont, nil) as? [CTFontDescriptor] + else { + return nil + } + + modifiedCascadeList = modifiedCascadeList.filter { descriptor in + if let name = CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute) as? String { + return name.lowercased().contains("emoji") || name.lowercased().contains("symbol") + } + return false + } + + modifiedCascadeList.insert( + AppFonts.chapterEmojiRegular.cTFontDescriptor(withSize: size), + at: 0 + ) + + modifiedCascadeList.append( + contentsOf: [ + AppFonts.notoEmojiRegular.cTFontDescriptor(withSize: size), + AppFonts.notoSansSymbolsRegular.cTFontDescriptor(withSize: size), + AppFonts.notoSansSymbols2Regular.cTFontDescriptor(withSize: size), + AppFonts.lastResortRegular.cTFontDescriptor(withSize: size) + ] + ) + + let modifiedFontDescriptor = CTFontDescriptorCreateWithAttributes([ + kCTFontCascadeListAttribute: modifiedCascadeList + ] as CFDictionary) + + let finalFont = CTFontCreateWithFontDescriptor(modifiedFontDescriptor, size, nil) + +// print(modifiedFontDescriptor) + + return finalFont as UIFont + } + + static func purgeCache() { + fontCache.removeAllObjects() + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/UIKit/UIImage+backgroundColor.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/UIKit/UIImage+backgroundColor.swift new file mode 100644 index 0000000..f1c10b6 --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/UIKit/UIImage+backgroundColor.swift @@ -0,0 +1,23 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit.UIImage + +extension UIImage { + + func opaqueBackground(color: UIColor) -> UIImage { + UIGraphicsBeginImageContextWithOptions(size, true, scale) + defer { + UIGraphicsEndImageContext() + } + + let imageRect = CGRect(origin: .zero, size: size) + color.set() + UIRectFill(imageRect) + draw(in: imageRect) + + return UIGraphicsGetImageFromCurrentImageContext() ?? self + } + +} diff --git a/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/UIKit/UIViewController+Extensions.swift b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/UIKit/UIViewController+Extensions.swift new file mode 100644 index 0000000..58c1e5e --- /dev/null +++ b/PlaygroundBook/Modules/BookCore.playgroundmodule/Sources/Extensions/UIKit/UIViewController+Extensions.swift @@ -0,0 +1,15 @@ +/* + * Copyright © 2021 Ethan Wong. Licensed under MIT. + */ + +import UIKit.UIViewController + +extension UIViewController { + + func addChild(_ childViewController: UIViewController, closure: (UIViewController) -> Void) { + addChild(childViewController) + closure(childViewController) + childViewController.didMove(toParent: self) + } + +} diff --git a/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/Algorithm.swift b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/Algorithm.swift new file mode 100644 index 0000000..213add3 --- /dev/null +++ b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/Algorithm.swift @@ -0,0 +1,688 @@ +// swiftlint:disable cyclomatic_complexity + +public extension StagedChangeset where Collection: RangeReplaceableCollection, Collection.Element: Differentiable { + /// Creates a new `StagedChangeset` from the two collections. + /// + /// Calculate the differences between the collections using + /// the algorithm optimized based on the Paul Heckel's diff algorithm. + /// + /// - Note: This algorithm can compute the differences at high performance with O(n) complexity. + /// However, not always calculates the shortest differences. + /// + /// - Note: If the elements with the same identifier duplicated, the algorithm calculates + /// the moves at best effort, and rest of the duplicates as insertion or deletion. + /// + /// - Note: The data and changes each changeset contains are represents the middle of whole the changes. + /// Each changes are from the previous stage. + /// + /// - Parameters: + /// - source: A source collection to calculate differences. + /// - target: A target collection to calculate differences. + /// + /// - Complexity: O(n) + @inlinable + init(source: Collection, target: Collection) { + self.init(source: source, target: target, section: 0) + } + + /// Creates a new `StagedChangeset` from the two collections. + /// + /// Calculate the differences between the collections using + /// the algorithm optimized based on the Paul Heckel's diff algorithm. + /// + /// - Note: This algorithm can compute the differences at high performance with O(n) complexity. + /// However, not always calculates the shortest differences. + /// + /// - Note: If the elements with the same identifier duplicated, the algorithm calculates + /// the moves at best effort, and rest of the duplicates as insertion or deletion. + /// + /// - Note: The data and changes each changeset contains are represents the middle of whole the changes. + /// Each changes are from the previous stage. + /// + /// - Parameters: + /// - source: A source collection to calculate differences. + /// - target: A target collection to calculate differences. + /// - section: An Int value to use as section index (or offset) of element. + /// + /// - Complexity: O(n) + @inlinable + init(source: Collection, target: Collection, section: Int) { + let sourceElements = ContiguousArray(source) + let targetElements = ContiguousArray(target) + + // Return empty changesets if both are empty. + if sourceElements.isEmpty && targetElements.isEmpty { + self.init() + return + } + + // Return changesets that all deletions if source is not empty and target is empty. + if !sourceElements.isEmpty && targetElements.isEmpty { + self.init([Changeset(data: target, elementDeleted: sourceElements.indices.map { ElementPath(element: $0, section: section) })]) + return + } + + // Return changesets that all insertions if source is empty and target is not empty. + if sourceElements.isEmpty && !targetElements.isEmpty { + self.init([Changeset(data: target, elementInserted: targetElements.indices.map { ElementPath(element: $0, section: section) })]) + return + } + + var firstStageElements = ContiguousArray() + var secondStageElements = ContiguousArray() + + let result = diff( + source: sourceElements, + target: targetElements, + useTargetIndexForUpdated: false, + mapIndex: { ElementPath(element: $0, section: section) }, + updatedElementsPointer: &firstStageElements, + notDeletedElementsPointer: &secondStageElements + ) + + var changesets = ContiguousArray>() + + // The 1st stage changeset. + // - Includes: + // - element updates + if !result.updated.isEmpty { + changesets.append( + Changeset( + data: Collection(firstStageElements), + elementUpdated: result.updated + ) + ) + } + + // The 2nd stage changeset. + // - Includes: + // - element deletes + if !result.deleted.isEmpty { + changesets.append( + Changeset( + data: Collection(secondStageElements), + elementDeleted: result.deleted + ) + ) + } + + // The 3rd stage changeset. + // - Includes: + // - element inserts + // - element moves + if !result.inserted.isEmpty || !result.moved.isEmpty { + changesets.append( + Changeset( + data: target, + elementInserted: result.inserted, + elementMoved: result.moved + ) + ) + } + + // Set the target to `data` of the last stage. + if !changesets.isEmpty { + let index = changesets.index(before: changesets.endIndex) + changesets[index].data = target + } + + self.init(changesets) + } +} + +public extension StagedChangeset where Collection: RangeReplaceableCollection, Collection.Element: DifferentiableSection { + /// Creates a new `StagedChangeset` from the two sectioned collections. + /// + /// Calculate the differences between the collections using + /// the algorithm optimized based on the Paul Heckel's diff algorithm. + /// + /// - Note: This algorithm can compute the differences at high performance with O(n) complexity. + /// However, not always calculates the shortest differences. + /// + /// - Note: If the elements with the same identifier duplicated, the algorithm calculates + /// the moves at best effort, and rest of the duplicates as insertion or deletion. + /// + /// - Note: The data and changes each changeset contains are represents the middle of whole the changes. + /// Each changes are from the previous stage. + /// + /// - Parameters: + /// - source: A source sectioned collection to calculate differences. + /// - target: A target sectioned collection to calculate differences. + /// + /// - Complexity: O(n) + @inlinable + init(source: Collection, target: Collection) { + typealias Section = Collection.Element + typealias SectionIdentifier = Collection.Element.DifferenceIdentifier + typealias Element = Collection.Element.Collection.Element + typealias ElementIdentifier = Collection.Element.Collection.Element.DifferenceIdentifier + + let sourceSections = ContiguousArray(source) + let targetSections = ContiguousArray(target) + + let contiguousSourceSections = ContiguousArray(sourceSections.map { ContiguousArray($0.elements) }) + let contiguousTargetSections = ContiguousArray(targetSections.map { ContiguousArray($0.elements) }) + + var firstStageSections = sourceSections + var secondStageSections = ContiguousArray
() + var thirdStageSections = ContiguousArray
() + var fourthStageSections = ContiguousArray
() + + var sourceElementTraces = contiguousSourceSections.map { section in + ContiguousArray(repeating: Trace(), count: section.count) + } + var targetElementReferences = contiguousTargetSections.map { section in + ContiguousArray(repeating: nil, count: section.count) + } + + let flattenSourceCount = contiguousSourceSections.reduce(into: 0) { $0 += $1.count } + var flattenSourceIdentifiers = ContiguousArray() + var flattenSourceElementPaths = ContiguousArray() + + thirdStageSections.reserveCapacity(contiguousTargetSections.count) + fourthStageSections.reserveCapacity(contiguousTargetSections.count) + + flattenSourceIdentifiers.reserveCapacity(flattenSourceCount) + flattenSourceElementPaths.reserveCapacity(flattenSourceCount) + + // Calculate section differences. + + let sectionResult = diff( + source: sourceSections, + target: targetSections, + useTargetIndexForUpdated: true, + mapIndex: { $0 } + ) + + // Calculate element differences. + + var elementDeleted = [ElementPath]() + var elementInserted = [ElementPath]() + var elementUpdated = [ElementPath]() + var elementMoved = [(source: ElementPath, target: ElementPath)]() + + for sourceSectionIndex in contiguousSourceSections.indices { + for sourceElementIndex in contiguousSourceSections[sourceSectionIndex].indices { + let sourceElementPath = ElementPath(element: sourceElementIndex, section: sourceSectionIndex) + let sourceElement = contiguousSourceSections[sourceElementPath] + flattenSourceIdentifiers.append(sourceElement.differenceIdentifier) + flattenSourceElementPaths.append(sourceElementPath) + } + } + + flattenSourceIdentifiers.withUnsafeBufferPointer { bufferPointer in + // The pointer and the table key are for optimization. + var sourceOccurrencesTable = [TableKey: Occurrence](minimumCapacity: flattenSourceCount) + + // Track indices of elements found in flatten source collection into occurrences table. + for flattenSourceIndex in flattenSourceIdentifiers.indices { + let pointer = bufferPointer.baseAddress!.advanced(by: flattenSourceIndex) + let key = TableKey(pointer: pointer) + + switch sourceOccurrencesTable[key] { + case .none: + sourceOccurrencesTable[key] = .unique(index: flattenSourceIndex) + + case .unique(let otherIndex)?: + let reference = IndicesReference([otherIndex, flattenSourceIndex]) + sourceOccurrencesTable[key] = .duplicate(reference: reference) + + case .duplicate(let reference)?: + reference.push(flattenSourceIndex) + } + } + + // Track target and source indices of the elements having same identifier. + for targetSectionIndex in contiguousTargetSections.indices { + let targetElements = contiguousTargetSections[targetSectionIndex] + + for targetElementIndex in targetElements.indices { + var targetIdentifier = targetElements[targetElementIndex].differenceIdentifier + let key = TableKey(pointer: &targetIdentifier) + + switch sourceOccurrencesTable[key] { + case .none: + break + + case .unique(let flattenSourceIndex)?: + let sourceElementPath = flattenSourceElementPaths[flattenSourceIndex] + let targetElementPath = ElementPath(element: targetElementIndex, section: targetSectionIndex) + + if case .none = sourceElementTraces[sourceElementPath].reference { + targetElementReferences[targetElementPath] = sourceElementPath + sourceElementTraces[sourceElementPath].reference = targetElementPath + } + + case .duplicate(let reference)?: + if let flattenSourceIndex = reference.next() { + let sourceElementPath = flattenSourceElementPaths[flattenSourceIndex] + let targetElementPath = ElementPath(element: targetElementIndex, section: targetSectionIndex) + targetElementReferences[targetElementPath] = sourceElementPath + sourceElementTraces[sourceElementPath].reference = targetElementPath + } + } + } + } + } + + // Track element deletes. + for sourceSectionIndex in contiguousSourceSections.indices { + let sourceSection = sourceSections[sourceSectionIndex] + let sourceElements = contiguousSourceSections[sourceSectionIndex] + var firstStageElements = sourceElements + + // Should not track element deletes in the deleted section. + if case .some = sectionResult.sourceTraces[sourceSectionIndex].reference { + var offsetByDelete = 0 + + var secondStageElements = ContiguousArray() + + for sourceElementIndex in sourceElements.indices { + let sourceElementPath = ElementPath(element: sourceElementIndex, section: sourceSectionIndex) + + sourceElementTraces[sourceElementPath].deleteOffset = offsetByDelete + + // Track element deletes if target section is tracked as inserts. + if let targetElementPath = sourceElementTraces[sourceElementPath].reference, + case .some = sectionResult.targetReferences[targetElementPath.section] { + let targetElement = contiguousTargetSections[targetElementPath] + firstStageElements[sourceElementIndex] = targetElement + secondStageElements.append(targetElement) + continue + } + + elementDeleted.append(sourceElementPath) + sourceElementTraces[sourceElementPath].isTracked = true + offsetByDelete += 1 + } + + let secondStageSection = Section(source: sourceSection, elements: secondStageElements) + secondStageSections.append(secondStageSection) + + } + + let firstStageSection = Section(source: sourceSection, elements: firstStageElements) + firstStageSections[sourceSectionIndex] = firstStageSection + } + + // Track element updates / moves / inserts. + for targetSectionIndex in contiguousTargetSections.indices { + // Should not track element updates / moves / inserts in the inserted section. + guard let sourceSectionIndex = sectionResult.targetReferences[targetSectionIndex] else { + thirdStageSections.append(targetSections[targetSectionIndex]) + fourthStageSections.append(targetSections[targetSectionIndex]) + continue + } + + var untrackedSourceIndex: Int? = 0 + let targetElements = contiguousTargetSections[targetSectionIndex] + + let sectionDeleteOffset = sectionResult.sourceTraces[sourceSectionIndex].deleteOffset + + let thirdStageSection = secondStageSections[sourceSectionIndex - sectionDeleteOffset] + thirdStageSections.append(thirdStageSection) + + var fourthStageElements = ContiguousArray() + fourthStageElements.reserveCapacity(targetElements.count) + + for targetElementIndex in targetElements.indices { + untrackedSourceIndex = untrackedSourceIndex.flatMap { index in + sourceElementTraces[sourceSectionIndex].suffix(from: index).firstIndex { !$0.isTracked } + } + + let targetElementPath = ElementPath(element: targetElementIndex, section: targetSectionIndex) + let targetElement = contiguousTargetSections[targetElementPath] + + // Track element inserts if source section is tracked as deletes. + guard let sourceElementPath = targetElementReferences[targetElementPath], + let movedSourceSectionIndex = sectionResult.sourceTraces[sourceElementPath.section].reference else { + fourthStageElements.append(targetElement) + elementInserted.append(targetElementPath) + continue + } + + sourceElementTraces[sourceElementPath].isTracked = true + + let sourceElement = contiguousSourceSections[sourceElementPath] + fourthStageElements.append(targetElement) + + if !targetElement.isContentEqual(to: sourceElement) { + elementUpdated.append(sourceElementPath) + } + + if sourceElementPath.section != sourceSectionIndex || sourceElementPath.element != untrackedSourceIndex { + let deleteOffset = sourceElementTraces[sourceElementPath].deleteOffset + let moveSourceElementPath = ElementPath(element: sourceElementPath.element - deleteOffset, section: movedSourceSectionIndex) + elementMoved.append((source: moveSourceElementPath, target: targetElementPath)) + } + } + + let fourthStageSection = Section(source: thirdStageSection, elements: fourthStageElements) + fourthStageSections.append(fourthStageSection) + } + + var changesets = ContiguousArray>() + + // The 1st stage changeset. + // - Includes: + // - element updates + if !elementUpdated.isEmpty { + changesets.append( + Changeset( + data: Collection(firstStageSections), + elementUpdated: elementUpdated + ) + ) + } + + // The 2nd stage changeset. + // - Includes: + // - section deletes + // - element deletes + if !sectionResult.deleted.isEmpty || !elementDeleted.isEmpty { + changesets.append( + Changeset( + data: Collection(secondStageSections), + sectionDeleted: sectionResult.deleted, + elementDeleted: elementDeleted + ) + ) + } + + // The 3rd stage changeset. + // - Includes: + // - section inserts + // - section moves + if !sectionResult.inserted.isEmpty || !sectionResult.moved.isEmpty { + changesets.append( + Changeset( + data: Collection(thirdStageSections), + sectionInserted: sectionResult.inserted, + sectionMoved: sectionResult.moved + ) + ) + } + + // The 4th stage changeset. + // - Includes: + // - element inserts + // - element moves + if !elementInserted.isEmpty || !elementMoved.isEmpty { + changesets.append( + Changeset( + data: Collection(fourthStageSections), + elementInserted: elementInserted, + elementMoved: elementMoved + ) + ) + } + + // The 5th stage changeset. + // - Includes: + // - section updates + if !sectionResult.updated.isEmpty { + changesets.append( + Changeset( + data: target, + sectionUpdated: sectionResult.updated + ) + ) + } + + // Set the target to `data` of the last stage. + if !changesets.isEmpty { + let index = changesets.index(before: changesets.endIndex) + changesets[index].data = target + } + + self.init(changesets) + } +} + +/// The shared algorithm to calculate diffs between two linear collections. +@inlinable +@discardableResult +internal func diff( + source: ContiguousArray, + target: ContiguousArray, + useTargetIndexForUpdated: Bool, + mapIndex: (Int) -> I, + updatedElementsPointer: UnsafeMutablePointer>? = nil, + notDeletedElementsPointer: UnsafeMutablePointer>? = nil + ) -> DiffResult { + var deleted = [I]() + var inserted = [I]() + var updated = [I]() + var moved = [(source: I, target: I)]() + + var sourceTraces = ContiguousArray>() + var sourceIdentifiers = ContiguousArray() + var targetReferences = ContiguousArray(repeating: nil, count: target.count) + + sourceTraces.reserveCapacity(source.count) + sourceIdentifiers.reserveCapacity(source.count) + + for sourceElement in source { + sourceTraces.append(Trace()) + sourceIdentifiers.append(sourceElement.differenceIdentifier) + } + + sourceIdentifiers.withUnsafeBufferPointer { bufferPointer in + // The pointer and the table key are for optimization. + var sourceOccurrencesTable = [TableKey: Occurrence](minimumCapacity: source.count) + + // Track indices of elements found in source collection into occurrences table. + for sourceIndex in sourceIdentifiers.indices { + let pointer = bufferPointer.baseAddress!.advanced(by: sourceIndex) + let key = TableKey(pointer: pointer) + + switch sourceOccurrencesTable[key] { + case .none: + sourceOccurrencesTable[key] = .unique(index: sourceIndex) + + case .unique(let otherIndex)?: + let reference = IndicesReference([otherIndex, sourceIndex]) + sourceOccurrencesTable[key] = .duplicate(reference: reference) + + case .duplicate(let reference)?: + reference.push(sourceIndex) + } + } + + // Track target and source indices of the elements having same identifier. + for targetIndex in target.indices { + var targetIdentifier = target[targetIndex].differenceIdentifier + let key = TableKey(pointer: &targetIdentifier) + + switch sourceOccurrencesTable[key] { + case .none: + break + + case .unique(let sourceIndex)?: + if case .none = sourceTraces[sourceIndex].reference { + targetReferences[targetIndex] = sourceIndex + sourceTraces[sourceIndex].reference = targetIndex + } + + case .duplicate(let reference)?: + if let sourceIndex = reference.next() { + targetReferences[targetIndex] = sourceIndex + sourceTraces[sourceIndex].reference = targetIndex + } + } + } + } + + var offsetByDelete = 0 + var untrackedSourceIndex: Int? = 0 + + // Track deletes. + for sourceIndex in source.indices { + sourceTraces[sourceIndex].deleteOffset = offsetByDelete + + if let targetIndex = sourceTraces[sourceIndex].reference { + let targetElement = target[targetIndex] + updatedElementsPointer?.pointee.append(targetElement) + notDeletedElementsPointer?.pointee.append(targetElement) + } + else { + let sourceElement = source[sourceIndex] + deleted.append(mapIndex(sourceIndex)) + sourceTraces[sourceIndex].isTracked = true + offsetByDelete += 1 + updatedElementsPointer?.pointee.append(sourceElement) + } + } + + // Track updates / moves / inserts. + for targetIndex in target.indices { + untrackedSourceIndex = untrackedSourceIndex.flatMap { index in + sourceTraces.suffix(from: index).firstIndex { !$0.isTracked } + } + + if let sourceIndex = targetReferences[targetIndex] { + sourceTraces[sourceIndex].isTracked = true + + let sourceElement = source[sourceIndex] + let targetElement = target[targetIndex] + + if !targetElement.isContentEqual(to: sourceElement) { + updated.append(mapIndex(useTargetIndexForUpdated ? targetIndex : sourceIndex)) + } + + if sourceIndex != untrackedSourceIndex { + let deleteOffset = sourceTraces[sourceIndex].deleteOffset + moved.append((source: mapIndex(sourceIndex - deleteOffset), target: mapIndex(targetIndex))) + } + } + else { + inserted.append(mapIndex(targetIndex)) + } + } + + return DiffResult( + deleted: deleted, + inserted: inserted, + updated: updated, + moved: moved, + sourceTraces: sourceTraces, + targetReferences: targetReferences + ) +} + +/// A set of changes and metadata as a result of calculating differences in linear collection. +@usableFromInline +internal struct DiffResult { + @usableFromInline + internal let deleted: [Index] + @usableFromInline + internal let inserted: [Index] + @usableFromInline + internal let updated: [Index] + @usableFromInline + internal let moved: [(source: Index, target: Index)] + @usableFromInline + internal let sourceTraces: ContiguousArray> + @usableFromInline + internal let targetReferences: ContiguousArray + + @usableFromInline + internal init( + deleted: [Index] = [], + inserted: [Index] = [], + updated: [Index] = [], + moved: [(source: Index, target: Index)] = [], + sourceTraces: ContiguousArray>, + targetReferences: ContiguousArray + ) { + self.deleted = deleted + self.inserted = inserted + self.updated = updated + self.moved = moved + self.sourceTraces = sourceTraces + self.targetReferences = targetReferences + } +} + +/// A set of informations in middle of difference calculation. +@usableFromInline +internal struct Trace { + @usableFromInline + internal var reference: Index? + @usableFromInline + internal var deleteOffset = 0 + @usableFromInline + internal var isTracked = false + + @usableFromInline + internal init() {} +} + +/// The occurrences of element. +@usableFromInline +internal enum Occurrence { + case unique(index: Int) + case duplicate(reference: IndicesReference) +} + +/// A mutable reference to indices of elements. +@usableFromInline +internal final class IndicesReference { + @usableFromInline + internal var indices: ContiguousArray + @usableFromInline + internal var position = 0 + + @usableFromInline + internal init(_ indices: ContiguousArray) { + self.indices = indices + } + + @inlinable + internal func push(_ index: Int) { + indices.append(index) + } + + @inlinable + internal func next() -> Int? { + guard position < indices.endIndex else { + return nil + } + defer { position += 1 } + return indices[position] + } +} + +/// Dictionary key using UnsafePointer for performance optimization. +@usableFromInline +internal struct TableKey: Hashable { + @usableFromInline + internal let pointeeHashValue: Int + @usableFromInline + internal let pointer: UnsafePointer + + @usableFromInline + internal init(pointer: UnsafePointer) { + self.pointeeHashValue = pointer.pointee.hashValue + self.pointer = pointer + } + + @inlinable + internal static func == (lhs: TableKey, rhs: TableKey) -> Bool { + return lhs.pointeeHashValue == rhs.pointeeHashValue + && (lhs.pointer.distance(to: rhs.pointer) == 0 || lhs.pointer.pointee == rhs.pointer.pointee) + } + + @inlinable + internal func hash(into hasher: inout Hasher) { + hasher.combine(pointeeHashValue) + } +} + +internal extension MutableCollection where Element: MutableCollection, Index == Int, Element.Index == Int { + @inlinable + subscript(path: ElementPath) -> Element.Element { + get { return self[path.section][path.element] } + set { self[path.section][path.element] = newValue } + } +} diff --git a/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/AnyDifferentiable.swift b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/AnyDifferentiable.swift new file mode 100644 index 0000000..e9c7bf3 --- /dev/null +++ b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/AnyDifferentiable.swift @@ -0,0 +1,108 @@ +/// A type-erased differentiable value. +/// +/// The `AnyDifferentiable` type hides the specific underlying types. +/// Associated type `DifferenceIdentifier` is erased by `AnyHashable`. +/// The comparisons of whether has updated is forwards to an underlying differentiable value. +/// +/// You can store mixed-type elements in collection that require `Differentiable` conformance by +/// wrapping mixed-type elements in `AnyDifferentiable`: +/// +/// extension String: Differentiable {} +/// extension Int: Differentiable {} +/// +/// let source = [ +/// AnyDifferentiable("ABC"), +/// AnyDifferentiable(100) +/// ] +/// let target = [ +/// AnyDifferentiable("ABC"), +/// AnyDifferentiable(100), +/// AnyDifferentiable(200) +/// ] +/// +/// let changeset = StagedChangeset(source: source, target: target) +/// print(changeset.isEmpty) // prints "false" +public struct AnyDifferentiable: Differentiable { + /// The value wrapped by this instance. + @inlinable + public var base: Any { + return box.base + } + + /// A type-erased identifier value for difference calculation. + @inlinable + public var differenceIdentifier: AnyHashable { + return box.differenceIdentifier + } + + @usableFromInline + internal let box: AnyDifferentiableBox + + /// Creates a type-erased differentiable value that wraps the given instance. + /// + /// - Parameters: + /// - base: A differentiable value to wrap. + public init(_ base: D) { + if let anyDifferentiable = base as? AnyDifferentiable { + self = anyDifferentiable + } + else { + box = DifferentiableBox(base) + } + } + + /// Indicate whether the content of `base` is equals to the content of the given source value. + /// + /// - Parameters: + /// - source: A source value to be compared. + /// + /// - Returns: A Boolean value indicating whether the content of `base` is equals + /// to the content of `base` of the given source value. + @inlinable + public func isContentEqual(to source: AnyDifferentiable) -> Bool { + return box.isContentEqual(to: source.box) + } +} + +extension AnyDifferentiable: CustomDebugStringConvertible { + public var debugDescription: String { + return "AnyDifferentiable(\(String(reflecting: base)))" + } +} + +@usableFromInline +internal protocol AnyDifferentiableBox { + var base: Any { get } + var differenceIdentifier: AnyHashable { get } + + func isContentEqual(to source: AnyDifferentiableBox) -> Bool +} + +@usableFromInline +internal struct DifferentiableBox: AnyDifferentiableBox { + @usableFromInline + internal let baseComponent: Base + + @inlinable + internal var base: Any { + return baseComponent + } + + @inlinable + internal var differenceIdentifier: AnyHashable { + return baseComponent.differenceIdentifier + } + + @usableFromInline + internal init(_ base: Base) { + baseComponent = base + } + + @inlinable + internal func isContentEqual(to source: AnyDifferentiableBox) -> Bool { + guard let sourceBase = source.base as? Base else { + return false + } + return baseComponent.isContentEqual(to: sourceBase) + } +} diff --git a/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/ArraySection.swift b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/ArraySection.swift new file mode 100644 index 0000000..0a7e2ab --- /dev/null +++ b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/ArraySection.swift @@ -0,0 +1,68 @@ +/// A differentiable section with model and array of elements. +/// +/// Arrays are can not be identify each one and comparing whether has updated from other one. +/// ArraySection is a generic wrapper to hold a model to allow it. +public struct ArraySection: DifferentiableSection { + /// The model of section for differentiated with other section. + public var model: Model + /// The array of element in the section. + public var elements: [Element] + + /// An identifier value that of model for difference calculation. + @inlinable + public var differenceIdentifier: Model.DifferenceIdentifier { + return model.differenceIdentifier + } + + /// Creates a section with the model and the elements. + /// + /// - Parameters: + /// - model: A differentiable model of section. + /// - elements: The collection of element in the section. + public init(model: Model, elements: C) where C.Element == Element { + self.model = model + self.elements = Array(elements) + } + + /// Creates a new section reproducing the given source section with replacing the elements. + /// + /// - Parameters: + /// - source: A source section to reproduce. + /// - elements: The collection of elements for the new section. + @inlinable + public init(source: ArraySection, elements: C) where C.Element == Element { + self.init(model: source.model, elements: elements) + } + + /// Indicate whether the content of `self` is equals to the content of + /// the given source section. + /// + /// - Note: It's compared by the model of `self` and the specified section. + /// + /// - Parameters: + /// - source: A source section to compare. + /// + /// - Returns: A Boolean value indicating whether the content of `self` is equals + /// to the content of the given source section. + @inlinable + public func isContentEqual(to source: ArraySection) -> Bool { + return model.isContentEqual(to: source.model) + } +} + +extension ArraySection: Equatable where Model: Equatable, Element: Equatable { + public static func == (lhs: ArraySection, rhs: ArraySection) -> Bool { + return lhs.model == rhs.model && lhs.elements == rhs.elements + } +} + +extension ArraySection: CustomDebugStringConvertible { + public var debugDescription: String { + return """ + ArraySection( + model: \(model), + elements: \(elements) + ) + """ + } +} diff --git a/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/Changeset.swift b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/Changeset.swift new file mode 100644 index 0000000..55b8e9e --- /dev/null +++ b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/Changeset.swift @@ -0,0 +1,160 @@ +/// A set of changes in the sectioned collection. +/// +/// Changes to the section of the linear collection should be empty. +/// +/// Notice that the value of the changes represents offsets of collection not index. +/// Since offsets are unordered, order is ignored when comparing two `Changeset`s. +public struct Changeset { + /// The collection after changed. + public var data: Collection + + /// The offsets of deleted sections. + public var sectionDeleted: [Int] + /// The offsets of inserted sections. + public var sectionInserted: [Int] + /// The offsets of updated sections. + public var sectionUpdated: [Int] + /// The pairs of source and target offset of moved sections. + public var sectionMoved: [(source: Int, target: Int)] + + /// The paths of deleted elements. + public var elementDeleted: [ElementPath] + /// The paths of inserted elements. + public var elementInserted: [ElementPath] + /// The paths of updated elements. + public var elementUpdated: [ElementPath] + /// The pairs of source and target path of moved elements. + public var elementMoved: [(source: ElementPath, target: ElementPath)] + + /// Creates a new `Changeset`. + /// + /// - Parameters: + /// - data: The collection after changed. + /// - sectionDeleted: The offsets of deleted sections. + /// - sectionInserted: The offsets of inserted sections. + /// - sectionUpdated: The offsets of updated sections. + /// - sectionMoved: The pairs of source and target offset of moved sections. + /// - elementDeleted: The paths of deleted elements. + /// - elementInserted: The paths of inserted elements. + /// - elementUpdated: The paths of updated elements. + /// - elementMoved: The pairs of source and target path of moved elements. + public init( + data: Collection, + sectionDeleted: [Int] = [], + sectionInserted: [Int] = [], + sectionUpdated: [Int] = [], + sectionMoved: [(source: Int, target: Int)] = [], + elementDeleted: [ElementPath] = [], + elementInserted: [ElementPath] = [], + elementUpdated: [ElementPath] = [], + elementMoved: [(source: ElementPath, target: ElementPath)] = [] + ) { + self.data = data + self.sectionDeleted = sectionDeleted + self.sectionInserted = sectionInserted + self.sectionUpdated = sectionUpdated + self.sectionMoved = sectionMoved + self.elementDeleted = elementDeleted + self.elementInserted = elementInserted + self.elementUpdated = elementUpdated + self.elementMoved = elementMoved + } +} + +public extension Changeset { + /// The number of section changes. + @inlinable + var sectionChangeCount: Int { + return sectionDeleted.count + + sectionInserted.count + + sectionUpdated.count + + sectionMoved.count + } + + /// The number of element changes. + @inlinable + var elementChangeCount: Int { + return elementDeleted.count + + elementInserted.count + + elementUpdated.count + + elementMoved.count + } + + /// The number of all changes. + @inlinable + var changeCount: Int { + return sectionChangeCount + elementChangeCount + } + + /// A Boolean value indicating whether has section changes. + @inlinable + var hasSectionChanges: Bool { + return sectionChangeCount > 0 + } + + /// A Boolean value indicating whether has element changes. + @inlinable + var hasElementChanges: Bool { + return elementChangeCount > 0 + } + + /// A Boolean value indicating whether has changes. + @inlinable + var hasChanges: Bool { + return changeCount > 0 + } +} + +extension Changeset: Equatable where Collection: Equatable { + public static func == (lhs: Changeset, rhs: Changeset) -> Bool { + return lhs.data == rhs.data + && Set(lhs.sectionDeleted) == Set(rhs.sectionDeleted) + && Set(lhs.sectionInserted) == Set(rhs.sectionInserted) + && Set(lhs.sectionUpdated) == Set(rhs.sectionUpdated) + && Set(lhs.sectionMoved.map(HashablePair.init)) == Set(rhs.sectionMoved.map(HashablePair.init)) + && Set(lhs.elementDeleted) == Set(rhs.elementDeleted) + && Set(lhs.elementInserted) == Set(rhs.elementInserted) + && Set(lhs.elementUpdated) == Set(rhs.elementUpdated) + && Set(lhs.elementMoved.map(HashablePair.init)) == Set(rhs.elementMoved.map(HashablePair.init)) + } +} + +extension Changeset: CustomDebugStringConvertible { + public var debugDescription: String { + guard !data.isEmpty || hasChanges else { + return """ + Changeset( + data: [] + )" + """ + } + + var description = """ + Changeset( + data: \(data.isEmpty ? "[]" : "[\n \(data.map { "\($0)" }.joined(separator: ",\n").split(separator: "\n").joined(separator: "\n "))\n ]") + """ + + func appendDescription(name: String, elements: [T]) { + guard !elements.isEmpty else { return } + + description += ",\n \(name): [\n \(elements.map { "\($0)" }.joined(separator: ",\n").split(separator: "\n").joined(separator: "\n "))\n ]" + } + + appendDescription(name: "sectionDeleted", elements: sectionDeleted) + appendDescription(name: "sectionInserted", elements: sectionInserted) + appendDescription(name: "sectionUpdated", elements: sectionUpdated) + appendDescription(name: "sectionMoved", elements: sectionMoved) + appendDescription(name: "elementDeleted", elements: elementDeleted) + appendDescription(name: "elementInserted", elements: elementInserted) + appendDescription(name: "elementUpdated", elements: elementUpdated) + appendDescription(name: "elementMoved", elements: elementMoved) + + description += "\n)" + return description + } +} + +private struct HashablePair: Hashable { + let first: H + let second: H +} diff --git a/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/ContentEquatable.swift b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/ContentEquatable.swift new file mode 100644 index 0000000..7ba853b --- /dev/null +++ b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/ContentEquatable.swift @@ -0,0 +1,52 @@ +/// Represents a value that can compare whether the content are equal. +public protocol ContentEquatable { + /// Indicate whether the content of `self` is equals to the content of + /// the given source value. + /// + /// - Parameters: + /// - source: A source value to be compared. + /// + /// - Returns: A Boolean value indicating whether the content of `self` is equals + /// to the content of the given source value. + func isContentEqual(to source: Self) -> Bool +} + +public extension ContentEquatable where Self: Equatable { + /// Indicate whether the content of `self` is equals to the content of the given source value. + /// Compared using `==` operator of `Equatable'. + /// + /// - Parameters: + /// - source: A source value to be compared. + /// + /// - Returns: A Boolean value indicating whether the content of `self` is equals + /// to the content of the given source value. + @inlinable + func isContentEqual(to source: Self) -> Bool { + return self == source + } +} + +extension Optional: ContentEquatable where Wrapped: ContentEquatable { + /// Indicate whether the content of `self` is equals to the content of the given source value. + /// Returns `true` if both values compared are nil. + /// The result of comparison between nil and non-nil values is `false`. + /// + /// - Parameters: + /// - source: An optional source value to be compared. + /// + /// - Returns: A Boolean value indicating whether the content of `self` is equals + /// to the content of the given source value. + @inlinable + public func isContentEqual(to source: Wrapped?) -> Bool { + switch (self, source) { + case let (lhs?, rhs?): + return lhs.isContentEqual(to: rhs) + + case (.none, .none): + return true + + case (.none, .some), (.some, .none): + return false + } + } +} diff --git a/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/ContentIdentifiable.swift b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/ContentIdentifiable.swift new file mode 100644 index 0000000..60daabe --- /dev/null +++ b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/ContentIdentifiable.swift @@ -0,0 +1,16 @@ +/// Represents the value that identified for differentiate. +public protocol ContentIdentifiable { + /// A type representing the identifier. + associatedtype DifferenceIdentifier: Hashable + + /// An identifier value for difference calculation. + var differenceIdentifier: DifferenceIdentifier { get } +} + +public extension ContentIdentifiable where Self: Hashable { + /// The `self` value as an identifier for difference calculation. + @inlinable + var differenceIdentifier: Self { + return self + } +} diff --git a/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/Differentiable.swift b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/Differentiable.swift new file mode 100644 index 0000000..889561d --- /dev/null +++ b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/Differentiable.swift @@ -0,0 +1,2 @@ +/// Represents a type that can be used for identifying and comparing for equality. +public typealias Differentiable = ContentIdentifiable & ContentEquatable diff --git a/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/DifferentiableSection.swift b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/DifferentiableSection.swift new file mode 100644 index 0000000..0c0ec18 --- /dev/null +++ b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/DifferentiableSection.swift @@ -0,0 +1,15 @@ +/// Represents the section of collection that can be identified and compared to whether has updated. +public protocol DifferentiableSection: Differentiable { + /// A type representing the elements in section. + associatedtype Collection: Swift.Collection where Collection.Element: Differentiable + + /// The collection of element in the section. + var elements: Collection { get } + + /// Creates a new section reproducing the given source section with replacing the elements. + /// + /// - Parameters: + /// - source: A source section to reproduce. + /// - elements: The collection of elements for the new section. + init(source: Self, elements: C) where C.Element == Collection.Element +} diff --git a/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/ElementPath.swift b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/ElementPath.swift new file mode 100644 index 0000000..ddf3363 --- /dev/null +++ b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/ElementPath.swift @@ -0,0 +1,25 @@ +/// Represents the path to a specific element in a tree of nested collections. +/// +/// - Note: `Foundation.IndexPath` is disadvantageous in performance. +public struct ElementPath: Hashable { + /// The element index (or offset) of this path. + public var element: Int + /// The section index (or offset) of this path. + public var section: Int + + /// Creates a new `ElementPath`. + /// + /// - Parameters: + /// - element: The element index (or offset). + /// - section: The section index (or offset). + public init(element: Int, section: Int) { + self.element = element + self.section = section + } +} + +extension ElementPath: CustomDebugStringConvertible { + public var debugDescription: String { + return "[element: \(element), section: \(section)]" + } +} diff --git a/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/StagedChangeset.swift b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/StagedChangeset.swift new file mode 100644 index 0000000..7c6319c --- /dev/null +++ b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/StagedChangeset.swift @@ -0,0 +1,100 @@ +/// An ordered collection of `Changeset` as staged set of changes in the sectioned collection. +/// +/// The order is representing the stages of changesets. +/// +/// We know that there are combination of changes that crash when applied simultaneously +/// in batch-updates of UI such as UITableView or UICollectionView. +/// The `StagedChangeset` created from the two collection is split at the minimal stages +/// that can be perform batch-updates with no crashes. +/// +/// Example for calculating differences between the two linear collections. +/// +/// extension String: Differentiable {} +/// +/// let source = ["A", "B", "C"] +/// let target = ["B", "C", "D"] +/// +/// let changeset = StagedChangeset(source: source, target: target) +/// print(changeset.isEmpty) // prints "false" +/// +/// Example for calculating differences between the two sectioned collections. +/// +/// let source = [ +/// Section(model: "A", elements: ["😉"]), +/// ] +/// let target = [ +/// Section(model: "A", elements: ["😉, 😺"]), +/// Section(model: "B", elements: ["😪"]) +/// ] +/// +/// let changeset = StagedChangeset(source: sectionedSource, target: sectionedTarget) +/// print(changeset.isEmpty) // prints "false" +public struct StagedChangeset { + @usableFromInline + internal var changesets: ContiguousArray> + + /// Creates a new `StagedChangeset`. + /// + /// - Parameters: + /// - changesets: The collection of `Changeset`. + public init(_ changesets: C) where C.Element == Changeset { + self.changesets = ContiguousArray(changesets) + } +} + +extension StagedChangeset: RandomAccessCollection, RangeReplaceableCollection, MutableCollection { + public typealias Element = Changeset + + @inlinable + public init() { + self.init([]) + } + + @inlinable + public var startIndex: Int { + return changesets.startIndex + } + + @inlinable + public var endIndex: Int { + return changesets.endIndex + } + + @inlinable + public func index(after i: Int) -> Int { + return changesets.index(after: i) + } + + @inlinable + public subscript(position: Int) -> Changeset { + get { return changesets[position] } + set { changesets[position] = newValue } + } + + @inlinable + public mutating func replaceSubrange(_ subrange: R, with newElements: C) where C.Element == Changeset, R.Bound == Int { + changesets.replaceSubrange(subrange, with: newElements) + } +} + +extension StagedChangeset: Equatable where Collection: Equatable { + @inlinable + public static func == (lhs: StagedChangeset, rhs: StagedChangeset) -> Bool { + return lhs.changesets == rhs.changesets + } +} + +extension StagedChangeset: ExpressibleByArrayLiteral { + @inlinable + public init(arrayLiteral elements: Changeset...) { + self.init(elements) + } +} + +extension StagedChangeset: CustomDebugStringConvertible { + public var debugDescription: String { + guard !isEmpty else { return "[]" } + + return "[\n\(map { " \($0.debugDescription.split(separator: "\n").joined(separator: "\n "))" }.joined(separator: ",\n"))\n]" + } +} diff --git a/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/UIKitExtension.swift b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/UIKitExtension.swift new file mode 100644 index 0000000..62d7bca --- /dev/null +++ b/PlaygroundBook/Modules/DifferenceKit.playgroundmodule/Sources/UIKitExtension.swift @@ -0,0 +1,69 @@ +import UIKit + +public extension UICollectionView { + /// Applies multiple animated updates in stages using `StagedChangeset`. + /// + /// - Note: There are combination of changes that crash when applied simultaneously in `performBatchUpdates`. + /// Assumes that `StagedChangeset` has a minimum staged changesets to avoid it. + /// The data of the data-source needs to be updated synchronously before `performBatchUpdates` in every stages. + /// + /// - Parameters: + /// - stagedChangeset: A staged set of changes. + /// - interrupt: A closure that takes an changeset as its argument and returns `true` if the animated + /// updates should be stopped and performed reloadData. Default is nil. + /// - setData: A closure that takes the collection as a parameter. + /// The collection should be set to data-source of UICollectionView. + func reload( + using stagedChangeset: StagedChangeset, + interrupt: ((Changeset) -> Bool)? = nil, + setData: (C) -> Void + ) { + if case .none = window, let data = stagedChangeset.last?.data { + setData(data) + return reloadData() + } + + for changeset in stagedChangeset { + if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data { + setData(data) + return reloadData() + } + + performBatchUpdates({ + setData(changeset.data) + + if !changeset.sectionDeleted.isEmpty { + deleteSections(IndexSet(changeset.sectionDeleted)) + } + + if !changeset.sectionInserted.isEmpty { + insertSections(IndexSet(changeset.sectionInserted)) + } + + if !changeset.sectionUpdated.isEmpty { + reloadSections(IndexSet(changeset.sectionUpdated)) + } + + for (source, target) in changeset.sectionMoved { + moveSection(source, toSection: target) + } + + if !changeset.elementDeleted.isEmpty { + deleteItems(at: changeset.elementDeleted.map { IndexPath(item: $0.element, section: $0.section) }) + } + + if !changeset.elementInserted.isEmpty { + insertItems(at: changeset.elementInserted.map { IndexPath(item: $0.element, section: $0.section) }) + } + + if !changeset.elementUpdated.isEmpty { + reloadItems(at: changeset.elementUpdated.map { IndexPath(item: $0.element, section: $0.section) }) + } + + for (source, target) in changeset.elementMoved { + moveItem(at: IndexPath(item: source.element, section: source.section), to: IndexPath(item: target.element, section: target.section)) + } + }) + } + } +} diff --git a/PlaygroundBook/PrivateResources/Assets.xcassets/CardBackground.colorset/Contents.json b/PlaygroundBook/PrivateResources/Assets.xcassets/CardBackground.colorset/Contents.json new file mode 100644 index 0000000..1982ae9 --- /dev/null +++ b/PlaygroundBook/PrivateResources/Assets.xcassets/CardBackground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFE", + "green" : "0xFF", + "red" : "0xFE" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PlaygroundBook/PrivateResources/Assets.xcassets/CardSelectionBorder.colorset/Contents.json b/PlaygroundBook/PrivateResources/Assets.xcassets/CardSelectionBorder.colorset/Contents.json new file mode 100644 index 0000000..79a8b23 --- /dev/null +++ b/PlaygroundBook/PrivateResources/Assets.xcassets/CardSelectionBorder.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0xD5", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PlaygroundBook/PrivateResources/Assets.xcassets/Contents.json b/PlaygroundBook/PrivateResources/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/PlaygroundBook/PrivateResources/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PlaygroundBook/PrivateResources/Assets.xcassets/ControlBackground.colorset/Contents.json b/PlaygroundBook/PrivateResources/Assets.xcassets/ControlBackground.colorset/Contents.json new file mode 100644 index 0000000..fafa476 --- /dev/null +++ b/PlaygroundBook/PrivateResources/Assets.xcassets/ControlBackground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PlaygroundBook/PrivateResources/Assets.xcassets/ControlForeground.colorset/Contents.json b/PlaygroundBook/PrivateResources/Assets.xcassets/ControlForeground.colorset/Contents.json new file mode 100644 index 0000000..a2c4b27 --- /dev/null +++ b/PlaygroundBook/PrivateResources/Assets.xcassets/ControlForeground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF0", + "green" : "0x9D", + "red" : "0x65" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PlaygroundBook/PrivateResources/Assets.xcassets/ListBackground.colorset/Contents.json b/PlaygroundBook/PrivateResources/Assets.xcassets/ListBackground.colorset/Contents.json new file mode 100644 index 0000000..fafa476 --- /dev/null +++ b/PlaygroundBook/PrivateResources/Assets.xcassets/ListBackground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PlaygroundBook/PrivateResources/Assets.xcassets/MapDot.colorset/Contents.json b/PlaygroundBook/PrivateResources/Assets.xcassets/MapDot.colorset/Contents.json new file mode 100644 index 0000000..783ba07 --- /dev/null +++ b/PlaygroundBook/PrivateResources/Assets.xcassets/MapDot.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.486", + "green" : "0.639", + "red" : "0.980" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PlaygroundBook/PrivateResources/Assets.xcassets/ModalLevel1Background.colorset/Contents.json b/PlaygroundBook/PrivateResources/Assets.xcassets/ModalLevel1Background.colorset/Contents.json new file mode 100644 index 0000000..ec47d54 --- /dev/null +++ b/PlaygroundBook/PrivateResources/Assets.xcassets/ModalLevel1Background.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF3", + "green" : "0xF7", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PlaygroundBook/PrivateResources/Assets.xcassets/ModalLevel2Background.colorset/Contents.json b/PlaygroundBook/PrivateResources/Assets.xcassets/ModalLevel2Background.colorset/Contents.json new file mode 100644 index 0000000..fafa476 --- /dev/null +++ b/PlaygroundBook/PrivateResources/Assets.xcassets/ModalLevel2Background.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PlaygroundBook/PrivateResources/Assets.xcassets/PageBackground.colorset/Contents.json b/PlaygroundBook/PrivateResources/Assets.xcassets/PageBackground.colorset/Contents.json new file mode 100644 index 0000000..255382d --- /dev/null +++ b/PlaygroundBook/PrivateResources/Assets.xcassets/PageBackground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF9", + "green" : "0xF0", + "red" : "0xE9" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PlaygroundBook/PrivateResources/Assets.xcassets/SecondaryText.colorset/Contents.json b/PlaygroundBook/PrivateResources/Assets.xcassets/SecondaryText.colorset/Contents.json new file mode 100644 index 0000000..5ae0cc0 --- /dev/null +++ b/PlaygroundBook/PrivateResources/Assets.xcassets/SecondaryText.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xB0", + "green" : "0xA3", + "red" : "0xA4" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PlaygroundBook/PrivateResources/Assets.xcassets/SelectionBackground.colorset/Contents.json b/PlaygroundBook/PrivateResources/Assets.xcassets/SelectionBackground.colorset/Contents.json new file mode 100644 index 0000000..0dec4dd --- /dev/null +++ b/PlaygroundBook/PrivateResources/Assets.xcassets/SelectionBackground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x7C", + "green" : "0xA3", + "red" : "0xFA" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PlaygroundBook/PrivateResources/Assets.xcassets/Text.colorset/Contents.json b/PlaygroundBook/PrivateResources/Assets.xcassets/Text.colorset/Contents.json new file mode 100644 index 0000000..df07581 --- /dev/null +++ b/PlaygroundBook/PrivateResources/Assets.xcassets/Text.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x4F", + "green" : "0x31", + "red" : "0x34" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PlaygroundBook/PrivateResources/Assets.xcassets/map.imageset/Contents.json b/PlaygroundBook/PrivateResources/Assets.xcassets/map.imageset/Contents.json new file mode 100644 index 0000000..9515cd8 --- /dev/null +++ b/PlaygroundBook/PrivateResources/Assets.xcassets/map.imageset/Contents.json @@ -0,0 +1,17 @@ +{ + "images" : [ + { + "filename" : "map.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "compression-type" : "lossless", + "preserves-vector-representation" : true, + "template-rendering-intent" : "original" + } +} diff --git a/PlaygroundBook/PrivateResources/Assets.xcassets/map.imageset/map.pdf b/PlaygroundBook/PrivateResources/Assets.xcassets/map.imageset/map.pdf new file mode 100644 index 0000000..bc12d69 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Assets.xcassets/map.imageset/map.pdf differ diff --git a/PlaygroundBook/PrivateResources/Assets.xcassets/modalBackground.colorset/Contents.json b/PlaygroundBook/PrivateResources/Assets.xcassets/modalBackground.colorset/Contents.json new file mode 100644 index 0000000..8f09bfc --- /dev/null +++ b/PlaygroundBook/PrivateResources/Assets.xcassets/modalBackground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xD4", + "green" : "0xE8", + "red" : "0xFA" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/PlaygroundBook/PrivateResources/Chapters/00-Welcome/WelcomeViewController.xib b/PlaygroundBook/PrivateResources/Chapters/00-Welcome/WelcomeViewController.xib new file mode 100644 index 0000000..1ff5ed5 --- /dev/null +++ b/PlaygroundBook/PrivateResources/Chapters/00-Welcome/WelcomeViewController.xib @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlaygroundBook/PrivateResources/Chapters/01-Basics/01-IntroductionToUnicode/IntroductionToUnicodeViewController.xib b/PlaygroundBook/PrivateResources/Chapters/01-Basics/01-IntroductionToUnicode/IntroductionToUnicodeViewController.xib new file mode 100644 index 0000000..6049347 --- /dev/null +++ b/PlaygroundBook/PrivateResources/Chapters/01-Basics/01-IntroductionToUnicode/IntroductionToUnicodeViewController.xib @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlaygroundBook/PrivateResources/Chapters/01-Basics/03-EncodingWithUtf/EncodingWithUtfViewController.xib b/PlaygroundBook/PrivateResources/Chapters/01-Basics/03-EncodingWithUtf/EncodingWithUtfViewController.xib new file mode 100644 index 0000000..de1a877 --- /dev/null +++ b/PlaygroundBook/PrivateResources/Chapters/01-Basics/03-EncodingWithUtf/EncodingWithUtfViewController.xib @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlaygroundBook/PrivateResources/Fonts/ChapterEmoji-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/ChapterEmoji-Regular.ttf new file mode 100644 index 0000000..7946c15 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/ChapterEmoji-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/LastResort-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/LastResort-Regular.ttf new file mode 100644 index 0000000..30dbb79 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/LastResort-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoEmoji-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoEmoji-Regular.ttf new file mode 100644 index 0000000..19b7bad Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoEmoji-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoMusic-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoMusic-Regular.ttf new file mode 100644 index 0000000..61a81cb Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoMusic-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansAnatolianHieroglyphs-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansAnatolianHieroglyphs-Regular.ttf new file mode 100644 index 0000000..2b54022 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansAnatolianHieroglyphs-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansCanadianAboriginal-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansCanadianAboriginal-Regular.ttf new file mode 100644 index 0000000..dc9efd8 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansCanadianAboriginal-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansCoptic-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansCoptic-Regular.ttf new file mode 100644 index 0000000..233d3c4 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansCoptic-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansDeseret-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansDeseret-Regular.ttf new file mode 100644 index 0000000..503ebe6 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansDeseret-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansElymaic-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansElymaic-Regular.ttf new file mode 100644 index 0000000..7a68e3b Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansElymaic-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansGeorgian-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansGeorgian-Regular.ttf new file mode 100644 index 0000000..c45ee7f Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansGeorgian-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansGunjalaGondi-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansGunjalaGondi-Regular.ttf new file mode 100644 index 0000000..0126b56 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansGunjalaGondi-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansIndicSiyaqNumbers-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansIndicSiyaqNumbers-Regular.ttf new file mode 100644 index 0000000..9eb3e66 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansIndicSiyaqNumbers-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansLao-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansLao-Regular.ttf new file mode 100644 index 0000000..cd0378c Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansLao-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansMalayalam-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansMalayalam-Regular.ttf new file mode 100644 index 0000000..4883d2b Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansMalayalam-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansMasaramGondi-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansMasaramGondi-Regular.ttf new file mode 100644 index 0000000..0662323 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansMasaramGondi-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansMedefaidrin-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansMedefaidrin-Regular.ttf new file mode 100644 index 0000000..7dbe0a9 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansMedefaidrin-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansMongolian-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansMongolian-Regular.ttf new file mode 100644 index 0000000..8c61324 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansMongolian-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansNushu-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansNushu-Regular.ttf new file mode 100644 index 0000000..e438ccf Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansNushu-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansOldSogdian-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansOldSogdian-Regular.ttf new file mode 100644 index 0000000..4167117 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansOldSogdian-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansSignWriting-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansSignWriting-Regular.ttf new file mode 100644 index 0000000..b00687e Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansSignWriting-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansSinhala-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansSinhala-Regular.ttf new file mode 100644 index 0000000..9d79d35 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansSinhala-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansSogdian-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansSogdian-Regular.ttf new file mode 100644 index 0000000..e27177d Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansSogdian-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansSoyombo-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansSoyombo-Regular.ttf new file mode 100644 index 0000000..57af7e2 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansSoyombo-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansSymbols-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansSymbols-Regular.ttf new file mode 100644 index 0000000..3218866 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansSymbols-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansSymbols2-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansSymbols2-Regular.ttf new file mode 100644 index 0000000..6582c92 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansSymbols2-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansSyriac-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansSyriac-Regular.ttf new file mode 100644 index 0000000..304a619 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansSyriac-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSansZanabazarSquare-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSansZanabazarSquare-Regular.ttf new file mode 100644 index 0000000..5857a3a Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSansZanabazarSquare-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSerifDogra-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSerifDogra-Regular.ttf new file mode 100644 index 0000000..c883f0f Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSerifDogra-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSerifNyiakengPuachueHmong-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSerifNyiakengPuachueHmong-Regular.ttf new file mode 100644 index 0000000..debeb0d Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSerifNyiakengPuachueHmong-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSerifTangut-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSerifTangut-Regular.ttf new file mode 100644 index 0000000..d8fd2fd Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSerifTangut-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Fonts/NotoSerifYezidi-Regular.ttf b/PlaygroundBook/PrivateResources/Fonts/NotoSerifYezidi-Regular.ttf new file mode 100644 index 0000000..111a22c Binary files /dev/null and b/PlaygroundBook/PrivateResources/Fonts/NotoSerifYezidi-Regular.ttf differ diff --git a/PlaygroundBook/PrivateResources/Illustrations/1-1-character-combining.png b/PlaygroundBook/PrivateResources/Illustrations/1-1-character-combining.png new file mode 100644 index 0000000..e9ab650 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Illustrations/1-1-character-combining.png differ diff --git a/PlaygroundBook/PrivateResources/Illustrations/1-2-user-interface.png b/PlaygroundBook/PrivateResources/Illustrations/1-2-user-interface.png new file mode 100644 index 0000000..5bbd98c Binary files /dev/null and b/PlaygroundBook/PrivateResources/Illustrations/1-2-user-interface.png differ diff --git a/PlaygroundBook/PrivateResources/Illustrations/1-3-plane-view.png b/PlaygroundBook/PrivateResources/Illustrations/1-3-plane-view.png new file mode 100644 index 0000000..3ed0c1f Binary files /dev/null and b/PlaygroundBook/PrivateResources/Illustrations/1-3-plane-view.png differ diff --git a/PlaygroundBook/PrivateResources/Illustrations/1-4-last-resort.png b/PlaygroundBook/PrivateResources/Illustrations/1-4-last-resort.png new file mode 100644 index 0000000..7e274d1 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Illustrations/1-4-last-resort.png differ diff --git a/PlaygroundBook/PrivateResources/Illustrations/2-1-utf-32.png b/PlaygroundBook/PrivateResources/Illustrations/2-1-utf-32.png new file mode 100644 index 0000000..3449826 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Illustrations/2-1-utf-32.png differ diff --git a/PlaygroundBook/PrivateResources/Illustrations/2-2-utf-16.png b/PlaygroundBook/PrivateResources/Illustrations/2-2-utf-16.png new file mode 100644 index 0000000..0c5d472 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Illustrations/2-2-utf-16.png differ diff --git a/PlaygroundBook/PrivateResources/Illustrations/2-3-utf-8.png b/PlaygroundBook/PrivateResources/Illustrations/2-3-utf-8.png new file mode 100644 index 0000000..9136268 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Illustrations/2-3-utf-8.png differ diff --git a/PlaygroundBook/PrivateResources/Illustrations/3-1-joining.png b/PlaygroundBook/PrivateResources/Illustrations/3-1-joining.png new file mode 100644 index 0000000..d6b5342 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Illustrations/3-1-joining.png differ diff --git a/PlaygroundBook/PrivateResources/Illustrations/3-2-modifiers.png b/PlaygroundBook/PrivateResources/Illustrations/3-2-modifiers.png new file mode 100644 index 0000000..342425e Binary files /dev/null and b/PlaygroundBook/PrivateResources/Illustrations/3-2-modifiers.png differ diff --git a/PlaygroundBook/PrivateResources/Illustrations/3-3-flags-and-keycaps.png b/PlaygroundBook/PrivateResources/Illustrations/3-3-flags-and-keycaps.png new file mode 100644 index 0000000..331dcb7 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Illustrations/3-3-flags-and-keycaps.png differ diff --git a/PlaygroundBook/PrivateResources/Illustrations/3-4-variations.png b/PlaygroundBook/PrivateResources/Illustrations/3-4-variations.png new file mode 100644 index 0000000..4668e00 Binary files /dev/null and b/PlaygroundBook/PrivateResources/Illustrations/3-4-variations.png differ diff --git a/PlaygroundBook/PrivateResources/JSON/blocks.json b/PlaygroundBook/PrivateResources/JSON/blocks.json new file mode 100644 index 0000000..ec7414d --- /dev/null +++ b/PlaygroundBook/PrivateResources/JSON/blocks.json @@ -0,0 +1,5375 @@ +[ + { + "codePointStart": 0, + "codePointEnd": 127, + "key": "basic-latin", + "name": "Basic Latin", + "categoryKey": "Latin", + "type": "alphabet", + "languages": "English, German, French, Italian, Polish", + "countries": [ + { + "name": "England", + "x": 115, + "y": 25 + }, + { + "name": "USA", + "x": 50, + "y": 35 + }, + { + "name": "Germany", + "x": 123, + "y": 25 + }, + { + "name": "France", + "x": 119, + "y": 29 + }, + { + "name": "Italy", + "x": 126, + "y": 32 + }, + { + "name": "Poland", + "x": 129, + "y": 25 + } + ] + }, + { + "codePointStart": 128, + "codePointEnd": 255, + "key": "latin-1-supplement", + "name": "Latin-1 Supplement", + "categoryKey": "Latin", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 256, + "codePointEnd": 383, + "key": "latin-extended-a", + "name": "Latin Extended-A", + "categoryKey": "Latin", + "type": "alphabet", + "languages": "Celtic, Sami, Maltese, Turkish", + "countries": [ + { + "name": "Scotland", + "x": 114, + "y": 21 + }, + { + "name": "Wales", + "x": 114, + "y": 25 + }, + { + "name": "Ireland", + "x": 112, + "y": 25 + }, + { + "name": "Norway", + "x": 122, + "y": 19 + }, + { + "name": "Finland", + "x": 134, + "y": 17 + }, + { + "name": "Sweden", + "x": 127, + "y": 19 + }, + { + "name": "Malta", + "x": 127, + "y": 35 + }, + { + "name": "Turkey", + "x": 139, + "y": 34 + } + ] + }, + { + "codePointStart": 384, + "codePointEnd": 591, + "key": "latin-extended-b", + "name": "Latin Extended-B", + "categoryKey": "Latin", + "type": "alphabet", + "languages": "Slovenian, Croatian", + "countries": [ + { + "name": "Slovenia", + "x": 128, + "y": 28 + }, + { + "name": "Croatia", + "x": 127, + "y": 30 + }, + { + "name": "Rumania", + "x": 135, + "y": 29 + }, + { + "name": "Libya", + "x": 127, + "y": 41 + } + ] + }, + { + "codePointStart": 592, + "codePointEnd": 687, + "key": "ipa-extensions", + "name": "IPA Extensions", + "categoryKey": "Latin", + "type": "alphabet", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 688, + "codePointEnd": 767, + "key": "spacing-modifier-letters", + "name": "Spacing Modifier Letters", + "categoryKey": "Latin", + "type": "alphabet", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 768, + "codePointEnd": 879, + "key": "combining-diacritical-marks", + "name": "Combining Diacritical Marks", + "categoryKey": "Europe", + "type": "alphabet", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 880, + "codePointEnd": 1023, + "key": "greek-coptic", + "name": "Greek and Coptic", + "categoryKey": "Europe", + "type": "alphabet", + "languages": "Greek, Coptic", + "countries": [ + { + "name": "Greece", + "x": 132, + "y": 35 + } + ] + }, + { + "codePointStart": 1024, + "codePointEnd": 1279, + "key": "cyrillic", + "name": "Cyrillic", + "categoryKey": "Europe", + "type": "alphabet", + "languages": "Russian, Ukrainian, Bulgarian", + "countries": [ + { + "name": "Russia", + "x": 170, + "y": 25 + }, + { + "name": "Ukraine", + "x": 138, + "y": 26 + }, + { + "name": "Bulgaria", + "x": 129, + "y": 31 + }, + { + "name": "Serbia", + "x": 132, + "y": 30 + }, + { + "name": "Macedonia", + "x": 132, + "y": 32 + }, + { + "name": "Moldova", + "x": 136, + "y": 28 + } + ] + }, + { + "codePointStart": 1280, + "codePointEnd": 1327, + "key": "cyrillic-supplement", + "name": "Cyrillic Supplement", + "categoryKey": "Europe", + "type": "alphabet", + "languages": "Komi", + "countries": [ + { + "name": "Russia", + "x": 170, + "y": 25 + } + ] + }, + { + "codePointStart": 1328, + "codePointEnd": 1423, + "key": "armenian", + "name": "Armenian", + "categoryKey": "Europe", + "type": "alphabet", + "languages": "Armenian", + "countries": [ + { + "name": "Armenia", + "x": 147, + "y": 33 + } + ] + }, + { + "codePointStart": 1424, + "codePointEnd": 1535, + "key": "hebrew", + "name": "Hebrew", + "categoryKey": "ME", + "type": "alphabet", + "languages": "Hebrew, Yiddish", + "countries": [ + { + "name": "Israel", + "x": 141, + "y": 39 + } + ] + }, + { + "codePointStart": 1536, + "codePointEnd": 1791, + "key": "arabic", + "name": "Arabic", + "categoryKey": "ME", + "type": "alphabet", + "languages": "Arabic, Persian, Kurd", + "countries": [ + { + "name": "Algeria", + "x": 117, + "y": 40 + }, + { + "name": "Bahrain", + "x": 152, + "y": 43 + }, + { + "name": "Egypt", + "x": 137, + "y": 42 + }, + { + "name": "Iraq", + "x": 147, + "y": 38 + }, + { + "name": "Iran", + "x": 153, + "y": 38 + }, + { + "name": "Kuwait", + "x": 150, + "y": 40 + }, + { + "name": "Afghanistan", + "x": 160, + "y": 40 + }, + { + "name": "Pakistan", + "x": 163, + "y": 41 + }, + { + "name": "India", + "x": 169, + "y": 47 + }, + { + "name": "Fiji Islands", + "x": 228, + "y": 74 + } + ] + }, + { + "codePointStart": 1792, + "codePointEnd": 1871, + "key": "syrian", + "name": "Syriac", + "categoryKey": "ME", + "type": "alphabet", + "languages": "Syrian, Arabic", + "countries": [ + { + "name": "Algeria", + "x": 117, + "y": 40 + }, + { + "name": "Bahrain", + "x": 152, + "y": 43 + }, + { + "name": "Egypt", + "x": 137, + "y": 42 + }, + { + "name": "Syria", + "x": 143, + "y": 37 + } + ] + }, + { + "codePointStart": 1872, + "codePointEnd": 1919, + "key": "arabic-supplement", + "name": "Arabic Supplement", + "categoryKey": "ME", + "type": "alphabet", + "languages": "Arabic, Persian, Kurd", + "countries": [ + { + "name": "Algeria", + "x": 117, + "y": 40 + }, + { + "name": "Bahrain", + "x": 152, + "y": 43 + }, + { + "name": "Egypt", + "x": 137, + "y": 42 + }, + { + "name": "Iraq", + "x": 147, + "y": 38 + }, + { + "name": "Iran", + "x": 153, + "y": 38 + }, + { + "name": "Kuwait", + "x": 150, + "y": 40 + }, + { + "name": "Afghanistan", + "x": 160, + "y": 40 + }, + { + "name": "Pakistan", + "x": 163, + "y": 41 + }, + { + "name": "India", + "x": 169, + "y": 47 + }, + { + "name": "Fiji Islands", + "x": 228, + "y": 74 + } + ] + }, + { + "codePointStart": 1920, + "codePointEnd": 1983, + "key": "thaana", + "name": "Thaana", + "categoryKey": "AsiaSC", + "type": "alphabet", + "languages": "Maldivian", + "countries": [ + { + "name": "Maldives", + "x": 167, + "y": 60 + } + ] + }, + { + "codePointStart": 1984, + "codePointEnd": 2047, + "key": "nko", + "name": "NKo", + "categoryKey": "Africa", + "type": "alphabet", + "languages": "Nko", + "countries": [ + { + "name": "Guinea", + "x": 108, + "y": 52 + }, + { + "name": "Mali", + "x": 116, + "y": 46 + } + ] + }, + { + "codePointStart": 2048, + "codePointEnd": 2111, + "key": "samaritan", + "name": "Samaritan", + "categoryKey": "ME", + "type": "alphabet", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 2112, + "codePointEnd": 2143, + "key": "mandaic", + "name": "Mandaic", + "categoryKey": "ME", + "type": "alphabet", + "languages": "Mandaic", + "countries": [], + "points": [] + }, + { + "codePointStart": 2144, + "codePointEnd": 2159, + "key": "syriac-supplement", + "name": "Syriac Supplement", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 2208, + "codePointEnd": 2303, + "key": "arabic-extended-a", + "name": "Arabic Extended-A", + "categoryKey": "ME", + "type": "alphabet", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 2304, + "codePointEnd": 2431, + "key": "devanagari", + "name": "Devanagari", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "Sanskrit, Hindi", + "countries": [ + { + "name": "India", + "x": 169, + "y": 47 + }, + { + "name": "Pakistan", + "x": 163, + "y": 41 + }, + { + "name": "Fiji Islands", + "x": 228, + "y": 74 + }, + { + "name": "Mauritius", + "x": 154, + "y": 74 + } + ] + }, + { + "codePointStart": 2432, + "codePointEnd": 2559, + "key": "bengali", + "name": "Bengali", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "Bengali", + "countries": [ + { + "name": "India", + "x": 169, + "y": 47 + }, + { + "name": "Bangladesh", + "x": 177, + "y": 44 + }, + { + "name": "Butane", + "x": 178, + "y": 42 + } + ] + }, + { + "codePointStart": 2560, + "codePointEnd": 2687, + "key": "gurmukhi", + "name": "Gurmukhi", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "Punjabi", + "countries": [ + { + "name": "India", + "x": 169, + "y": 47 + }, + { + "name": "Pakistan", + "x": 163, + "y": 41 + } + ] + }, + { + "codePointStart": 2688, + "codePointEnd": 2815, + "key": "gujarati", + "name": "Gujarati", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "", + "countries": [ + { + "name": "India", + "x": 169, + "y": 47 + }, + { + "name": "Pakistan", + "x": 163, + "y": 41 + }, + { + "name": "Uganda", + "x": 138, + "y": 57 + }, + { + "name": "Tanzania", + "x": 141, + "y": 64 + }, + { + "name": "Kenya", + "x": 143, + "y": 59 + } + ] + }, + { + "codePointStart": 2816, + "codePointEnd": 2943, + "key": "oriya", + "name": "Oriya", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "Oriya", + "countries": [ + { + "name": "India", + "x": 169, + "y": 47 + } + ] + }, + { + "codePointStart": 2944, + "codePointEnd": 3071, + "key": "tamil", + "name": "Tamil", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "Tamil, Sanskrit", + "countries": [ + { + "name": "India", + "x": 169, + "y": 47 + }, + { + "name": "Sri-Lanka", + "x": 171, + "y": 55 + }, + { + "name": "Singapore", + "x": 186, + "y": 58 + }, + { + "name": "Malaysia", + "x": 186, + "y": 57 + }, + { + "name": "Kenya", + "x": 143, + "y": 59 + } + ] + }, + { + "codePointStart": 3072, + "codePointEnd": 3199, + "key": "telugu", + "name": "Telugu", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "Telugu", + "countries": [ + { + "name": "India", + "x": 169, + "y": 47 + } + ] + }, + { + "codePointStart": 3200, + "codePointEnd": 3327, + "key": "kannada", + "name": "Kannada", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "Dravidian", + "countries": [ + { + "name": "India", + "x": 169, + "y": 47 + }, + { + "name": "Goa", + "x": 166, + "y": 49 + } + ] + }, + { + "codePointStart": 3328, + "codePointEnd": 3455, + "key": "malayalam", + "name": "Malayalam", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "Dravidian", + "countries": [ + { + "name": "India", + "x": 169, + "y": 47 + }, + { + "name": "Goa", + "x": 166, + "y": 49 + } + ] + }, + { + "codePointStart": 3456, + "codePointEnd": 3583, + "key": "sinhala", + "name": "Sinhala", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "Sinhalese, Sanskrit", + "countries": [ + { + "name": "Sri-Lanka", + "x": 171, + "y": 55 + }, + { + "name": "India", + "x": 169, + "y": 47 + } + ] + }, + { + "codePointStart": 3584, + "codePointEnd": 3711, + "key": "thai", + "name": "Thai", + "categoryKey": "AsiaSE", + "type": "abugida", + "languages": "Thai", + "countries": [ + { + "name": "Thailand", + "x": 185, + "y": 49 + }, + { + "name": "India", + "x": 169, + "y": 47 + } + ] + }, + { + "codePointStart": 3712, + "codePointEnd": 3839, + "key": "lao", + "name": "Lao", + "categoryKey": "AsiaSE", + "type": "abugida", + "languages": "Laotian", + "countries": [ + { + "name": "Thailand", + "x": 185, + "y": 49 + }, + { + "name": "Laos", + "x": 186, + "y": 48 + } + ] + }, + { + "codePointStart": 3840, + "codePointEnd": 4095, + "key": "tibetan", + "name": "Tibetan", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "Tibetan", + "countries": [ + { + "name": "China", + "x": 184, + "y": 37 + }, + { + "name": "India", + "x": 169, + "y": 47 + }, + { + "name": "Butane", + "x": 178, + "y": 42 + }, + { + "name": "Nepal", + "x": 173, + "y": 41 + }, + { + "name": "Pakistan", + "x": 163, + "y": 41 + } + ] + }, + { + "codePointStart": 4096, + "codePointEnd": 4255, + "key": "myanmar", + "name": "Myanmar", + "categoryKey": "AsiaSE", + "type": "abugida", + "languages": "Burmese", + "countries": [ + { + "name": "Myanmar", + "x": 182, + "y": 47 + }, + { + "name": "Thailand", + "x": 185, + "y": 49 + }, + { + "name": "Bangladesh", + "x": 177, + "y": 44 + }, + { + "name": "Malaysia", + "x": 186, + "y": 57 + } + ] + }, + { + "codePointStart": 4256, + "codePointEnd": 4351, + "key": "georgian", + "name": "Georgian", + "categoryKey": "Europe", + "type": "alphabet", + "languages": "Georgian, Abkhazian", + "countries": [ + { + "name": "Georgia", + "x": 144, + "y": 31 + }, + { + "name": "Abkhazia", + "x": 144, + "y": 31 + } + ] + }, + { + "codePointStart": 4352, + "codePointEnd": 4607, + "key": "hangul-jamo", + "name": "Hangul Jamo", + "categoryKey": "AsiaEast", + "type": "abugida", + "languages": "Korean", + "countries": [ + { + "name": "North Korea", + "x": 202, + "y": 33 + }, + { + "name": "South Korea", + "x": 203, + "y": 36 + }, + { + "name": "China", + "x": 184, + "y": 37 + }, + { + "name": "Japan", + "x": 210, + "y": 36 + }, + { + "name": "Indonesia", + "x": 193, + "y": 60 + } + ] + }, + { + "codePointStart": 4608, + "codePointEnd": 4991, + "key": "ethiopic", + "name": "Ethiopic", + "categoryKey": "Africa", + "type": "abugida", + "languages": "", + "countries": [ + { + "name": "Ethiopia", + "x": 145, + "y": 54 + }, + { + "name": "Eritrea", + "x": 143, + "y": 50 + }, + { + "name": "Somalia", + "x": 148, + "y": 55 + }, + { + "name": "Sudan", + "x": 137, + "y": 50 + }, + { + "name": "Israel", + "x": 141, + "y": 39 + } + ] + }, + { + "codePointStart": 4992, + "codePointEnd": 5023, + "key": "ethiopic-supplement", + "name": "Ethiopic Supplement", + "categoryKey": "Africa", + "type": "abugida", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 5024, + "codePointEnd": 5119, + "key": "cherokee", + "name": "Cherokee", + "categoryKey": "Americas", + "type": "syllabary", + "languages": "Cherokee", + "countries": [ + { + "name": "USA", + "x": 50, + "y": 35 + } + ] + }, + { + "codePointStart": 5120, + "codePointEnd": 5759, + "key": "unified-canadian-aboriginal-syllabics", + "name": "Unified Canadian Aboriginal Syllabics", + "categoryKey": "Americas", + "type": "abugida", + "languages": "Cree", + "countries": [ + { + "name": "Canada", + "x": 48, + "y": 21 + }, + { + "name": "USA", + "x": 50, + "y": 35 + } + ] + }, + { + "codePointStart": 5760, + "codePointEnd": 5791, + "key": "ogham", + "name": "Ogham", + "categoryKey": "Europe", + "type": "alphabet", + "languages": "primitive Irish, Pictish", + "countries": [ + { + "name": "Scotland", + "x": 114, + "y": 21 + }, + { + "name": "Ireland", + "x": 112, + "y": 25 + }, + { + "name": "Wales", + "x": 114, + "y": 25 + } + ] + }, + { + "codePointStart": 5792, + "codePointEnd": 5887, + "key": "runic", + "name": "Runic", + "categoryKey": "Europe", + "type": "alphabet", + "languages": "old Italic, Runic", + "countries": [], + "points": [] + }, + { + "codePointStart": 5888, + "codePointEnd": 5919, + "key": "tagalog", + "name": "Tagalog", + "categoryKey": "IndOcean", + "type": "abugida", + "languages": "Tagalog, Runic", + "countries": [ + { + "name": "North Philippines", + "x": 198, + "y": 49 + } + ] + }, + { + "codePointStart": 5920, + "codePointEnd": 5951, + "key": "hanunoo", + "name": "Hanunoo", + "categoryKey": "IndOcean", + "type": "abugida", + "languages": "Hanunoo", + "countries": [ + { + "name": "Philippines", + "x": 199, + "y": 52 + } + ] + }, + { + "codePointStart": 5952, + "codePointEnd": 5983, + "key": "buhid", + "name": "Buhid", + "categoryKey": "IndOcean", + "type": "abugida", + "languages": "Buhid", + "countries": [ + { + "name": "Philippines", + "x": 199, + "y": 52 + } + ] + }, + { + "codePointStart": 5984, + "codePointEnd": 6015, + "key": "tagbanwa", + "name": "Tagbanwa", + "categoryKey": "IndOcean", + "type": "abugida", + "languages": "", + "countries": [ + { + "name": "Philippines", + "x": 199, + "y": 52 + } + ] + }, + { + "codePointStart": 6016, + "codePointEnd": 6143, + "key": "khmer", + "name": "Khmer", + "categoryKey": "AsiaSE", + "type": "abugida", + "languages": "Khmer", + "countries": [ + { + "name": "Cambodia", + "x": 187, + "y": 52 + }, + { + "name": "Vietnam", + "x": 190, + "y": 52 + }, + { + "name": "Thailand", + "x": 185, + "y": 49 + }, + { + "name": "Laos", + "x": 186, + "y": 48 + }, + { + "name": "China", + "x": 184, + "y": 37 + } + ] + }, + { + "codePointStart": 6144, + "codePointEnd": 6319, + "key": "mongolian", + "name": "Mongolian", + "categoryKey": "AsiaSC", + "type": "alphabet", + "languages": "Mongolian", + "countries": [ + { + "name": "China", + "x": 184, + "y": 37 + }, + { + "name": "Mongolia", + "x": 187, + "y": 27 + }, + { + "name": "Afghanistan", + "x": 160, + "y": 40 + } + ] + }, + { + "codePointStart": 6320, + "codePointEnd": 6399, + "key": "unified-canadian-aboriginal-syllabics-extended", + "name": "Unified Canadian Aboriginal Syllabics Extended", + "categoryKey": "Americas", + "type": "abugida", + "languages": "Cree", + "countries": [ + { + "name": "Canada", + "x": 48, + "y": 21 + }, + { + "name": "USA", + "x": 50, + "y": 35 + } + ] + }, + { + "codePointStart": 6400, + "codePointEnd": 6479, + "key": "limbu", + "name": "Limbu", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "Limbu", + "countries": [ + { + "name": "Nepal", + "x": 173, + "y": 41 + }, + { + "name": "India", + "x": 169, + "y": 47 + }, + { + "name": "Kashmir", + "x": 168, + "y": 39 + }, + { + "name": "Pakistan", + "x": 163, + "y": 41 + } + ] + }, + { + "codePointStart": 6480, + "codePointEnd": 6527, + "key": "tai-le", + "name": "Tai Le", + "categoryKey": "AsiaSE", + "type": "abugida", + "languages": "", + "countries": [ + { + "name": "Vietnam", + "x": 190, + "y": 52 + }, + { + "name": "Laos", + "x": 186, + "y": 48 + }, + { + "name": "Myanmar", + "x": 182, + "y": 47 + }, + { + "name": "Thailand", + "x": 185, + "y": 49 + } + ] + }, + { + "codePointStart": 6528, + "codePointEnd": 6623, + "key": "new-tai-lue", + "name": "New Tai Lue", + "categoryKey": "AsiaSE", + "type": "alphabet", + "languages": "", + "countries": [ + { + "name": "Vietnam", + "x": 190, + "y": 52 + }, + { + "name": "Laos", + "x": 186, + "y": 48 + }, + { + "name": "Myanmar", + "x": 182, + "y": 47 + }, + { + "name": "Thailand", + "x": 185, + "y": 49 + } + ] + }, + { + "codePointStart": 6624, + "codePointEnd": 6655, + "key": "khmer-symbols", + "name": "Khmer Symbols", + "categoryKey": "AsiaSE", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 6656, + "codePointEnd": 6687, + "key": "buginese", + "name": "Buginese", + "categoryKey": "IndOcean", + "type": "abugida", + "languages": "Buginese, Makassarese, Mandar", + "countries": [ + { + "name": "Indonesia", + "x": 193, + "y": 60 + } + ] + }, + { + "codePointStart": 6688, + "codePointEnd": 6831, + "key": "tai-tham", + "name": "Tai Tham", + "categoryKey": "AsiaSE", + "type": "abugida", + "languages": "", + "countries": [ + { + "name": "Thailand", + "x": 185, + "y": 49 + }, + { + "name": "Burma", + "x": 181, + "y": 47 + }, + { + "name": "Laos", + "x": 186, + "y": 48 + }, + { + "name": "Cambodia", + "x": 187, + "y": 52 + }, + { + "name": "China", + "x": 184, + "y": 37 + }, + { + "name": "Vietnam", + "x": 190, + "y": 52 + } + ] + }, + { + "codePointStart": 6832, + "codePointEnd": 6911, + "key": "combining-diacritical-marks-extended", + "name": "Combining Diacritical Marks Extended", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 6912, + "codePointEnd": 7039, + "key": "balinese", + "name": "Balinese", + "categoryKey": "IndOcean", + "type": "abugida", + "languages": "Balinese, Sasak", + "countries": [ + { + "name": "Bali", + "x": 194, + "y": 65 + }, + { + "name": "Lombok", + "x": 195, + "y": 65 + }, + { + "name": "Indonesia", + "x": 193, + "y": 60 + } + ] + }, + { + "codePointStart": 7040, + "codePointEnd": 7103, + "key": "sundanese", + "name": "Sundanese", + "categoryKey": "IndOcean", + "type": "abugida", + "languages": "Sundanese", + "countries": [ + { + "name": "Indonesia", + "x": 193, + "y": 60 + }, + { + "name": "Jakarta", + "x": 188, + "y": 64 + } + ] + }, + { + "codePointStart": 7104, + "codePointEnd": 7167, + "key": "batak", + "name": "Batak", + "categoryKey": "IndOcean", + "type": "abugida", + "languages": "Batak", + "countries": [ + { + "name": "Indonesia", + "x": 193, + "y": 60 + } + ] + }, + { + "codePointStart": 7168, + "codePointEnd": 7247, + "key": "lepcha", + "name": "Lepcha", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "Lepcha", + "countries": [ + { + "name": "India", + "x": 169, + "y": 47 + }, + { + "name": "Butane", + "x": 178, + "y": 42 + }, + { + "name": "Nepal", + "x": 173, + "y": 41 + } + ] + }, + { + "codePointStart": 7248, + "codePointEnd": 7295, + "key": "ol-chiki", + "name": "Ol Chiki", + "categoryKey": "AsiaSC", + "type": "alphabet", + "languages": "Santali", + "countries": [ + { + "name": "India", + "x": 169, + "y": 47 + }, + { + "name": "Butane", + "x": 178, + "y": 42 + }, + { + "name": "Nepal", + "x": 173, + "y": 41 + }, + { + "name": "Bangladesh", + "x": 177, + "y": 44 + } + ] + }, + { + "codePointStart": 7296, + "codePointEnd": 7311, + "key": "cyrillic-extended-c", + "name": "Cyrillic Extended C", + "categoryKey": "Europe", + "type": "alphabet", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 7312, + "codePointEnd": 7359, + "key": "georgian-extended", + "name": "Georgian Extended", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 7360, + "codePointEnd": 7375, + "key": "sundanese-supplement", + "name": "Sundanese Supplement", + "categoryKey": "IndOcean", + "type": "abugida", + "languages": "Sundanese", + "countries": [], + "points": [] + }, + { + "codePointStart": 7376, + "codePointEnd": 7423, + "key": "vedic-extensions", + "name": "Vedic Extensions", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 7424, + "codePointEnd": 7551, + "key": "phonetic-extensions", + "name": "Phonetic Extensions", + "categoryKey": "Latin", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 7552, + "codePointEnd": 7615, + "key": "phonetic-extensions-supplement", + "name": "Phonetic Extensions Supplement", + "categoryKey": "Latin", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 7616, + "codePointEnd": 7679, + "key": "combining-diacritical-marks-supplement", + "name": "Combining Diacritical Marks Supplement", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 7680, + "codePointEnd": 7935, + "key": "latin-extended-additional", + "name": "Latin Extended Additional", + "categoryKey": "Latin", + "type": "", + "languages": "", + "countries": [ + { + "name": "England", + "x": 115, + "y": 25 + }, + { + "name": "USA", + "x": 50, + "y": 35 + }, + { + "name": "Germany", + "x": 123, + "y": 25 + }, + { + "name": "France", + "x": 119, + "y": 29 + }, + { + "name": "Italy", + "x": 126, + "y": 32 + }, + { + "name": "Poland", + "x": 129, + "y": 25 + } + ] + }, + { + "codePointStart": 7936, + "codePointEnd": 8191, + "key": "greek-extended", + "name": "Greek Extended", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [ + { + "name": "Greece", + "x": 132, + "y": 35 + } + ] + }, + { + "codePointStart": 8192, + "codePointEnd": 8303, + "key": "general-punctuation", + "name": "General Punctuation", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 8304, + "codePointEnd": 8351, + "key": "superscripts-and-subscripts", + "name": "Superscripts and Subscripts", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 8352, + "codePointEnd": 8399, + "key": "currency-symbols", + "name": "Currency Symbols", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 8400, + "codePointEnd": 8447, + "key": "combining-diacritical-marks-for-symbols", + "name": "Combining Diacritical Marks for Symbols", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 8448, + "codePointEnd": 8527, + "key": "letterlike-symbols", + "name": "Letterlike Symbols", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 8528, + "codePointEnd": 8591, + "key": "number-forms", + "name": "Number Forms", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 8592, + "codePointEnd": 8703, + "key": "arrows", + "name": "Arrows", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 8704, + "codePointEnd": 8959, + "key": "mathematical-operators", + "name": "Mathematical Operators", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 8960, + "codePointEnd": 9215, + "key": "miscellaneous-technical", + "name": "Miscellaneous Technical", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 9216, + "codePointEnd": 9279, + "key": "control-pictures", + "name": "Control Pictures", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 9280, + "codePointEnd": 9311, + "key": "optical-character-recognition", + "name": "Optical Character Recognition", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 9312, + "codePointEnd": 9471, + "key": "enclosed-alphanumerics", + "name": "Enclosed Alphanumerics", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 9472, + "codePointEnd": 9599, + "key": "box-drawing", + "name": "Box Drawing", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 9600, + "codePointEnd": 9631, + "key": "block-elements", + "name": "Block Elements", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 9632, + "codePointEnd": 9727, + "key": "geometric-shapes", + "name": "Geometric Shapes", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 9728, + "codePointEnd": 9983, + "key": "miscellaneous-symbols", + "name": "Miscellaneous Symbols", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 9984, + "codePointEnd": 10175, + "key": "dingbats", + "name": "Dingbats", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 10176, + "codePointEnd": 10223, + "key": "miscellaneous-mathematical-symbols-a", + "name": "Miscellaneous Mathematical Symbols-A", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 10224, + "codePointEnd": 10239, + "key": "supplemental-arrows-a", + "name": "Supplemental Arrows-A", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 10240, + "codePointEnd": 10495, + "key": "braille-patterns", + "name": "Braille Patterns", + "categoryKey": "notation", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 10496, + "codePointEnd": 10623, + "key": "supplemental-arrows-b", + "name": "Supplemental Arrows-B", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 10624, + "codePointEnd": 10751, + "key": "miscellaneous-mathematical-symbols-b", + "name": "Miscellaneous Mathematical Symbols-B", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 10752, + "codePointEnd": 11007, + "key": "supplemental-mathematical-operators", + "name": "Supplemental Mathematical Operators", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 11008, + "codePointEnd": 11263, + "key": "miscellaneous-symbols-and-arrows", + "name": "Miscellaneous Symbols and Arrows", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 11264, + "codePointEnd": 11359, + "key": "glagolitic", + "name": "Glagolitic", + "categoryKey": "Europe", + "type": "alphabet", + "languages": "old church Slavic", + "countries": [ + { + "name": "Russia", + "x": 170, + "y": 25 + }, + { + "name": "Ukraine", + "x": 138, + "y": 26 + }, + { + "name": "Poland", + "x": 129, + "y": 25 + }, + { + "name": "Bulgaria", + "x": 129, + "y": 31 + }, + { + "name": "Bosnia", + "x": 129, + "y": 31 + }, + { + "name": "Herzegovina", + "x": 129, + "y": 31 + }, + { + "name": "Croatia", + "x": 127, + "y": 30 + }, + { + "name": "Montenegro", + "x": 131, + "y": 32 + }, + { + "name": "Macedonia", + "x": 132, + "y": 32 + } + ] + }, + { + "codePointStart": 11360, + "codePointEnd": 11391, + "key": "latin-extended-c", + "name": "Latin Extended-C", + "categoryKey": "Latin", + "type": "alphabet", + "languages": "", + "countries": [ + { + "name": "China", + "x": 184, + "y": 37 + }, + { + "name": "Kazakhstan", + "x": 161, + "y": 28 + } + ] + }, + { + "codePointStart": 11392, + "codePointEnd": 11519, + "key": "coptic", + "name": "Coptic", + "categoryKey": "Europe", + "type": "alphabet", + "languages": "Coptic", + "countries": [ + { + "name": "Egypt", + "x": 137, + "y": 42 + }, + { + "name": "Canada", + "x": 48, + "y": 21 + }, + { + "name": "Australia", + "x": 206, + "y": 76 + }, + { + "name": "USA", + "x": 50, + "y": 35 + } + ] + }, + { + "codePointStart": 11520, + "codePointEnd": 11567, + "key": "georgian-supplement", + "name": "Georgian Supplement", + "categoryKey": "Europe", + "type": "alphabet", + "languages": "Georgian", + "countries": [ + { + "name": "Georgia", + "x": 144, + "y": 31 + }, + { + "name": "Abkhazia", + "x": 144, + "y": 31 + } + ] + }, + { + "codePointStart": 11568, + "codePointEnd": 11647, + "key": "tifinagh", + "name": "Tifinagh", + "categoryKey": "Africa", + "type": "abjad", + "languages": "Tuareg", + "countries": [ + { + "name": "North Africa", + "x": 124, + "y": 43 + } + ] + }, + { + "codePointStart": 11648, + "codePointEnd": 11743, + "key": "ethiopic-extended", + "name": "Ethiopic Extended", + "categoryKey": "Africa", + "type": "abugida", + "languages": "Amharic, Tigtinya", + "countries": [ + { + "name": "Ethiopia", + "x": 145, + "y": 54 + }, + { + "name": "Eritrea", + "x": 143, + "y": 50 + }, + { + "name": "Somalia", + "x": 148, + "y": 55 + }, + { + "name": "Sudan", + "x": 137, + "y": 50 + }, + { + "name": "Israel", + "x": 141, + "y": 39 + } + ] + }, + { + "codePointStart": 11744, + "codePointEnd": 11775, + "key": "cyrillic-extended-a", + "name": "Cyrillic Extended-A", + "categoryKey": "Europe", + "type": "alphabet", + "languages": "Russian, Ukrainian", + "countries": [ + { + "name": "Russia", + "x": 170, + "y": 25 + }, + { + "name": "Ukraine", + "x": 138, + "y": 26 + }, + { + "name": "Bulgaria", + "x": 129, + "y": 31 + }, + { + "name": "Serbia", + "x": 132, + "y": 30 + }, + { + "name": "Macedonia", + "x": 132, + "y": 32 + }, + { + "name": "Moldova", + "x": 136, + "y": 28 + } + ] + }, + { + "codePointStart": 11776, + "codePointEnd": 11903, + "key": "supplemental-punctuation", + "name": "Supplemental Punctuation", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 11904, + "codePointEnd": 12031, + "key": "cjk-radicals-supplement", + "name": "CJK Radicals Supplement", + "categoryKey": "Han", + "type": "", + "languages": "Chinese, Japanese, Vietnamese, Korean", + "countries": [ + { + "name": "Peru", + "x": 66, + "y": 65 + }, + { + "name": "Laos", + "x": 186, + "y": 48 + }, + { + "name": "North Korea", + "x": 202, + "y": 33 + }, + { + "name": "China", + "x": 184, + "y": 37 + }, + { + "name": "Cambodia", + "x": 187, + "y": 52 + }, + { + "name": "Japan", + "x": 210, + "y": 36 + }, + { + "name": "Guam", + "x": 207, + "y": 53 + }, + { + "name": "Taiwan", + "x": 198, + "y": 44 + }, + { + "name": "South Korea", + "x": 203, + "y": 36 + }, + { + "name": "Vietnam", + "x": 190, + "y": 52 + }, + { + "name": "Somalia", + "x": 148, + "y": 55 + }, + { + "name": "Sudan", + "x": 137, + "y": 50 + }, + { + "name": "Israel", + "x": 141, + "y": 39 + }, + { + "name": "Singapore", + "x": 186, + "y": 58 + }, + { + "name": "Philippines", + "x": 199, + "y": 52 + }, + { + "name": "Malaysia", + "x": 186, + "y": 57 + }, + { + "name": "Indonesia", + "x": 193, + "y": 60 + }, + { + "name": "Thailand", + "x": 185, + "y": 49 + } + ] + }, + { + "codePointStart": 12032, + "codePointEnd": 12255, + "key": "kangxi-radicals", + "name": "Kangxi Radicals", + "categoryKey": "Han", + "type": "", + "languages": "", + "countries": [ + { + "name": "China", + "x": 184, + "y": 37 + } + ] + }, + { + "codePointStart": 12272, + "codePointEnd": 12287, + "key": "ideographic-description-characters", + "name": "Ideographic Description Characters", + "categoryKey": "Han", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 12288, + "codePointEnd": 12351, + "key": "cjk-symbols-and-punctuation", + "name": "CJK Symbols and Punctuation", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 12352, + "codePointEnd": 12447, + "key": "hiragana", + "name": "Hiragana", + "categoryKey": "AsiaEast", + "type": "syllabary", + "languages": "Japanese, Okinawan", + "countries": [ + { + "name": "Peru", + "x": 66, + "y": 65 + }, + { + "name": "North Korea", + "x": 202, + "y": 33 + }, + { + "name": "Japan", + "x": 210, + "y": 36 + }, + { + "name": "Guam", + "x": 207, + "y": 53 + }, + { + "name": "Taiwan", + "x": 198, + "y": 44 + }, + { + "name": "South Korea", + "x": 203, + "y": 36 + } + ] + }, + { + "codePointStart": 12448, + "codePointEnd": 12543, + "key": "katakana", + "name": "Katakana", + "categoryKey": "AsiaEast", + "type": "syllabary", + "languages": "Japanese, Okinawan, Ainu, Palaun", + "countries": [ + { + "name": "Peru", + "x": 66, + "y": 65 + }, + { + "name": "North Korea", + "x": 202, + "y": 33 + }, + { + "name": "Japan", + "x": 210, + "y": 36 + }, + { + "name": "Guam", + "x": 207, + "y": 53 + }, + { + "name": "Taiwan", + "x": 198, + "y": 44 + }, + { + "name": "South Korea", + "x": 203, + "y": 36 + } + ] + }, + { + "codePointStart": 12544, + "codePointEnd": 12591, + "key": "bopomofo", + "name": "Bopomofo", + "categoryKey": "AsiaEast", + "type": "syllabary", + "languages": "Chinese", + "countries": [ + { + "name": "Taiwan", + "x": 198, + "y": 44 + } + ] + }, + { + "codePointStart": 12592, + "codePointEnd": 12687, + "key": "hangul-compatibility-jamo", + "name": "Hangul Compatibility Jamo", + "categoryKey": "AsiaEast", + "type": "syllabary", + "languages": "Chinese", + "countries": [ + { + "name": "North Korea", + "x": 202, + "y": 33 + }, + { + "name": "South Korea", + "x": 203, + "y": 36 + }, + { + "name": "China", + "x": 184, + "y": 37 + }, + { + "name": "Japan", + "x": 210, + "y": 36 + } + ] + }, + { + "codePointStart": 12688, + "codePointEnd": 12703, + "key": "kanbun", + "name": "Kanbun", + "categoryKey": "AsiaEast", + "type": "syllabary", + "languages": "", + "countries": [ + { + "name": "China", + "x": 184, + "y": 37 + } + ] + }, + { + "codePointStart": 12704, + "codePointEnd": 12735, + "key": "bopomofo-extended", + "name": "Bopomofo Extended", + "categoryKey": "AsiaEast", + "type": "syllabary", + "languages": "", + "countries": [ + { + "name": "Taiwan", + "x": 198, + "y": 44 + } + ] + }, + { + "codePointStart": 12736, + "codePointEnd": 12783, + "key": "cjk-strokes", + "name": "CJK Strokes", + "categoryKey": "Han", + "type": "syllabary", + "languages": "Chinese", + "countries": [ + { + "name": "Peru", + "x": 66, + "y": 65 + }, + { + "name": "Laos", + "x": 186, + "y": 48 + }, + { + "name": "North Korea", + "x": 202, + "y": 33 + }, + { + "name": "China", + "x": 184, + "y": 37 + }, + { + "name": "Cambodia", + "x": 187, + "y": 52 + }, + { + "name": "Japan", + "x": 210, + "y": 36 + }, + { + "name": "Guam", + "x": 207, + "y": 53 + }, + { + "name": "Taiwan", + "x": 198, + "y": 44 + }, + { + "name": "South Korea", + "x": 203, + "y": 36 + }, + { + "name": "Vietnam", + "x": 190, + "y": 52 + }, + { + "name": "Somalia", + "x": 148, + "y": 55 + }, + { + "name": "Sudan", + "x": 137, + "y": 50 + }, + { + "name": "Israel", + "x": 141, + "y": 39 + }, + { + "name": "Singapore", + "x": 186, + "y": 58 + }, + { + "name": "Philippines", + "x": 199, + "y": 52 + }, + { + "name": "Malaysia", + "x": 186, + "y": 57 + }, + { + "name": "Indonesia", + "x": 193, + "y": 60 + }, + { + "name": "Thailand", + "x": 185, + "y": 49 + } + ] + }, + { + "codePointStart": 12784, + "codePointEnd": 12799, + "key": "katakana-phonetic-extensions", + "name": "Katakana Phonetic Extensions", + "categoryKey": "AsiaEast", + "type": "syllabary", + "languages": "Japanese, Okinawan, Ainu, Palauan", + "countries": [ + { + "name": "Peru", + "x": 66, + "y": 65 + }, + { + "name": "North Korea", + "x": 202, + "y": 33 + }, + { + "name": "Japan", + "x": 210, + "y": 36 + }, + { + "name": "Guam", + "x": 207, + "y": 53 + }, + { + "name": "Taiwan", + "x": 198, + "y": 44 + }, + { + "name": "South Korea", + "x": 203, + "y": 36 + } + ] + }, + { + "codePointStart": 12800, + "codePointEnd": 13055, + "key": "enclosed-cjk-letters-and-months", + "name": "Enclosed CJK Letters and Months", + "categoryKey": "symbols", + "type": "syllabary", + "languages": "Chinese", + "countries": [ + { + "name": "Peru", + "x": 66, + "y": 65 + }, + { + "name": "Laos", + "x": 186, + "y": 48 + }, + { + "name": "North Korea", + "x": 202, + "y": 33 + }, + { + "name": "China", + "x": 184, + "y": 37 + }, + { + "name": "Cambodia", + "x": 187, + "y": 52 + }, + { + "name": "Japan", + "x": 210, + "y": 36 + }, + { + "name": "Guam", + "x": 207, + "y": 53 + }, + { + "name": "Taiwan", + "x": 198, + "y": 44 + }, + { + "name": "South Korea", + "x": 203, + "y": 36 + }, + { + "name": "Vietnam", + "x": 190, + "y": 52 + }, + { + "name": "Somalia", + "x": 148, + "y": 55 + }, + { + "name": "Sudan", + "x": 137, + "y": 50 + }, + { + "name": "Israel", + "x": 141, + "y": 39 + }, + { + "name": "Singapore", + "x": 186, + "y": 58 + }, + { + "name": "Philippines", + "x": 199, + "y": 52 + }, + { + "name": "Malaysia", + "x": 186, + "y": 57 + }, + { + "name": "Indonesia", + "x": 193, + "y": 60 + }, + { + "name": "Thailand", + "x": 185, + "y": 49 + } + ] + }, + { + "codePointStart": 13056, + "codePointEnd": 13311, + "key": "cjk-compatibility", + "name": "CJK Compatibility", + "categoryKey": "symbols", + "type": "syllabary", + "languages": "Chinese, Japanese, Korean, Vietnamese", + "countries": [ + { + "name": "Peru", + "x": 66, + "y": 65 + }, + { + "name": "Laos", + "x": 186, + "y": 48 + }, + { + "name": "North Korea", + "x": 202, + "y": 33 + }, + { + "name": "China", + "x": 184, + "y": 37 + }, + { + "name": "Cambodia", + "x": 187, + "y": 52 + }, + { + "name": "Japan", + "x": 210, + "y": 36 + }, + { + "name": "Guam", + "x": 207, + "y": 53 + }, + { + "name": "Taiwan", + "x": 198, + "y": 44 + }, + { + "name": "South Korea", + "x": 203, + "y": 36 + }, + { + "name": "Vietnam", + "x": 190, + "y": 52 + }, + { + "name": "Somalia", + "x": 148, + "y": 55 + }, + { + "name": "Sudan", + "x": 137, + "y": 50 + }, + { + "name": "Israel", + "x": 141, + "y": 39 + }, + { + "name": "Singapore", + "x": 186, + "y": 58 + }, + { + "name": "Philippines", + "x": 199, + "y": 52 + }, + { + "name": "Malaysia", + "x": 186, + "y": 57 + }, + { + "name": "Indonesia", + "x": 193, + "y": 60 + }, + { + "name": "Thailand", + "x": 185, + "y": 49 + } + ] + }, + { + "codePointStart": 13312, + "codePointEnd": 19903, + "key": "cjk-unified-ideographs-extension-a", + "name": "CJK Unified Ideographs Extension A", + "categoryKey": "Han", + "type": "syllabary", + "languages": "Chinese, Japanese, Korean, Vietnamese", + "countries": [ + { + "name": "Peru", + "x": 66, + "y": 65 + }, + { + "name": "Laos", + "x": 186, + "y": 48 + }, + { + "name": "North Korea", + "x": 202, + "y": 33 + }, + { + "name": "China", + "x": 184, + "y": 37 + }, + { + "name": "Cambodia", + "x": 187, + "y": 52 + }, + { + "name": "Japan", + "x": 210, + "y": 36 + }, + { + "name": "Guam", + "x": 207, + "y": 53 + }, + { + "name": "Taiwan", + "x": 198, + "y": 44 + }, + { + "name": "South Korea", + "x": 203, + "y": 36 + }, + { + "name": "Vietnam", + "x": 190, + "y": 52 + }, + { + "name": "Somalia", + "x": 148, + "y": 55 + }, + { + "name": "Sudan", + "x": 137, + "y": 50 + }, + { + "name": "Israel", + "x": 141, + "y": 39 + }, + { + "name": "Singapore", + "x": 186, + "y": 58 + }, + { + "name": "Philippines", + "x": 199, + "y": 52 + }, + { + "name": "Malaysia", + "x": 186, + "y": 57 + }, + { + "name": "Indonesia", + "x": 193, + "y": 60 + }, + { + "name": "Thailand", + "x": 185, + "y": 49 + } + ] + }, + { + "codePointStart": 19904, + "codePointEnd": 19967, + "key": "yijing-hexagram-symbols", + "name": "Yijing Hexagram Symbols", + "categoryKey": "symbols", + "type": "syllabary", + "languages": "", + "countries": [ + { + "name": "China", + "x": 184, + "y": 37 + } + ] + }, + { + "codePointStart": 19968, + "codePointEnd": 40959, + "key": "cjk-unified-ideographs", + "name": "CJK Unified Ideographs", + "categoryKey": "Han", + "type": "syllabary", + "languages": "Chinese, Japanese, Korean, Vietnamese", + "countries": [ + { + "name": "Peru", + "x": 66, + "y": 65 + }, + { + "name": "Laos", + "x": 186, + "y": 48 + }, + { + "name": "North Korea", + "x": 202, + "y": 33 + }, + { + "name": "China", + "x": 184, + "y": 37 + }, + { + "name": "Cambodia", + "x": 187, + "y": 52 + }, + { + "name": "Japan", + "x": 210, + "y": 36 + }, + { + "name": "Guam", + "x": 207, + "y": 53 + }, + { + "name": "Taiwan", + "x": 198, + "y": 44 + }, + { + "name": "South Korea", + "x": 203, + "y": 36 + }, + { + "name": "Vietnam", + "x": 190, + "y": 52 + }, + { + "name": "Somalia", + "x": 148, + "y": 55 + }, + { + "name": "Sudan", + "x": 137, + "y": 50 + }, + { + "name": "Israel", + "x": 141, + "y": 39 + }, + { + "name": "Singapore", + "x": 186, + "y": 58 + }, + { + "name": "Philippines", + "x": 199, + "y": 52 + }, + { + "name": "Malaysia", + "x": 186, + "y": 57 + }, + { + "name": "Indonesia", + "x": 193, + "y": 60 + }, + { + "name": "Thailand", + "x": 185, + "y": 49 + } + ] + }, + { + "codePointStart": 40960, + "codePointEnd": 42127, + "key": "yi-syllables", + "name": "Yi Syllables", + "categoryKey": "AsiaEast", + "type": "syllabary", + "languages": "Yi", + "countries": [ + { + "name": "China", + "x": 184, + "y": 37 + } + ] + }, + { + "codePointStart": 42128, + "codePointEnd": 42191, + "key": "yi-radicals", + "name": "Yi Radicals", + "categoryKey": "AsiaEast", + "type": "syllabary", + "languages": "Yi", + "countries": [ + { + "name": "China", + "x": 184, + "y": 37 + } + ] + }, + { + "codePointStart": 42192, + "codePointEnd": 42239, + "key": "lisu", + "name": "Lisu", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 42240, + "codePointEnd": 42559, + "key": "vai", + "name": "Vai", + "categoryKey": "Africa", + "type": "syllabary", + "languages": "Vai", + "countries": [ + { + "name": "Liberia", + "x": 111, + "y": 55 + }, + { + "name": "Sierra-Leone", + "x": 108, + "y": 53 + } + ] + }, + { + "codePointStart": 42560, + "codePointEnd": 42655, + "key": "ciryllic-extended-b", + "name": "Cyrillic Extended-B", + "categoryKey": "Europe", + "type": "alphabet", + "languages": "", + "countries": [ + { + "name": "Russia", + "x": 170, + "y": 25 + }, + { + "name": "Ukraine", + "x": 138, + "y": 26 + }, + { + "name": "Bulgaria", + "x": 129, + "y": 31 + }, + { + "name": "Serbia", + "x": 132, + "y": 30 + }, + { + "name": "Macedonia", + "x": 132, + "y": 32 + }, + { + "name": "Moldova", + "x": 136, + "y": 28 + } + ] + }, + { + "codePointStart": 42656, + "codePointEnd": 42751, + "key": "bamum", + "name": "Bamum", + "categoryKey": "Africa", + "type": "semisyllabary", + "languages": "Bamum", + "countries": [ + { + "name": "Cameroon", + "x": 124, + "y": 56 + }, + { + "name": "Nigeria", + "x": 122, + "y": 54 + } + ] + }, + { + "codePointStart": 42752, + "codePointEnd": 42783, + "key": "modifier-tone-letters", + "name": "Modifier Tone Letters", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 42784, + "codePointEnd": 43007, + "key": "latin-extended-d", + "name": "Latin Extended-D", + "categoryKey": "Latin", + "type": "alphabet", + "languages": "", + "countries": [ + { + "name": "Finland", + "x": 134, + "y": 17 + }, + { + "name": "Estland", + "x": 134, + "y": 22 + }, + { + "name": "Hungary", + "x": 130, + "y": 28 + }, + { + "name": "Salvador", + "x": 57, + "y": 50 + } + ] + }, + { + "codePointStart": 43008, + "codePointEnd": 43055, + "key": "syloty-nagri", + "name": "Syloti Nagri", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "Sylheti, Bengali", + "countries": [ + { + "name": "Bangladesh", + "x": 177, + "y": 44 + }, + { + "name": "India", + "x": 169, + "y": 47 + } + ] + }, + { + "codePointStart": 43056, + "codePointEnd": 43071, + "key": "common-indic-number-forms", + "name": "Common Indic Number Forms", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 43072, + "codePointEnd": 43135, + "key": "phags-pa", + "name": "Phags-pa", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "Mongolian, Sanskrit, Tibetan, Chinese, Uyghur", + "countries": [ + { + "name": "Mongolia", + "x": 187, + "y": 27 + }, + { + "name": "India", + "x": 169, + "y": 47 + }, + { + "name": "China", + "x": 184, + "y": 37 + }, + { + "name": "Tibet", + "x": 173, + "y": 39 + }, + { + "name": "Kazakhstan", + "x": 161, + "y": 28 + } + ] + }, + { + "codePointStart": 43136, + "codePointEnd": 43231, + "key": "saurashtra", + "name": "Saurashtra", + "categoryKey": "AsiaSC", + "type": "alphabet", + "languages": "Saurashtra", + "countries": [ + { + "name": "Mongolia", + "x": 187, + "y": 27 + } + ] + }, + { + "codePointStart": 43232, + "codePointEnd": 43263, + "key": "devanagari-extended-characters", + "name": "Devanagari Extended", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "Marathi, Indian, Sanskrit, Hindi", + "countries": [ + { + "name": "India", + "x": 169, + "y": 47 + }, + { + "name": "Pakistan", + "x": 163, + "y": 41 + }, + { + "name": "Fiji Islands", + "x": 228, + "y": 74 + }, + { + "name": "Mauritius", + "x": 154, + "y": 74 + } + ] + }, + { + "codePointStart": 43264, + "codePointEnd": 43311, + "key": "kayah-li", + "name": "Kayah Li", + "categoryKey": "AsiaSE", + "type": "abugida", + "languages": "Kayah", + "countries": [ + { + "name": "Burma", + "x": 181, + "y": 47 + }, + { + "name": "Thailand", + "x": 185, + "y": 49 + } + ] + }, + { + "codePointStart": 43312, + "codePointEnd": 43359, + "key": "rejang", + "name": "Rejang", + "categoryKey": "IndOcean", + "type": "abugida", + "languages": "Rejang", + "countries": [ + { + "name": "Sumatra", + "x": 183, + "y": 58 + }, + { + "name": "Indonesia", + "x": 193, + "y": 60 + } + ] + }, + { + "codePointStart": 43360, + "codePointEnd": 43391, + "key": "hangul-jamo-extended-a", + "name": "Hangul Jamo Extended-A", + "categoryKey": "AsiaEast", + "type": "alphabet", + "languages": "Korean", + "countries": [ + { + "name": "North Korea", + "x": 202, + "y": 33 + }, + { + "name": "South Korea", + "x": 203, + "y": 36 + }, + { + "name": "China", + "x": 184, + "y": 37 + }, + { + "name": "Japan", + "x": 210, + "y": 36 + }, + { + "name": "Indonesia", + "x": 193, + "y": 60 + } + ] + }, + { + "codePointStart": 43392, + "codePointEnd": 43487, + "key": "javanese-alphabet", + "name": "Javanese", + "categoryKey": "IndOcean", + "type": "abugida", + "languages": "Javanese, Sundanese", + "countries": [ + { + "name": "Indonesia", + "x": 193, + "y": 60 + } + ] + }, + { + "codePointStart": 43488, + "codePointEnd": 43519, + "key": "myanmar-extended-b", + "name": "Myanmar Extended-B", + "categoryKey": "AsiaSE", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 43520, + "codePointEnd": 43615, + "key": "cham-alphabet", + "name": "Cham", + "categoryKey": "AsiaSE", + "type": "abugida", + "languages": "Cham", + "countries": [ + { + "name": "Cambodia", + "x": 187, + "y": 52 + }, + { + "name": "Vietnam", + "x": 190, + "y": 52 + }, + { + "name": "Thailand", + "x": 185, + "y": 49 + }, + { + "name": "China", + "x": 184, + "y": 37 + } + ] + }, + { + "codePointStart": 43616, + "codePointEnd": 43647, + "key": "myanmar-extended-a", + "name": "Myanmar Extended-A", + "categoryKey": "AsiaSE", + "type": "abugida", + "languages": "", + "countries": [ + { + "name": "Myanmar", + "x": 182, + "y": 47 + }, + { + "name": "Thailand", + "x": 185, + "y": 49 + }, + { + "name": "Bangladesh", + "x": 177, + "y": 44 + }, + { + "name": "Malaysia", + "x": 186, + "y": 57 + } + ] + }, + { + "codePointStart": 43648, + "codePointEnd": 43743, + "key": "tai-viet", + "name": "Tai Viet", + "categoryKey": "AsiaSE", + "type": "abugida", + "languages": "", + "countries": [ + { + "name": "Vietnam", + "x": 190, + "y": 52 + }, + { + "name": "Thailand", + "x": 185, + "y": 49 + }, + { + "name": "Laos", + "x": 186, + "y": 48 + }, + { + "name": "China", + "x": 184, + "y": 37 + } + ] + }, + { + "codePointStart": 43744, + "codePointEnd": 43775, + "key": "meitei-meyek-extensions", + "name": "Meetei Mayek Extensions", + "categoryKey": "AsiaSC", + "type": "abugida", + "languages": "", + "countries": [ + { + "name": "Vietnam", + "x": 190, + "y": 52 + }, + { + "name": "Thailand", + "x": 185, + "y": 49 + }, + { + "name": "Laos", + "x": 186, + "y": 48 + }, + { + "name": "China", + "x": 184, + "y": 37 + } + ] + }, + { + "codePointStart": 43776, + "codePointEnd": 43823, + "key": "ethiopic-extended-a", + "name": "Ethiopic Extended-A", + "categoryKey": "Africa", + "type": "abugida", + "languages": "ethiopian Semitic", + "countries": [ + { + "name": "Ethiopia", + "x": 145, + "y": 54 + }, + { + "name": "Sudan", + "x": 137, + "y": 50 + }, + { + "name": "Eritrea", + "x": 143, + "y": 50 + }, + { + "name": "Somalia", + "x": 148, + "y": 55 + }, + { + "name": "Israel", + "x": 141, + "y": 39 + } + ] + }, + { + "codePointStart": 43824, + "codePointEnd": 43887, + "key": "latin-extended-e", + "name": "Latin Extended-E", + "categoryKey": "Latin", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 43888, + "codePointEnd": 43967, + "key": "cherokee-supplement", + "name": "Cherokee Supplement", + "categoryKey": "Americas", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 43968, + "codePointEnd": 44031, + "key": "meetei-mayek", + "name": "Meetei Mayek", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [ + { + "name": "India", + "x": 169, + "y": 47 + }, + { + "name": "Bangladesh", + "x": 177, + "y": 44 + }, + { + "name": "Burma", + "x": 181, + "y": 47 + } + ] + }, + { + "codePointStart": 44032, + "codePointEnd": 55215, + "key": "hangul-syllables", + "name": "Hangul Syllables", + "categoryKey": "AsiaEast", + "type": "alphabet", + "languages": "Korean, cia-Cia", + "countries": [ + { + "name": "North Korea", + "x": 202, + "y": 33 + }, + { + "name": "South Korea", + "x": 203, + "y": 36 + }, + { + "name": "China", + "x": 184, + "y": 37 + }, + { + "name": "Japan", + "x": 210, + "y": 36 + }, + { + "name": "Indonesia", + "x": 193, + "y": 60 + } + ] + }, + { + "codePointStart": 55216, + "codePointEnd": 55295, + "key": "hangul-jamo-extended-b", + "name": "Hangul Jamo Extended-B", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 55296, + "codePointEnd": 56191, + "key": "high-surrogates", + "name": "High Surrogates", + "categoryKey": "surrogates", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 56192, + "codePointEnd": 56319, + "key": "high-private-use-surrogates", + "name": "High Private Use Surrogates", + "categoryKey": "surrogates", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 56320, + "codePointEnd": 57343, + "key": "low-surrogates", + "name": "Low Surrogates", + "categoryKey": "surrogates", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 57344, + "codePointEnd": 63743, + "key": "private-use-area", + "name": "Private Use Area", + "categoryKey": "private", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 63744, + "codePointEnd": 64255, + "key": "cjk-compatibility-ideographs", + "name": "CJK Compatibility Ideographs", + "categoryKey": "Han", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 64256, + "codePointEnd": 64335, + "key": "alphabetic-presentation-forms", + "name": "Alphabetic Presentation Forms", + "categoryKey": "Latin", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 64336, + "codePointEnd": 65023, + "key": "arabic-presentation-forms-a", + "name": "Arabic Presentation Forms-A", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 65024, + "codePointEnd": 65039, + "key": "variation-selectors", + "name": "Variation Selectors", + "categoryKey": "variation", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 65040, + "codePointEnd": 65055, + "key": "vertical-forms", + "name": "Vertical Forms", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 65056, + "codePointEnd": 65071, + "key": "combining-half-marks", + "name": "Combining Half Marks", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 65072, + "codePointEnd": 65103, + "key": "cjk-compatibility-forms", + "name": "CJK Compatibility Forms", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 65104, + "codePointEnd": 65135, + "key": "small-form-variants", + "name": "Small Form Variants", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 65136, + "codePointEnd": 65279, + "key": "arabic-presentation-forms-b", + "name": "Arabic Presentation Forms-B", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 65280, + "codePointEnd": 65519, + "key": "halfwidth-and-fullwidth-forms", + "name": "Halfwidth and Fullwidth Forms", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 65520, + "codePointEnd": 65535, + "key": "specials", + "name": "Specials", + "categoryKey": "misc", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 65536, + "codePointEnd": 65663, + "key": "linear-b-syllabary", + "name": "Linear B Syllabary", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 65664, + "codePointEnd": 65791, + "key": "linear-b-ideograms", + "name": "Linear B Ideograms", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 65792, + "codePointEnd": 65855, + "key": "aegean-numbers", + "name": "Aegean Numbers", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 65856, + "codePointEnd": 65935, + "key": "ancient-greek-numbers", + "name": "Ancient Greek Numbers", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 65936, + "codePointEnd": 65999, + "key": "ancient-symbols", + "name": "Ancient Symbols", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 66000, + "codePointEnd": 66047, + "key": "phaistos-disc", + "name": "Phaistos Disc", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 66176, + "codePointEnd": 66207, + "key": "lycian", + "name": "Lycian", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 66208, + "codePointEnd": 66271, + "key": "carian", + "name": "Carian", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 66272, + "codePointEnd": 66303, + "key": "coptic-epact-numbers", + "name": "Coptic Epact Numbers", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 66304, + "codePointEnd": 66351, + "key": "old-italic", + "name": "Old Italic", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 66352, + "codePointEnd": 66383, + "key": "gothic", + "name": "Gothic", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 66384, + "codePointEnd": 66431, + "key": "old-permic", + "name": "Old Permic", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 66432, + "codePointEnd": 66463, + "key": "ugaritic", + "name": "Ugaritic", + "categoryKey": "cuneiform", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 66464, + "codePointEnd": 66527, + "key": "old-persian", + "name": "Old Persian", + "categoryKey": "cuneiform", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 66560, + "codePointEnd": 66639, + "key": "deseret", + "name": "Deseret", + "categoryKey": "Americas", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 66640, + "codePointEnd": 66687, + "key": "shavian", + "name": "Shavian", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 66688, + "codePointEnd": 66735, + "key": "osmanya", + "name": "Osmanya", + "categoryKey": "Africa", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 66736, + "codePointEnd": 66815, + "key": "osage", + "name": "Osage", + "categoryKey": "Americas", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 66816, + "codePointEnd": 66863, + "key": "elbasan", + "name": "Elbasan", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 66864, + "codePointEnd": 66927, + "key": "caucasian-albanian", + "name": "Caucasian Albanian", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 67072, + "codePointEnd": 67455, + "key": "linear-a", + "name": "Linear A", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 67584, + "codePointEnd": 67647, + "key": "cypriot-syllabary", + "name": "Cypriot Syllabary", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 67648, + "codePointEnd": 67679, + "key": "imperial-aramaic", + "name": "Imperial Aramaic", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 67680, + "codePointEnd": 67711, + "key": "palmyrene", + "name": "Palmyrene", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 67712, + "codePointEnd": 67759, + "key": "nabataean", + "name": "Nabataean", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 67808, + "codePointEnd": 67839, + "key": "hatran", + "name": "Hatran", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 67840, + "codePointEnd": 67871, + "key": "phoenician", + "name": "Phoenician", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 67872, + "codePointEnd": 67903, + "key": "lydian", + "name": "Lydian", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 67968, + "codePointEnd": 67999, + "key": "meroitic-hieroglyphs", + "name": "Meroitic Hieroglyphs", + "categoryKey": "hieroglyphs", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 68000, + "codePointEnd": 68095, + "key": "meroitic-cursive", + "name": "Meroitic Cursive", + "categoryKey": "hieroglyphs", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 68096, + "codePointEnd": 68191, + "key": "kharoshthi", + "name": "Kharoshthi", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 68192, + "codePointEnd": 68223, + "key": "old-south-arabian", + "name": "Old South Arabian", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 68224, + "codePointEnd": 68255, + "key": "old-north-arabian", + "name": "Old North Arabian", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 68288, + "codePointEnd": 68351, + "key": "manichaean", + "name": "Manichaean", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 68352, + "codePointEnd": 68415, + "key": "avestan", + "name": "Avestan", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 68416, + "codePointEnd": 68447, + "key": "inscriptional-parthian", + "name": "Inscriptional Parthian", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 68448, + "codePointEnd": 68479, + "key": "inscriptional-pahlavi", + "name": "Inscriptional Pahlavi", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 68480, + "codePointEnd": 68527, + "key": "psalter-pahlavi", + "name": "Psalter Pahlavi", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 68608, + "codePointEnd": 68687, + "key": "old-turkic", + "name": "Old Turkic", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 68736, + "codePointEnd": 68863, + "key": "old-hungarian", + "name": "Old Hungarian", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 68864, + "codePointEnd": 68927, + "key": "hanifi-rohingya", + "name": "Hanifi Rohingya", + "categoryKey": "AsiaSE", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 69216, + "codePointEnd": 69247, + "key": "rumi-numeral-symbols", + "name": "Rumi Numeral Symbols", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 69248, + "codePointEnd": 69311, + "key": "yezidi", + "name": "Yezidi", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 69376, + "codePointEnd": 69423, + "key": "old-sogdian", + "name": "Old Sogdian", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 69424, + "codePointEnd": 69487, + "key": "sogdian", + "name": "Sogdian", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 69552, + "codePointEnd": 69599, + "key": "chorasmian", + "name": "Chorasmian", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 69600, + "codePointEnd": 69631, + "key": "elymaic", + "name": "Elymaic", + "categoryKey": "ME", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 69632, + "codePointEnd": 69759, + "key": "brahmi", + "name": "Brahmi", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 69760, + "codePointEnd": 69839, + "key": "kaithi", + "name": "Kaithi", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 69840, + "codePointEnd": 69887, + "key": "sora-sompeng", + "name": "Sora Sompeng", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 69888, + "codePointEnd": 69967, + "key": "chakma", + "name": "Chakma", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 69968, + "codePointEnd": 70015, + "key": "mahajani", + "name": "Mahajani", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 70016, + "codePointEnd": 70111, + "key": "sharada", + "name": "Sharada", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 70112, + "codePointEnd": 70143, + "key": "sinhala-archaic-numbers", + "name": "Sinhala Archaic Numbers", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 70144, + "codePointEnd": 70223, + "key": "khojki", + "name": "Khojki", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 70272, + "codePointEnd": 70319, + "key": "multani", + "name": "Multani", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 70320, + "codePointEnd": 70399, + "key": "khudawadi", + "name": "Khudawadi", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 70400, + "codePointEnd": 70527, + "key": "grantha", + "name": "Grantha", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 70656, + "codePointEnd": 70783, + "key": "newa", + "name": "Newa", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 70784, + "codePointEnd": 70879, + "key": "tirhuta", + "name": "Tirhuta", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 71040, + "codePointEnd": 71167, + "key": "siddham", + "name": "Siddham", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 71168, + "codePointEnd": 71263, + "key": "modi", + "name": "Modi", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 71264, + "codePointEnd": 71295, + "key": "mongolian-supplement", + "name": "Mongolian Supplement", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 71296, + "codePointEnd": 71375, + "key": "takri", + "name": "Takri", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 71424, + "codePointEnd": 71487, + "key": "ahom", + "name": "Ahom", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 71680, + "codePointEnd": 71759, + "key": "dogra", + "name": "Dogra", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 71840, + "codePointEnd": 71935, + "key": "warang-citi", + "name": "Warang Citi", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 71936, + "codePointEnd": 72031, + "key": "dives-akuru", + "name": "Dives Akuru", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 72096, + "codePointEnd": 72191, + "key": "nandinagari", + "name": "Nandinagari", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 72192, + "codePointEnd": 72271, + "key": "zanabazar-square", + "name": "Zanabazar Square", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 72272, + "codePointEnd": 72367, + "key": "soyombo", + "name": "Soyombo", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 72384, + "codePointEnd": 72447, + "key": "pau-cin-hau", + "name": "Pau Cin Hau", + "categoryKey": "AsiaSE", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 72704, + "codePointEnd": 72815, + "key": "bhaiksuki", + "name": "Bhaiksuki", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 72816, + "codePointEnd": 72895, + "key": "marchen", + "name": "Marchen", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 72960, + "codePointEnd": 73055, + "key": "masaram-gondi", + "name": "Masaram Gondi", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 73056, + "codePointEnd": 73135, + "key": "gunjala-gondi", + "name": "Gunjala Gondi", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 73440, + "codePointEnd": 73471, + "key": "makasar", + "name": "Makasar", + "categoryKey": "IndOcean", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 73648, + "codePointEnd": 73663, + "key": "lisu-supplement", + "name": "Lisu Supplement", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 73664, + "codePointEnd": 73727, + "key": "tamil-supplement", + "name": "Tamil Supplement", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 73728, + "codePointEnd": 74751, + "key": "cuneiform", + "name": "Cuneiform", + "categoryKey": "cuneiform", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 74752, + "codePointEnd": 74879, + "key": "cuneiform-numbers-and-punctuation", + "name": "Cuneiform Numbers and Punctuation", + "categoryKey": "cuneiform", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 74880, + "codePointEnd": 75087, + "key": "early-dynastic-cuneiform", + "name": "Early Dynastic Cuneiform", + "categoryKey": "cuneiform", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 77824, + "codePointEnd": 78895, + "key": "egyptian-hieroglyphs", + "name": "Egyptian Hieroglyphs", + "categoryKey": "hieroglyphs", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 78896, + "codePointEnd": 78911, + "key": "egyptian-hieroglyph-format-controls", + "name": "Egyptian Hieroglyph Format Controls", + "categoryKey": "hieroglyphs", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 82944, + "codePointEnd": 83583, + "key": "anatolian-hieroglyphs", + "name": "Anatolian Hieroglyphs", + "categoryKey": "hieroglyphs", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 92160, + "codePointEnd": 92735, + "key": "bamum-supplement", + "name": "Bamum Supplement", + "categoryKey": "Africa", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 92736, + "codePointEnd": 92783, + "key": "mro", + "name": "Mro", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 92880, + "codePointEnd": 92927, + "key": "bassa-vah", + "name": "Bassa Vah", + "categoryKey": "Africa", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 92928, + "codePointEnd": 93071, + "key": "pahawh-hmong", + "name": "Pahawh Hmong", + "categoryKey": "AsiaSE", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 93760, + "codePointEnd": 93855, + "key": "medefaidrin", + "name": "Medefaidrin", + "categoryKey": "Africa", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 93952, + "codePointEnd": 94111, + "key": "miao", + "name": "Miao", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 94176, + "codePointEnd": 94207, + "key": "ideographic-symbols-and-punctuation", + "name": "Ideographic Symbols and Punctuation", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 94208, + "codePointEnd": 100351, + "key": "tangut", + "name": "Tangut", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 100352, + "codePointEnd": 101119, + "key": "tangut-components", + "name": "Tangut Components", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 101120, + "codePointEnd": 101631, + "key": "khitan-small-script", + "name": "Khitan Small Script", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 101632, + "codePointEnd": 101775, + "key": "tangut-supplement", + "name": "Tangut Supplement", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 110592, + "codePointEnd": 110847, + "key": "kana-supplement", + "name": "Kana Supplement", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 110848, + "codePointEnd": 110895, + "key": "kana-extended-a", + "name": "Kana Extended-A", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 110896, + "codePointEnd": 110959, + "key": "small-kana-extension", + "name": "Small Kana Extension", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 110960, + "codePointEnd": 111359, + "key": "nushu", + "name": "Nushu", + "categoryKey": "AsiaEast", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 113664, + "codePointEnd": 113823, + "key": "duployan", + "name": "Duployan", + "categoryKey": "notation", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 113824, + "codePointEnd": 113839, + "key": "shorthand-format-controls", + "name": "Shorthand Format Controls", + "categoryKey": "notation", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 118784, + "codePointEnd": 119039, + "key": "byzantine-musical-symbols", + "name": "Byzantine Musical Symbols", + "categoryKey": "notation", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 119040, + "codePointEnd": 119295, + "key": "musical-symbols", + "name": "Musical Symbols", + "categoryKey": "notation", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 119296, + "codePointEnd": 119375, + "key": "ancient-greek-musical-notation", + "name": "Ancient Greek Musical Notation", + "categoryKey": "notation", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 119520, + "codePointEnd": 119551, + "key": "mayan-numerals", + "name": "Mayan Numerals", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 119552, + "codePointEnd": 119647, + "key": "tai-xuan-jing-symbols", + "name": "Tai Xuan Jing Symbols", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 119648, + "codePointEnd": 119679, + "key": "counting-rod-numerals", + "name": "Counting Rod Numerals", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 119808, + "codePointEnd": 120831, + "key": "mathematical-alphanumeric-symbols", + "name": "Mathematical Alphanumeric Symbols", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 120832, + "codePointEnd": 121519, + "key": "sutton-sign-writing", + "name": "Sutton SignWriting", + "categoryKey": "notation", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 122880, + "codePointEnd": 122927, + "key": "glagolitic-supplement", + "name": "Glagolitic Supplement", + "categoryKey": "Europe", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 123136, + "codePointEnd": 123215, + "key": "nyiakeng-puachue-hmong", + "name": "Nyiakeng Puachue Hmong", + "categoryKey": "AsiaSE", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 123584, + "codePointEnd": 123647, + "key": "wancho", + "name": "Wancho", + "categoryKey": "AsiaSC", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 124928, + "codePointEnd": 125151, + "key": "mende-kikakui", + "name": "Mende Kikakui", + "categoryKey": "Africa", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 125184, + "codePointEnd": 125279, + "key": "adlam", + "name": "Adlam", + "categoryKey": "Africa", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 126064, + "codePointEnd": 126143, + "key": "indic-siyaq-numbers", + "name": "Indic Siyaq Numbers", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 126208, + "codePointEnd": 126287, + "key": "ottoman-siyaq-numbers", + "name": "Ottoman Siyaq Numbers", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 126464, + "codePointEnd": 126719, + "key": "arabic-mathematical-alphabetic-symbols", + "name": "Arabic Mathematical Alphabetic Symbols", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 126976, + "codePointEnd": 127023, + "key": "mahjong-tiles", + "name": "Mahjong Tiles", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 127024, + "codePointEnd": 127135, + "key": "domino-tiles", + "name": "Domino Tiles", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 127136, + "codePointEnd": 127231, + "key": "playing-cards", + "name": "Playing Cards", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 127232, + "codePointEnd": 127487, + "key": "enclosed-alphanumeric-supplement", + "name": "Enclosed Alphanumeric Supplement", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 127488, + "codePointEnd": 127743, + "key": "enclosed-ideographic-supplement", + "name": "Enclosed Ideographic Supplement", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 127744, + "codePointEnd": 128511, + "key": "miscellaneous-symbols-and-pictographs", + "name": "Miscellaneous Symbols and Pictographs", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 128512, + "codePointEnd": 128591, + "key": "emoticons", + "name": "Emoticons (Emoji)", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 128592, + "codePointEnd": 128639, + "key": "ornamental-dingbats", + "name": "Ornamental Dingbats", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 128640, + "codePointEnd": 128767, + "key": "transport-and-map-symbols", + "name": "Transport and Map Symbols", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 128768, + "codePointEnd": 128895, + "key": "alchemical-symbols", + "name": "Alchemical Symbols", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 128896, + "codePointEnd": 129023, + "key": "geometric-shapes-extended", + "name": "Geometric Shapes Extended", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 129024, + "codePointEnd": 129279, + "key": "supplemental-arrows-c", + "name": "Supplemental Arrows-C", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 129280, + "codePointEnd": 129535, + "key": "supplemental-symbols-and-pictographs", + "name": "Supplemental Symbols and Pictographs", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 129536, + "codePointEnd": 129647, + "key": "chess-symbols", + "name": "Chess Symbols", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 129648, + "codePointEnd": 129791, + "key": "symbols-and-pictographs-extended-a", + "name": "Symbols and Pictographs Extended-A", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 129792, + "codePointEnd": 130047, + "key": "symbols-for-legacy-computing", + "name": "Symbols for Legacy Computing", + "categoryKey": "symbols", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 131072, + "codePointEnd": 173791, + "key": "cjk-unified-ideographs-extension-b", + "name": "CJK Unified Ideographs Extension B", + "categoryKey": "Han", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 173824, + "codePointEnd": 177983, + "key": "cjk-unified-ideographs-extension-c", + "name": "CJK Unified Ideographs Extension C", + "categoryKey": "Han", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 177984, + "codePointEnd": 178207, + "key": "cjk-unified-ideographs-extension-d", + "name": "CJK Unified Ideographs Extension D", + "categoryKey": "Han", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 178208, + "codePointEnd": 183983, + "key": "cjk-unified-ideographs-extension-e", + "name": "CJK Unified Ideographs Extension E", + "categoryKey": "Han", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 183984, + "codePointEnd": 191471, + "key": "cjk-unified-ideographs-extension-f", + "name": "CJK Unified Ideographs Extension F", + "categoryKey": "Han", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 194560, + "codePointEnd": 195103, + "key": "cjk-compatibility-ideographs-supplement", + "name": "CJK Compatibility Ideographs Supplement", + "categoryKey": "Han", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 196608, + "codePointEnd": 201551, + "key": "cjk-unified-ideographs-extension-g", + "name": "CJK Unified Ideographs Extension G", + "categoryKey": "Han", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 917504, + "codePointEnd": 917631, + "key": "tags", + "name": "Tags", + "categoryKey": "tags", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 917760, + "codePointEnd": 917999, + "key": "variation-selectors-supplement", + "name": "Variation Selectors Supplement", + "categoryKey": "variation", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 983040, + "codePointEnd": 1048575, + "key": "supplementary-private-use-area-a", + "name": "Supplementary Private Use Area-A", + "categoryKey": "private", + "type": "", + "languages": "", + "countries": [], + "points": [] + }, + { + "codePointStart": 1048576, + "codePointEnd": 1114111, + "key": "supplementary-private-use-area-b", + "name": "Supplementary Private Use Area-B", + "categoryKey": "private", + "type": "", + "languages": "", + "countries": [], + "points": [] + } +] diff --git a/PlaygroundBook/PrivateResources/JSON/categories.json b/PlaygroundBook/PrivateResources/JSON/categories.json new file mode 100644 index 0000000..202791d --- /dev/null +++ b/PlaygroundBook/PrivateResources/JSON/categories.json @@ -0,0 +1,78 @@ +[ + { + "key": "Africa", + "name": "African scripts" + }, + { + "key": "Americas", + "name": "American scripts" + }, + { + "key": "AsiaEast", + "name": "East Asian scripts" + }, + { + "key": "AsiaSC", + "name": "South and Central Asian scripts" + }, + { + "key": "AsiaSE", + "name": "Southeast Asian scripts" + }, + { + "key": "cuneiform", + "name": "Cuneiform" + }, + { + "key": "Europe", + "name": "Non-Latin European scripts" + }, + { + "key": "Han", + "name": "CJK characters" + }, + { + "key": "hieroglyphs", + "name": "Hieroglyphs" + }, + { + "key": "IndOcean", + "name": "Indonesian and Oceanic scripts" + }, + { + "key": "Latin", + "name": "Latin script" + }, + { + "key": "ME", + "name": "Middle Eastern and Southwest Asian scripts" + }, + { + "key": "misc", + "name": "Miscellaneous characters" + }, + { + "key": "notation", + "name": "Notational systems" + }, + { + "key": "private", + "name": "Private use" + }, + { + "key": "surrogates", + "name": "UTF-16 surrogates" + }, + { + "key": "symbols", + "name": "Symbols" + }, + { + "key": "tags", + "name": "Tags" + }, + { + "key": "variation", + "name": "Variation Selectors" + } +] diff --git a/PlaygroundBook/PrivateResources/JSON/emoji-input.json b/PlaygroundBook/PrivateResources/JSON/emoji-input.json new file mode 100644 index 0000000..0a6b330 --- /dev/null +++ b/PlaygroundBook/PrivateResources/JSON/emoji-input.json @@ -0,0 +1,2750 @@ +{ + "zwj-sequences": [ + { + "title": "ZWJ Sequences", + "characters": [ + "👨‍❤️‍👨", + "👨‍❤️‍💋‍👨", + "👨‍👦", + "👨‍👦‍👦", + "👨‍👧", + "👨‍👧‍👦", + "👨‍👧‍👧", + "👨‍👨‍👦", + "👨‍👨‍👦‍👦", + "👨‍👨‍👧", + "👨‍👨‍👧‍👦", + "👨‍👨‍👧‍👧", + "👨‍👩‍👦", + "👨‍👩‍👦‍👦", + "👨‍👩‍👧", + "👨‍👩‍👧‍👦", + "👨‍👩‍👧‍👧", + "👩‍❤️‍👨", + "👩‍❤️‍👩", + "👩‍❤️‍💋‍👨", + "👩‍❤️‍💋‍👩", + "👩‍👦", + "👩‍👦‍👦", + "👩‍👧", + "👩‍👧‍👦", + "👩‍👧‍👧", + "👩‍👩‍👦", + "👩‍👩‍👦‍👦", + "👩‍👩‍👧", + "👩‍👩‍👧‍👦", + "👩‍👩‍👧‍👧", + "🧑‍🤝‍🧑", + "🧑🏻‍🎄", + "🧑🏻‍🤝‍🧑🏻", + "🧑🏻‍🤝‍🧑🏼", + "🧑🏻‍🤝‍🧑🏽", + "🧑🏻‍🤝‍🧑🏾", + "🧑🏻‍🤝‍🧑🏿", + "🧑🏼‍🎄", + "🧑🏼‍🤝‍🧑🏻", + "🧑🏼‍🤝‍🧑🏼", + "🧑🏼‍🤝‍🧑🏽", + "🧑🏼‍🤝‍🧑🏾", + "🧑🏼‍🤝‍🧑🏿", + "🧑🏽‍🎄", + "🧑🏽‍🤝‍🧑🏻", + "🧑🏽‍🤝‍🧑🏼", + "🧑🏽‍🤝‍🧑🏽", + "🧑🏽‍🤝‍🧑🏾", + "🧑🏽‍🤝‍🧑🏿", + "🧑🏾‍🎄", + "🧑🏾‍🤝‍🧑🏻", + "🧑🏾‍🤝‍🧑🏼", + "🧑🏾‍🤝‍🧑🏽", + "🧑🏾‍🤝‍🧑🏾", + "🧑🏾‍🤝‍🧑🏿", + "🧑🏿‍🎄", + "🧑🏿‍🤝‍🧑🏻", + "🧑🏿‍🤝‍🧑🏼", + "🧑🏿‍🤝‍🧑🏽", + "🧑🏿‍🤝‍🧑🏾", + "🧑🏿‍🤝‍🧑🏿", + "👨‍⚕️", + "👨‍⚖️", + "👨‍✈️", + "👨‍🌾", + "👨‍🍳", + "👨‍🍼", + "👨‍🎓", + "👨‍🎤", + "👨‍🎨", + "👨‍🏫", + "👨‍🏭", + "👨‍💻", + "👨‍💼", + "👨‍🔧", + "👨‍🔬", + "👨‍🚀", + "👨‍🚒", + "👨‍🦯", + "👨‍🦼", + "👨‍🦽", + "👨🏻‍⚕️", + "👨🏻‍⚖️", + "👨🏻‍✈️", + "👨🏻‍🌾", + "👨🏻‍🍳", + "👨🏻‍🍼", + "👨🏻‍🎓", + "👨🏻‍🎤", + "👨🏻‍🎨", + "👨🏻‍🏫", + "👨🏻‍🏭", + "👨🏻‍💻", + "👨🏻‍💼", + "👨🏻‍🔧", + "👨🏻‍🔬", + "👨🏻‍🚀", + "👨🏻‍🚒", + "👨🏻‍🦯", + "👨🏻‍🦼", + "👨🏻‍🦽", + "👨🏼‍⚕️", + "👨🏼‍⚖️", + "👨🏼‍✈️", + "👨🏼‍🌾", + "👨🏼‍🍳", + "👨🏼‍🍼", + "👨🏼‍🎓", + "👨🏼‍🎤", + "👨🏼‍🎨", + "👨🏼‍🏫", + "👨🏼‍🏭", + "👨🏼‍💻", + "👨🏼‍💼", + "👨🏼‍🔧", + "👨🏼‍🔬", + "👨🏼‍🚀", + "👨🏼‍🚒", + "👨🏼‍🦯", + "👨🏼‍🦼", + "👨🏼‍🦽", + "👨🏽‍⚕️", + "👨🏽‍⚖️", + "👨🏽‍✈️", + "👨🏽‍🌾", + "👨🏽‍🍳", + "👨🏽‍🍼", + "👨🏽‍🎓", + "👨🏽‍🎤", + "👨🏽‍🎨", + "👨🏽‍🏫", + "👨🏽‍🏭", + "👨🏽‍💻", + "👨🏽‍💼", + "👨🏽‍🔧", + "👨🏽‍🔬", + "👨🏽‍🚀", + "👨🏽‍🚒", + "👨🏽‍🦯", + "👨🏽‍🦼", + "👨🏽‍🦽", + "👨🏾‍⚕️", + "👨🏾‍⚖️", + "👨🏾‍✈️", + "👨🏾‍🌾", + "👨🏾‍🍳", + "👨🏾‍🍼", + "👨🏾‍🎓", + "👨🏾‍🎤", + "👨🏾‍🎨", + "👨🏾‍🏫", + "👨🏾‍🏭", + "👨🏾‍💻", + "👨🏾‍💼", + "👨🏾‍🔧", + "👨🏾‍🔬", + "👨🏾‍🚀", + "👨🏾‍🚒", + "👨🏾‍🦯", + "👨🏾‍🦼", + "👨🏾‍🦽", + "👨🏿‍⚕️", + "👨🏿‍⚖️", + "👨🏿‍✈️", + "👨🏿‍🌾", + "👨🏿‍🍳", + "👨🏿‍🍼", + "👨🏿‍🎓", + "👨🏿‍🎤", + "👨🏿‍🎨", + "👨🏿‍🏫", + "👨🏿‍🏭", + "👨🏿‍💻", + "👨🏿‍💼", + "👨🏿‍🔧", + "👨🏿‍🔬", + "👨🏿‍🚀", + "👨🏿‍🚒", + "👨🏿‍🦯", + "👨🏿‍🦼", + "👨🏿‍🦽", + "👩‍⚕️", + "👩‍⚖️", + "👩‍✈️", + "👩‍🌾", + "👩‍🍳", + "👩‍🍼", + "👩‍🎓", + "👩‍🎤", + "👩‍🎨", + "👩‍🏫", + "👩‍🏭", + "👩‍💻", + "👩‍💼", + "👩‍🔧", + "👩‍🔬", + "👩‍🚀", + "👩‍🚒", + "👩‍🦯", + "👩‍🦼", + "👩‍🦽", + "👩🏻‍⚕️", + "👩🏻‍⚖️", + "👩🏻‍✈️", + "👩🏻‍🌾", + "👩🏻‍🍳", + "👩🏻‍🍼", + "👩🏻‍🎓", + "👩🏻‍🎤", + "👩🏻‍🎨", + "👩🏻‍🏫", + "👩🏻‍🏭", + "👩🏻‍💻", + "👩🏻‍💼", + "👩🏻‍🔧", + "👩🏻‍🔬", + "👩🏻‍🚀", + "👩🏻‍🚒", + "👩🏻‍🦯", + "👩🏻‍🦼", + "👩🏻‍🦽", + "👩🏼‍⚕️", + "👩🏼‍⚖️", + "👩🏼‍✈️", + "👩🏼‍🌾", + "👩🏼‍🍳", + "👩🏼‍🍼", + "👩🏼‍🎓", + "👩🏼‍🎤", + "👩🏼‍🎨", + "👩🏼‍🏫", + "👩🏼‍🏭", + "👩🏼‍💻", + "👩🏼‍💼", + "👩🏼‍🔧", + "👩🏼‍🔬", + "👩🏼‍🚀", + "👩🏼‍🚒", + "👩🏼‍🦯", + "👩🏼‍🦼", + "👩🏼‍🦽", + "👩🏽‍⚕️", + "👩🏽‍⚖️", + "👩🏽‍✈️", + "👩🏽‍🌾", + "👩🏽‍🍳", + "👩🏽‍🍼", + "👩🏽‍🎓", + "👩🏽‍🎤", + "👩🏽‍🎨", + "👩🏽‍🏫", + "👩🏽‍🏭", + "👩🏽‍💻", + "👩🏽‍💼", + "👩🏽‍🔧", + "👩🏽‍🔬", + "👩🏽‍🚀", + "👩🏽‍🚒", + "👩🏽‍🦯", + "👩🏽‍🦼", + "👩🏽‍🦽", + "👩🏾‍⚕️", + "👩🏾‍⚖️", + "👩🏾‍✈️", + "👩🏾‍🌾", + "👩🏾‍🍳", + "👩🏾‍🍼", + "👩🏾‍🎓", + "👩🏾‍🎤", + "👩🏾‍🎨", + "👩🏾‍🏫", + "👩🏾‍🏭", + "👩🏾‍💻", + "👩🏾‍💼", + "👩🏾‍🔧", + "👩🏾‍🔬", + "👩🏾‍🚀", + "👩🏾‍🚒", + "👩🏾‍🦯", + "👩🏾‍🦼", + "👩🏾‍🦽", + "👩🏿‍⚕️", + "👩🏿‍⚖️", + "👩🏿‍✈️", + "👩🏿‍🌾", + "👩🏿‍🍳", + "👩🏿‍🍼", + "👩🏿‍🎓", + "👩🏿‍🎤", + "👩🏿‍🎨", + "👩🏿‍🏫", + "👩🏿‍🏭", + "👩🏿‍💻", + "👩🏿‍💼", + "👩🏿‍🔧", + "👩🏿‍🔬", + "👩🏿‍🚀", + "👩🏿‍🚒", + "👩🏿‍🦯", + "👩🏿‍🦼", + "👩🏿‍🦽", + "🧑‍⚕️", + "🧑‍⚖️", + "🧑‍✈️", + "🧑‍🌾", + "🧑‍🍳", + "🧑‍🍼", + "🧑‍🎓", + "🧑‍🎤", + "🧑‍🎨", + "🧑‍🏫", + "🧑‍🏭", + "🧑‍💻", + "🧑‍💼", + "🧑‍🔧", + "🧑‍🔬", + "🧑‍🚀", + "🧑‍🚒", + "🧑‍🦯", + "🧑‍🦼", + "🧑‍🦽", + "🧑🏻‍⚕️", + "🧑🏻‍⚖️", + "🧑🏻‍✈️", + "🧑🏻‍🌾", + "🧑🏻‍🍳", + "🧑🏻‍🍼", + "🧑🏻‍🎓", + "🧑🏻‍🎤", + "🧑🏻‍🎨", + "🧑🏻‍🏫", + "🧑🏻‍🏭", + "🧑🏻‍💻", + "🧑🏻‍💼", + "🧑🏻‍🔧", + "🧑🏻‍🔬", + "🧑🏻‍🚀", + "🧑🏻‍🚒", + "🧑🏻‍🦯", + "🧑🏻‍🦼", + "🧑🏻‍🦽", + "🧑🏼‍⚕️", + "🧑🏼‍⚖️", + "🧑🏼‍✈️", + "🧑🏼‍🌾", + "🧑🏼‍🍳", + "🧑🏼‍🍼", + "🧑🏼‍🎓", + "🧑🏼‍🎤", + "🧑🏼‍🎨", + "🧑🏼‍🏫", + "🧑🏼‍🏭", + "🧑🏼‍💻", + "🧑🏼‍💼", + "🧑🏼‍🔧", + "🧑🏼‍🔬", + "🧑🏼‍🚀", + "🧑🏼‍🚒", + "🧑🏼‍🦯", + "🧑🏼‍🦼", + "🧑🏼‍🦽", + "🧑🏽‍⚕️", + "🧑🏽‍⚖️", + "🧑🏽‍✈️", + "🧑🏽‍🌾", + "🧑🏽‍🍳", + "🧑🏽‍🍼", + "🧑🏽‍🎓", + "🧑🏽‍🎤", + "🧑🏽‍🎨", + "🧑🏽‍🏫", + "🧑🏽‍🏭", + "🧑🏽‍💻", + "🧑🏽‍💼", + "🧑🏽‍🔧", + "🧑🏽‍🔬", + "🧑🏽‍🚀", + "🧑🏽‍🚒", + "🧑🏽‍🦯", + "🧑🏽‍🦼", + "🧑🏽‍🦽", + "🧑🏾‍⚕️", + "🧑🏾‍⚖️", + "🧑🏾‍✈️", + "🧑🏾‍🌾", + "🧑🏾‍🍳", + "🧑🏾‍🍼", + "🧑🏾‍🎓", + "🧑🏾‍🎤", + "🧑🏾‍🎨", + "🧑🏾‍🏫", + "🧑🏾‍🏭", + "🧑🏾‍💻", + "🧑🏾‍💼", + "🧑🏾‍🔧", + "🧑🏾‍🔬", + "🧑🏾‍🚀", + "🧑🏾‍🚒", + "🧑🏾‍🦯", + "🧑🏾‍🦼", + "🧑🏾‍🦽", + "🧑🏿‍⚕️", + "🧑🏿‍⚖️", + "🧑🏿‍✈️", + "🧑🏿‍🌾", + "🧑🏿‍🍳", + "🧑🏿‍🍼", + "🧑🏿‍🎓", + "🧑🏿‍🎤", + "🧑🏿‍🎨", + "🧑🏿‍🏫", + "🧑🏿‍🏭", + "🧑🏿‍💻", + "🧑🏿‍💼", + "🧑🏿‍🔧", + "🧑🏿‍🔬", + "🧑🏿‍🚀", + "🧑🏿‍🚒", + "🧑🏿‍🦯", + "🧑🏿‍🦼", + "🧑🏿‍🦽", + "⛹🏻‍♀️", + "⛹🏻‍♂️", + "⛹🏼‍♀️", + "⛹🏼‍♂️", + "⛹🏽‍♀️", + "⛹🏽‍♂️", + "⛹🏾‍♀️", + "⛹🏾‍♂️", + "⛹🏿‍♀️", + "⛹🏿‍♂️", + "⛹️‍♀️", + "⛹️‍♂️", + "🏃‍♀️", + "🏃‍♂️", + "🏃🏻‍♀️", + "🏃🏻‍♂️", + "🏃🏼‍♀️", + "🏃🏼‍♂️", + "🏃🏽‍♀️", + "🏃🏽‍♂️", + "🏃🏾‍♀️", + "🏃🏾‍♂️", + "🏃🏿‍♀️", + "🏃🏿‍♂️", + "🏄‍♀️", + "🏄‍♂️", + "🏄🏻‍♀️", + "🏄🏻‍♂️", + "🏄🏼‍♀️", + "🏄🏼‍♂️", + "🏄🏽‍♀️", + "🏄🏽‍♂️", + "🏄🏾‍♀️", + "🏄🏾‍♂️", + "🏄🏿‍♀️", + "🏄🏿‍♂️", + "🏊‍♀️", + "🏊‍♂️", + "🏊🏻‍♀️", + "🏊🏻‍♂️", + "🏊🏼‍♀️", + "🏊🏼‍♂️", + "🏊🏽‍♀️", + "🏊🏽‍♂️", + "🏊🏾‍♀️", + "🏊🏾‍♂️", + "🏊🏿‍♀️", + "🏊🏿‍♂️", + "🏋🏻‍♀️", + "🏋🏻‍♂️", + "🏋🏼‍♀️", + "🏋🏼‍♂️", + "🏋🏽‍♀️", + "🏋🏽‍♂️", + "🏋🏾‍♀️", + "🏋🏾‍♂️", + "🏋🏿‍♀️", + "🏋🏿‍♂️", + "🏋️‍♀️", + "🏋️‍♂️", + "🏌🏻‍♀️", + "🏌🏻‍♂️", + "🏌🏼‍♀️", + "🏌🏼‍♂️", + "🏌🏽‍♀️", + "🏌🏽‍♂️", + "🏌🏾‍♀️", + "🏌🏾‍♂️", + "🏌🏿‍♀️", + "🏌🏿‍♂️", + "🏌️‍♀️", + "🏌️‍♂️", + "👮‍♀️", + "👮‍♂️", + "👮🏻‍♀️", + "👮🏻‍♂️", + "👮🏼‍♀️", + "👮🏼‍♂️", + "👮🏽‍♀️", + "👮🏽‍♂️", + "👮🏾‍♀️", + "👮🏾‍♂️", + "👮🏿‍♀️", + "👮🏿‍♂️", + "👯‍♀️", + "👯‍♂️", + "👰‍♀️", + "👰‍♂️", + "👰🏻‍♀️", + "👰🏻‍♂️", + "👰🏼‍♀️", + "👰🏼‍♂️", + "👰🏽‍♀️", + "👰🏽‍♂️", + "👰🏾‍♀️", + "👰🏾‍♂️", + "👰🏿‍♀️", + "👰🏿‍♂️", + "👱‍♀️", + "👱‍♂️", + "👱🏻‍♀️", + "👱🏻‍♂️", + "👱🏼‍♀️", + "👱🏼‍♂️", + "👱🏽‍♀️", + "👱🏽‍♂️", + "👱🏾‍♀️", + "👱🏾‍♂️", + "👱🏿‍♀️", + "👱🏿‍♂️", + "👳‍♀️", + "👳‍♂️", + "👳🏻‍♀️", + "👳🏻‍♂️", + "👳🏼‍♀️", + "👳🏼‍♂️", + "👳🏽‍♀️", + "👳🏽‍♂️", + "👳🏾‍♀️", + "👳🏾‍♂️", + "👳🏿‍♀️", + "👳🏿‍♂️", + "👷‍♀️", + "👷‍♂️", + "👷🏻‍♀️", + "👷🏻‍♂️", + "👷🏼‍♀️", + "👷🏼‍♂️", + "👷🏽‍♀️", + "👷🏽‍♂️", + "👷🏾‍♀️", + "👷🏾‍♂️", + "👷🏿‍♀️", + "👷🏿‍♂️", + "💁‍♀️", + "💁‍♂️", + "💁🏻‍♀️", + "💁🏻‍♂️", + "💁🏼‍♀️", + "💁🏼‍♂️", + "💁🏽‍♀️", + "💁🏽‍♂️", + "💁🏾‍♀️", + "💁🏾‍♂️", + "💁🏿‍♀️", + "💁🏿‍♂️", + "💂‍♀️", + "💂‍♂️", + "💂🏻‍♀️", + "💂🏻‍♂️", + "💂🏼‍♀️", + "💂🏼‍♂️", + "💂🏽‍♀️", + "💂🏽‍♂️", + "💂🏾‍♀️", + "💂🏾‍♂️", + "💂🏿‍♀️", + "💂🏿‍♂️", + "💆‍♀️", + "💆‍♂️", + "💆🏻‍♀️", + "💆🏻‍♂️", + "💆🏼‍♀️", + "💆🏼‍♂️", + "💆🏽‍♀️", + "💆🏽‍♂️", + "💆🏾‍♀️", + "💆🏾‍♂️", + "💆🏿‍♀️", + "💆🏿‍♂️", + "💇‍♀️", + "💇‍♂️", + "💇🏻‍♀️", + "💇🏻‍♂️", + "💇🏼‍♀️", + "💇🏼‍♂️", + "💇🏽‍♀️", + "💇🏽‍♂️", + "💇🏾‍♀️", + "💇🏾‍♂️", + "💇🏿‍♀️", + "💇🏿‍♂️", + "🕵🏻‍♀️", + "🕵🏻‍♂️", + "🕵🏼‍♀️", + "🕵🏼‍♂️", + "🕵🏽‍♀️", + "🕵🏽‍♂️", + "🕵🏾‍♀️", + "🕵🏾‍♂️", + "🕵🏿‍♀️", + "🕵🏿‍♂️", + "🕵️‍♀️", + "🕵️‍♂️", + "🙅‍♀️", + "🙅‍♂️", + "🙅🏻‍♀️", + "🙅🏻‍♂️", + "🙅🏼‍♀️", + "🙅🏼‍♂️", + "🙅🏽‍♀️", + "🙅🏽‍♂️", + "🙅🏾‍♀️", + "🙅🏾‍♂️", + "🙅🏿‍♀️", + "🙅🏿‍♂️", + "🙆‍♀️", + "🙆‍♂️", + "🙆🏻‍♀️", + "🙆🏻‍♂️", + "🙆🏼‍♀️", + "🙆🏼‍♂️", + "🙆🏽‍♀️", + "🙆🏽‍♂️", + "🙆🏾‍♀️", + "🙆🏾‍♂️", + "🙆🏿‍♀️", + "🙆🏿‍♂️", + "🙇‍♀️", + "🙇‍♂️", + "🙇🏻‍♀️", + "🙇🏻‍♂️", + "🙇🏼‍♀️", + "🙇🏼‍♂️", + "🙇🏽‍♀️", + "🙇🏽‍♂️", + "🙇🏾‍♀️", + "🙇🏾‍♂️", + "🙇🏿‍♀️", + "🙇🏿‍♂️", + "🙋‍♀️", + "🙋‍♂️", + "🙋🏻‍♀️", + "🙋🏻‍♂️", + "🙋🏼‍♀️", + "🙋🏼‍♂️", + "🙋🏽‍♀️", + "🙋🏽‍♂️", + "🙋🏾‍♀️", + "🙋🏾‍♂️", + "🙋🏿‍♀️", + "🙋🏿‍♂️", + "🙍‍♀️", + "🙍‍♂️", + "🙍🏻‍♀️", + "🙍🏻‍♂️", + "🙍🏼‍♀️", + "🙍🏼‍♂️", + "🙍🏽‍♀️", + "🙍🏽‍♂️", + "🙍🏾‍♀️", + "🙍🏾‍♂️", + "🙍🏿‍♀️", + "🙍🏿‍♂️", + "🙎‍♀️", + "🙎‍♂️", + "🙎🏻‍♀️", + "🙎🏻‍♂️", + "🙎🏼‍♀️", + "🙎🏼‍♂️", + "🙎🏽‍♀️", + "🙎🏽‍♂️", + "🙎🏾‍♀️", + "🙎🏾‍♂️", + "🙎🏿‍♀️", + "🙎🏿‍♂️", + "🚣‍♀️", + "🚣‍♂️", + "🚣🏻‍♀️", + "🚣🏻‍♂️", + "🚣🏼‍♀️", + "🚣🏼‍♂️", + "🚣🏽‍♀️", + "🚣🏽‍♂️", + "🚣🏾‍♀️", + "🚣🏾‍♂️", + "🚣🏿‍♀️", + "🚣🏿‍♂️", + "🚴‍♀️", + "🚴‍♂️", + "🚴🏻‍♀️", + "🚴🏻‍♂️", + "🚴🏼‍♀️", + "🚴🏼‍♂️", + "🚴🏽‍♀️", + "🚴🏽‍♂️", + "🚴🏾‍♀️", + "🚴🏾‍♂️", + "🚴🏿‍♀️", + "🚴🏿‍♂️", + "🚵‍♀️", + "🚵‍♂️", + "🚵🏻‍♀️", + "🚵🏻‍♂️", + "🚵🏼‍♀️", + "🚵🏼‍♂️", + "🚵🏽‍♀️", + "🚵🏽‍♂️", + "🚵🏾‍♀️", + "🚵🏾‍♂️", + "🚵🏿‍♀️", + "🚵🏿‍♂️", + "🚶‍♀️", + "🚶‍♂️", + "🚶🏻‍♀️", + "🚶🏻‍♂️", + "🚶🏼‍♀️", + "🚶🏼‍♂️", + "🚶🏽‍♀️", + "🚶🏽‍♂️", + "🚶🏾‍♀️", + "🚶🏾‍♂️", + "🚶🏿‍♀️", + "🚶🏿‍♂️", + "🤦‍♀️", + "🤦‍♂️", + "🤦🏻‍♀️", + "🤦🏻‍♂️", + "🤦🏼‍♀️", + "🤦🏼‍♂️", + "🤦🏽‍♀️", + "🤦🏽‍♂️", + "🤦🏾‍♀️", + "🤦🏾‍♂️", + "🤦🏿‍♀️", + "🤦🏿‍♂️", + "🤵‍♀️", + "🤵‍♂️", + "🤵🏻‍♀️", + "🤵🏻‍♂️", + "🤵🏼‍♀️", + "🤵🏼‍♂️", + "🤵🏽‍♀️", + "🤵🏽‍♂️", + "🤵🏾‍♀️", + "🤵🏾‍♂️", + "🤵🏿‍♀️", + "🤵🏿‍♂️", + "🤷‍♀️", + "🤷‍♂️", + "🤷🏻‍♀️", + "🤷🏻‍♂️", + "🤷🏼‍♀️", + "🤷🏼‍♂️", + "🤷🏽‍♀️", + "🤷🏽‍♂️", + "🤷🏾‍♀️", + "🤷🏾‍♂️", + "🤷🏿‍♀️", + "🤷🏿‍♂️", + "🤸‍♀️", + "🤸‍♂️", + "🤸🏻‍♀️", + "🤸🏻‍♂️", + "🤸🏼‍♀️", + "🤸🏼‍♂️", + "🤸🏽‍♀️", + "🤸🏽‍♂️", + "🤸🏾‍♀️", + "🤸🏾‍♂️", + "🤸🏿‍♀️", + "🤸🏿‍♂️", + "🤹‍♀️", + "🤹‍♂️", + "🤹🏻‍♀️", + "🤹🏻‍♂️", + "🤹🏼‍♀️", + "🤹🏼‍♂️", + "🤹🏽‍♀️", + "🤹🏽‍♂️", + "🤹🏾‍♀️", + "🤹🏾‍♂️", + "🤹🏿‍♀️", + "🤹🏿‍♂️", + "🤼‍♀️", + "🤼‍♂️", + "🤽‍♀️", + "🤽‍♂️", + "🤽🏻‍♀️", + "🤽🏻‍♂️", + "🤽🏼‍♀️", + "🤽🏼‍♂️", + "🤽🏽‍♀️", + "🤽🏽‍♂️", + "🤽🏾‍♀️", + "🤽🏾‍♂️", + "🤽🏿‍♀️", + "🤽🏿‍♂️", + "🤾‍♀️", + "🤾‍♂️", + "🤾🏻‍♀️", + "🤾🏻‍♂️", + "🤾🏼‍♀️", + "🤾🏼‍♂️", + "🤾🏽‍♀️", + "🤾🏽‍♂️", + "🤾🏾‍♀️", + "🤾🏾‍♂️", + "🤾🏿‍♀️", + "🤾🏿‍♂️", + "🦸‍♀️", + "🦸‍♂️", + "🦸🏻‍♀️", + "🦸🏻‍♂️", + "🦸🏼‍♀️", + "🦸🏼‍♂️", + "🦸🏽‍♀️", + "🦸🏽‍♂️", + "🦸🏾‍♀️", + "🦸🏾‍♂️", + "🦸🏿‍♀️", + "🦸🏿‍♂️", + "🦹‍♀️", + "🦹‍♂️", + "🦹🏻‍♀️", + "🦹🏻‍♂️", + "🦹🏼‍♀️", + "🦹🏼‍♂️", + "🦹🏽‍♀️", + "🦹🏽‍♂️", + "🦹🏾‍♀️", + "🦹🏾‍♂️", + "🦹🏿‍♀️", + "🦹🏿‍♂️", + "🧍‍♀️", + "🧍‍♂️", + "🧍🏻‍♀️", + "🧍🏻‍♂️", + "🧍🏼‍♀️", + "🧍🏼‍♂️", + "🧍🏽‍♀️", + "🧍🏽‍♂️", + "🧍🏾‍♀️", + "🧍🏾‍♂️", + "🧍🏿‍♀️", + "🧍🏿‍♂️", + "🧎‍♀️", + "🧎‍♂️", + "🧎🏻‍♀️", + "🧎🏻‍♂️", + "🧎🏼‍♀️", + "🧎🏼‍♂️", + "🧎🏽‍♀️", + "🧎🏽‍♂️", + "🧎🏾‍♀️", + "🧎🏾‍♂️", + "🧎🏿‍♀️", + "🧎🏿‍♂️", + "🧏‍♀️", + "🧏‍♂️", + "🧏🏻‍♀️", + "🧏🏻‍♂️", + "🧏🏼‍♀️", + "🧏🏼‍♂️", + "🧏🏽‍♀️", + "🧏🏽‍♂️", + "🧏🏾‍♀️", + "🧏🏾‍♂️", + "🧏🏿‍♀️", + "🧏🏿‍♂️", + "🧖‍♀️", + "🧖‍♂️", + "🧖🏻‍♀️", + "🧖🏻‍♂️", + "🧖🏼‍♀️", + "🧖🏼‍♂️", + "🧖🏽‍♀️", + "🧖🏽‍♂️", + "🧖🏾‍♀️", + "🧖🏾‍♂️", + "🧖🏿‍♀️", + "🧖🏿‍♂️", + "🧗‍♀️", + "🧗‍♂️", + "🧗🏻‍♀️", + "🧗🏻‍♂️", + "🧗🏼‍♀️", + "🧗🏼‍♂️", + "🧗🏽‍♀️", + "🧗🏽‍♂️", + "🧗🏾‍♀️", + "🧗🏾‍♂️", + "🧗🏿‍♀️", + "🧗🏿‍♂️", + "🧘‍♀️", + "🧘‍♂️", + "🧘🏻‍♀️", + "🧘🏻‍♂️", + "🧘🏼‍♀️", + "🧘🏼‍♂️", + "🧘🏽‍♀️", + "🧘🏽‍♂️", + "🧘🏾‍♀️", + "🧘🏾‍♂️", + "🧘🏿‍♀️", + "🧘🏿‍♂️", + "🧙‍♀️", + "🧙‍♂️", + "🧙🏻‍♀️", + "🧙🏻‍♂️", + "🧙🏼‍♀️", + "🧙🏼‍♂️", + "🧙🏽‍♀️", + "🧙🏽‍♂️", + "🧙🏾‍♀️", + "🧙🏾‍♂️", + "🧙🏿‍♀️", + "🧙🏿‍♂️", + "🧚‍♀️", + "🧚‍♂️", + "🧚🏻‍♀️", + "🧚🏻‍♂️", + "🧚🏼‍♀️", + "🧚🏼‍♂️", + "🧚🏽‍♀️", + "🧚🏽‍♂️", + "🧚🏾‍♀️", + "🧚🏾‍♂️", + "🧚🏿‍♀️", + "🧚🏿‍♂️", + "🧛‍♀️", + "🧛‍♂️", + "🧛🏻‍♀️", + "🧛🏻‍♂️", + "🧛🏼‍♀️", + "🧛🏼‍♂️", + "🧛🏽‍♀️", + "🧛🏽‍♂️", + "🧛🏾‍♀️", + "🧛🏾‍♂️", + "🧛🏿‍♀️", + "🧛🏿‍♂️", + "🧜‍♀️", + "🧜‍♂️", + "🧜🏻‍♀️", + "🧜🏻‍♂️", + "🧜🏼‍♀️", + "🧜🏼‍♂️", + "🧜🏽‍♀️", + "🧜🏽‍♂️", + "🧜🏾‍♀️", + "🧜🏾‍♂️", + "🧜🏿‍♀️", + "🧜🏿‍♂️", + "🧝‍♀️", + "🧝‍♂️", + "🧝🏻‍♀️", + "🧝🏻‍♂️", + "🧝🏼‍♀️", + "🧝🏼‍♂️", + "🧝🏽‍♀️", + "🧝🏽‍♂️", + "🧝🏾‍♀️", + "🧝🏾‍♂️", + "🧝🏿‍♀️", + "🧝🏿‍♂️", + "🧞‍♀️", + "🧞‍♂️", + "🧟‍♀️", + "🧟‍♂️", + "👨‍🦰", + "👨‍🦱", + "👨‍🦲", + "👨‍🦳", + "👨🏻‍🦰", + "👨🏻‍🦱", + "👨🏻‍🦲", + "👨🏻‍🦳", + "👨🏼‍🦰", + "👨🏼‍🦱", + "👨🏼‍🦲", + "👨🏼‍🦳", + "👨🏽‍🦰", + "👨🏽‍🦱", + "👨🏽‍🦲", + "👨🏽‍🦳", + "👨🏾‍🦰", + "👨🏾‍🦱", + "👨🏾‍🦲", + "👨🏾‍🦳", + "👨🏿‍🦰", + "👨🏿‍🦱", + "👨🏿‍🦲", + "👨🏿‍🦳", + "👩‍🦰", + "👩‍🦱", + "👩‍🦲", + "👩‍🦳", + "👩🏻‍🦰", + "👩🏻‍🦱", + "👩🏻‍🦲", + "👩🏻‍🦳", + "👩🏼‍🦰", + "👩🏼‍🦱", + "👩🏼‍🦲", + "👩🏼‍🦳", + "👩🏽‍🦰", + "👩🏽‍🦱", + "👩🏽‍🦲", + "👩🏽‍🦳", + "👩🏾‍🦰", + "👩🏾‍🦱", + "👩🏾‍🦲", + "👩🏾‍🦳", + "👩🏿‍🦰", + "👩🏿‍🦱", + "👩🏿‍🦲", + "👩🏿‍🦳", + "🧑‍🦰", + "🧑‍🦱", + "🧑‍🦲", + "🧑‍🦳", + "🧑🏻‍🦰", + "🧑🏻‍🦱", + "🧑🏻‍🦲", + "🧑🏻‍🦳", + "🧑🏼‍🦰", + "🧑🏼‍🦱", + "🧑🏼‍🦲", + "🧑🏼‍🦳", + "🧑🏽‍🦰", + "🧑🏽‍🦱", + "🧑🏽‍🦲", + "🧑🏽‍🦳", + "🧑🏾‍🦰", + "🧑🏾‍🦱", + "🧑🏾‍🦲", + "🧑🏾‍🦳", + "🧑🏿‍🦰", + "🧑🏿‍🦱", + "🧑🏿‍🦲", + "🧑🏿‍🦳", + "🏳️‍⚧️", + "🏳️‍🌈", + "🏴‍☠️", + "🐈‍⬛", + "🐕‍🦺", + "🐻‍❄️", + "👁️‍🗨️", + "🧑‍🎄" + ] + } + ], + "zwj-sequence-components": [ + { + "title": "Zero Width Joiner", + "characters": [ + "‍" + ] + }, + { + "title": "Skin Modifiers", + "characters": [ + "🏻", + "🏼", + "🏽", + "🏾", + "🏿" + ] + }, + { + "title": "Hair Components", + "characters": [ + "🦰", + "🦱", + "🦳", + "🦲" + ] + }, + { + "title": "Variation Selectors", + "characters": [ + "︎", + "️" + ] + } + ], + "keycaps": [ + { + "title": "Composed KayCaps", + "characters": [ + "0️⃣", + "1️⃣", + "2️⃣", + "3️⃣", + "4️⃣", + "5️⃣", + "6️⃣", + "7️⃣", + "8️⃣", + "9️⃣", + "#️⃣", + "*️⃣" + ] + }, + { + "title": "Components", + "characters": [ + "⃣", + "︎", + "️", + "#", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9" + ] + }, + { + "title": "Related Single Code Point Character", + "characters": [ + "🔟", + "🔢" + ] + } + ], + "flags": [ + { + "title": "Regional Indicator", + "characters": [ + "🇦", + "🇧", + "🇨", + "🇩", + "🇪", + "🇫", + "🇬", + "🇭", + "🇮", + "🇯", + "🇰", + "🇱", + "🇲", + "🇳", + "🇴", + "🇵", + "🇶", + "🇷", + "🇸", + "🇹", + "🇺", + "🇻", + "🇼", + "🇽", + "🇾", + "🇿" + ] + }, + { + "title": "Flags", + "characters": [ + "🇦🇨", + "🇦🇩", + "🇦🇪", + "🇦🇫", + "🇦🇬", + "🇦🇮", + "🇦🇱", + "🇦🇲", + "🇦🇴", + "🇦🇶", + "🇦🇷", + "🇦🇸", + "🇦🇹", + "🇦🇺", + "🇦🇼", + "🇦🇽", + "🇦🇿", + "🇧🇦", + "🇧🇧", + "🇧🇩", + "🇧🇪", + "🇧🇫", + "🇧🇬", + "🇧🇭", + "🇧🇮", + "🇧🇯", + "🇧🇱", + "🇧🇲", + "🇧🇳", + "🇧🇴", + "🇧🇶", + "🇧🇷", + "🇧🇸", + "🇧🇹", + "🇧🇻", + "🇧🇼", + "🇧🇾", + "🇧🇿", + "🇨🇦", + "🇨🇨", + "🇨🇩", + "🇨🇫", + "🇨🇬", + "🇨🇭", + "🇨🇮", + "🇨🇰", + "🇨🇱", + "🇨🇲", + "🇨🇳", + "🇨🇴", + "🇨🇵", + "🇨🇷", + "🇨🇺", + "🇨🇻", + "🇨🇼", + "🇨🇽", + "🇨🇾", + "🇨🇿", + "🇩🇪", + "🇩🇬", + "🇩🇯", + "🇩🇰", + "🇩🇲", + "🇩🇴", + "🇩🇿", + "🇪🇦", + "🇪🇨", + "🇪🇪", + "🇪🇬", + "🇪🇭", + "🇪🇷", + "🇪🇸", + "🇪🇹", + "🇪🇺", + "🇫🇮", + "🇫🇯", + "🇫🇰", + "🇫🇲", + "🇫🇴", + "🇫🇷", + "🇬🇦", + "🇬🇧", + "🇬🇩", + "🇬🇪", + "🇬🇫", + "🇬🇬", + "🇬🇭", + "🇬🇮", + "🇬🇱", + "🇬🇲", + "🇬🇳", + "🇬🇵", + "🇬🇶", + "🇬🇷", + "🇬🇸", + "🇬🇹", + "🇬🇺", + "🇬🇼", + "🇬🇾", + "🇭🇰", + "🇭🇲", + "🇭🇳", + "🇭🇷", + "🇭🇹", + "🇭🇺", + "🇮🇨", + "🇮🇩", + "🇮🇪", + "🇮🇱", + "🇮🇲", + "🇮🇳", + "🇮🇴", + "🇮🇶", + "🇮🇷", + "🇮🇸", + "🇮🇹", + "🇯🇪", + "🇯🇲", + "🇯🇴", + "🇯🇵", + "🇰🇪", + "🇰🇬", + "🇰🇭", + "🇰🇮", + "🇰🇲", + "🇰🇳", + "🇰🇵", + "🇰🇷", + "🇰🇼", + "🇰🇾", + "🇰🇿", + "🇱🇦", + "🇱🇧", + "🇱🇨", + "🇱🇮", + "🇱🇰", + "🇱🇷", + "🇱🇸", + "🇱🇹", + "🇱🇺", + "🇱🇻", + "🇱🇾", + "🇲🇦", + "🇲🇨", + "🇲🇩", + "🇲🇪", + "🇲🇫", + "🇲🇬", + "🇲🇭", + "🇲🇰", + "🇲🇱", + "🇲🇲", + "🇲🇳", + "🇲🇴", + "🇲🇵", + "🇲🇶", + "🇲🇷", + "🇲🇸", + "🇲🇹", + "🇲🇺", + "🇲🇻", + "🇲🇼", + "🇲🇽", + "🇲🇾", + "🇲🇿", + "🇳🇦", + "🇳🇨", + "🇳🇪", + "🇳🇫", + "🇳🇬", + "🇳🇮", + "🇳🇱", + "🇳🇴", + "🇳🇵", + "🇳🇷", + "🇳🇺", + "🇳🇿", + "🇴🇲", + "🇵🇦", + "🇵🇪", + "🇵🇫", + "🇵🇬", + "🇵🇭", + "🇵🇰", + "🇵🇱", + "🇵🇲", + "🇵🇳", + "🇵🇷", + "🇵🇸", + "🇵🇹", + "🇵🇼", + "🇵🇾", + "🇶🇦", + "🇷🇪", + "🇷🇴", + "🇷🇸", + "🇷🇺", + "🇷🇼", + "🇸🇦", + "🇸🇧", + "🇸🇨", + "🇸🇩", + "🇸🇪", + "🇸🇬", + "🇸🇭", + "🇸🇮", + "🇸🇯", + "🇸🇰", + "🇸🇱", + "🇸🇲", + "🇸🇳", + "🇸🇴", + "🇸🇷", + "🇸🇸", + "🇸🇹", + "🇸🇻", + "🇸🇽", + "🇸🇾", + "🇸🇿", + "🇹🇦", + "🇹🇨", + "🇹🇩", + "🇹🇫", + "🇹🇬", + "🇹🇭", + "🇹🇯", + "🇹🇰", + "🇹🇱", + "🇹🇲", + "🇹🇳", + "🇹🇴", + "🇹🇷", + "🇹🇹", + "🇹🇻", + "🇹🇿", + "🇺🇦", + "🇺🇬", + "🇺🇲", + "🇺🇳", + "🇺🇸", + "🇺🇾", + "🇺🇿", + "🇻🇦", + "🇻🇨", + "🇻🇪", + "🇻🇬", + "🇻🇮", + "🇻🇳", + "🇻🇺", + "🇼🇫", + "🇼🇸", + "🇽🇰", + "🇾🇪", + "🇾🇹", + "🇿🇦", + "🇿🇲", + "🇿🇼" + ] + } + ], + "modifiers": [ + { + "title": "Modifiers", + "characters": [ + "🏻", + "🏼", + "🏽", + "🏾", + "🏿" + ] + }, + { + "title": "Modifier Sequences", + "characters": [ + "☝🏻", + "☝🏼", + "☝🏽", + "☝🏾", + "☝🏿", + "⛹🏻", + "⛹🏼", + "⛹🏽", + "⛹🏾", + "⛹🏿", + "✊🏻", + "✊🏼", + "✊🏽", + "✊🏾", + "✊🏿", + "✋🏻", + "✋🏼", + "✋🏽", + "✋🏾", + "✋🏿", + "✌🏻", + "✌🏼", + "✌🏽", + "✌🏾", + "✌🏿", + "✍🏻", + "✍🏼", + "✍🏽", + "✍🏾", + "✍🏿", + "🎅🏻", + "🎅🏼", + "🎅🏽", + "🎅🏾", + "🎅🏿", + "🏂🏻", + "🏂🏼", + "🏂🏽", + "🏂🏾", + "🏂🏿", + "🏃🏻", + "🏃🏼", + "🏃🏽", + "🏃🏾", + "🏃🏿", + "🏄🏻", + "🏄🏼", + "🏄🏽", + "🏄🏾", + "🏄🏿", + "🏇🏻", + "🏇🏼", + "🏇🏽", + "🏇🏾", + "🏇🏿", + "🏊🏻", + "🏊🏼", + "🏊🏽", + "🏊🏾", + "🏊🏿", + "🏋🏻", + "🏋🏼", + "🏋🏽", + "🏋🏾", + "🏋🏿", + "🏌🏻", + "🏌🏼", + "🏌🏽", + "🏌🏾", + "🏌🏿", + "👂🏻", + "👂🏼", + "👂🏽", + "👂🏾", + "👂🏿", + "👃🏻", + "👃🏼", + "👃🏽", + "👃🏾", + "👃🏿", + "👆🏻", + "👆🏼", + "👆🏽", + "👆🏾", + "👆🏿", + "👇🏻", + "👇🏼", + "👇🏽", + "👇🏾", + "👇🏿", + "👈🏻", + "👈🏼", + "👈🏽", + "👈🏾", + "👈🏿", + "👉🏻", + "👉🏼", + "👉🏽", + "👉🏾", + "👉🏿", + "👊🏻", + "👊🏼", + "👊🏽", + "👊🏾", + "👊🏿", + "👋🏻", + "👋🏼", + "👋🏽", + "👋🏾", + "👋🏿", + "👌🏻", + "👌🏼", + "👌🏽", + "👌🏾", + "👌🏿", + "👍🏻", + "👍🏼", + "👍🏽", + "👍🏾", + "👍🏿", + "👎🏻", + "👎🏼", + "👎🏽", + "👎🏾", + "👎🏿", + "👏🏻", + "👏🏼", + "👏🏽", + "👏🏾", + "👏🏿", + "👐🏻", + "👐🏼", + "👐🏽", + "👐🏾", + "👐🏿", + "👦🏻", + "👦🏼", + "👦🏽", + "👦🏾", + "👦🏿", + "👧🏻", + "👧🏼", + "👧🏽", + "👧🏾", + "👧🏿", + "👨🏻", + "👨🏼", + "👨🏽", + "👨🏾", + "👨🏿", + "👩🏻", + "👩🏼", + "👩🏽", + "👩🏾", + "👩🏿", + "👫🏻", + "👫🏼", + "👫🏽", + "👫🏾", + "👫🏿", + "👬🏻", + "👬🏼", + "👬🏽", + "👬🏾", + "👬🏿", + "👭🏻", + "👭🏼", + "👭🏽", + "👭🏾", + "👭🏿", + "👮🏻", + "👮🏼", + "👮🏽", + "👮🏾", + "👮🏿", + "👰🏻", + "👰🏼", + "👰🏽", + "👰🏾", + "👰🏿", + "👱🏻", + "👱🏼", + "👱🏽", + "👱🏾", + "👱🏿", + "👲🏻", + "👲🏼", + "👲🏽", + "👲🏾", + "👲🏿", + "👳🏻", + "👳🏼", + "👳🏽", + "👳🏾", + "👳🏿", + "👴🏻", + "👴🏼", + "👴🏽", + "👴🏾", + "👴🏿", + "👵🏻", + "👵🏼", + "👵🏽", + "👵🏾", + "👵🏿", + "👶🏻", + "👶🏼", + "👶🏽", + "👶🏾", + "👶🏿", + "👷🏻", + "👷🏼", + "👷🏽", + "👷🏾", + "👷🏿", + "👸🏻", + "👸🏼", + "👸🏽", + "👸🏾", + "👸🏿", + "👼🏻", + "👼🏼", + "👼🏽", + "👼🏾", + "👼🏿", + "💁🏻", + "💁🏼", + "💁🏽", + "💁🏾", + "💁🏿", + "💂🏻", + "💂🏼", + "💂🏽", + "💂🏾", + "💂🏿", + "💃🏻", + "💃🏼", + "💃🏽", + "💃🏾", + "💃🏿", + "💅🏻", + "💅🏼", + "💅🏽", + "💅🏾", + "💅🏿", + "💆🏻", + "💆🏼", + "💆🏽", + "💆🏾", + "💆🏿", + "💇🏻", + "💇🏼", + "💇🏽", + "💇🏾", + "💇🏿", + "💏🏻", + "💏🏼", + "💏🏽", + "💏🏾", + "💏🏿", + "💑🏻", + "💑🏼", + "💑🏽", + "💑🏾", + "💑🏿", + "💪🏻", + "💪🏼", + "💪🏽", + "💪🏾", + "💪🏿", + "🕴🏻", + "🕴🏼", + "🕴🏽", + "🕴🏾", + "🕴🏿", + "🕵🏻", + "🕵🏼", + "🕵🏽", + "🕵🏾", + "🕵🏿", + "🕺🏻", + "🕺🏼", + "🕺🏽", + "🕺🏾", + "🕺🏿", + "🖐🏻", + "🖐🏼", + "🖐🏽", + "🖐🏾", + "🖐🏿", + "🖕🏻", + "🖕🏼", + "🖕🏽", + "🖕🏾", + "🖕🏿", + "🖖🏻", + "🖖🏼", + "🖖🏽", + "🖖🏾", + "🖖🏿", + "🙅🏻", + "🙅🏼", + "🙅🏽", + "🙅🏾", + "🙅🏿", + "🙆🏻", + "🙆🏼", + "🙆🏽", + "🙆🏾", + "🙆🏿", + "🙇🏻", + "🙇🏼", + "🙇🏽", + "🙇🏾", + "🙇🏿", + "🙋🏻", + "🙋🏼", + "🙋🏽", + "🙋🏾", + "🙋🏿", + "🙌🏻", + "🙌🏼", + "🙌🏽", + "🙌🏾", + "🙌🏿", + "🙍🏻", + "🙍🏼", + "🙍🏽", + "🙍🏾", + "🙍🏿", + "🙎🏻", + "🙎🏼", + "🙎🏽", + "🙎🏾", + "🙎🏿", + "🙏🏻", + "🙏🏼", + "🙏🏽", + "🙏🏾", + "🙏🏿", + "🚣🏻", + "🚣🏼", + "🚣🏽", + "🚣🏾", + "🚣🏿", + "🚴🏻", + "🚴🏼", + "🚴🏽", + "🚴🏾", + "🚴🏿", + "🚵🏻", + "🚵🏼", + "🚵🏽", + "🚵🏾", + "🚵🏿", + "🚶🏻", + "🚶🏼", + "🚶🏽", + "🚶🏾", + "🚶🏿", + "🛀🏻", + "🛀🏼", + "🛀🏽", + "🛀🏾", + "🛀🏿", + "🛌🏻", + "🛌🏼", + "🛌🏽", + "🛌🏾", + "🛌🏿", + "🤌🏻", + "🤌🏼", + "🤌🏽", + "🤌🏾", + "🤌🏿", + "🤏🏻", + "🤏🏼", + "🤏🏽", + "🤏🏾", + "🤏🏿", + "🤘🏻", + "🤘🏼", + "🤘🏽", + "🤘🏾", + "🤘🏿", + "🤙🏻", + "🤙🏼", + "🤙🏽", + "🤙🏾", + "🤙🏿", + "🤚🏻", + "🤚🏼", + "🤚🏽", + "🤚🏾", + "🤚🏿", + "🤛🏻", + "🤛🏼", + "🤛🏽", + "🤛🏾", + "🤛🏿", + "🤜🏻", + "🤜🏼", + "🤜🏽", + "🤜🏾", + "🤜🏿", + "🤞🏻", + "🤞🏼", + "🤞🏽", + "🤞🏾", + "🤞🏿", + "🤟🏻", + "🤟🏼", + "🤟🏽", + "🤟🏾", + "🤟🏿", + "🤦🏻", + "🤦🏼", + "🤦🏽", + "🤦🏾", + "🤦🏿", + "🤰🏻", + "🤰🏼", + "🤰🏽", + "🤰🏾", + "🤰🏿", + "🤱🏻", + "🤱🏼", + "🤱🏽", + "🤱🏾", + "🤱🏿", + "🤲🏻", + "🤲🏼", + "🤲🏽", + "🤲🏾", + "🤲🏿", + "🤳🏻", + "🤳🏼", + "🤳🏽", + "🤳🏾", + "🤳🏿", + "🤴🏻", + "🤴🏼", + "🤴🏽", + "🤴🏾", + "🤴🏿", + "🤵🏻", + "🤵🏼", + "🤵🏽", + "🤵🏾", + "🤵🏿", + "🤶🏻", + "🤶🏼", + "🤶🏽", + "🤶🏾", + "🤶🏿", + "🤷🏻", + "🤷🏼", + "🤷🏽", + "🤷🏾", + "🤷🏿", + "🤸🏻", + "🤸🏼", + "🤸🏽", + "🤸🏾", + "🤸🏿", + "🤹🏻", + "🤹🏼", + "🤹🏽", + "🤹🏾", + "🤹🏿", + "🤽🏻", + "🤽🏼", + "🤽🏽", + "🤽🏾", + "🤽🏿", + "🤾🏻", + "🤾🏼", + "🤾🏽", + "🤾🏾", + "🤾🏿", + "🥷🏻", + "🥷🏼", + "🥷🏽", + "🥷🏾", + "🥷🏿", + "🦵🏻", + "🦵🏼", + "🦵🏽", + "🦵🏾", + "🦵🏿", + "🦶🏻", + "🦶🏼", + "🦶🏽", + "🦶🏾", + "🦶🏿", + "🦸🏻", + "🦸🏼", + "🦸🏽", + "🦸🏾", + "🦸🏿", + "🦹🏻", + "🦹🏼", + "🦹🏽", + "🦹🏾", + "🦹🏿", + "🦻🏻", + "🦻🏼", + "🦻🏽", + "🦻🏾", + "🦻🏿", + "🧍🏻", + "🧍🏼", + "🧍🏽", + "🧍🏾", + "🧍🏿", + "🧎🏻", + "🧎🏼", + "🧎🏽", + "🧎🏾", + "🧎🏿", + "🧏🏻", + "🧏🏼", + "🧏🏽", + "🧏🏾", + "🧏🏿", + "🧑🏻", + "🧑🏼", + "🧑🏽", + "🧑🏾", + "🧑🏿", + "🧒🏻", + "🧒🏼", + "🧒🏽", + "🧒🏾", + "🧒🏿", + "🧓🏻", + "🧓🏼", + "🧓🏽", + "🧓🏾", + "🧓🏿", + "🧔🏻", + "🧔🏼", + "🧔🏽", + "🧔🏾", + "🧔🏿", + "🧕🏻", + "🧕🏼", + "🧕🏽", + "🧕🏾", + "🧕🏿", + "🧖🏻", + "🧖🏼", + "🧖🏽", + "🧖🏾", + "🧖🏿", + "🧗🏻", + "🧗🏼", + "🧗🏽", + "🧗🏾", + "🧗🏿", + "🧘🏻", + "🧘🏼", + "🧘🏽", + "🧘🏾", + "🧘🏿", + "🧙🏻", + "🧙🏼", + "🧙🏽", + "🧙🏾", + "🧙🏿", + "🧚🏻", + "🧚🏼", + "🧚🏽", + "🧚🏾", + "🧚🏿", + "🧛🏻", + "🧛🏼", + "🧛🏽", + "🧛🏾", + "🧛🏿", + "🧜🏻", + "🧜🏼", + "🧜🏽", + "🧜🏾", + "🧜🏿", + "🧝🏻", + "🧝🏼", + "🧝🏽", + "🧝🏾", + "🧝🏿" + ] + } + ], + "variations": [ + { + "title": "Variation Selectors", + "characters": [ + "︎", + "️" + ] + }, + { + "title": "Variation Sequences", + "characters": [ + "©︎", + "©️", + "®︎", + "®️", + "‼︎", + "‼️", + "⁉︎", + "⁉️", + "™︎", + "™️", + "ℹ︎", + "ℹ️", + "↔︎", + "↔️", + "↕︎", + "↕️", + "↖︎", + "↖️", + "↗︎", + "↗️", + "↘︎", + "↘️", + "↙︎", + "↙️", + "↩︎", + "↩️", + "↪︎", + "↪️", + "⌚︎", + "⌚️", + "⌛︎", + "⌛️", + "⌨︎", + "⌨️", + "⏏︎", + "⏏️", + "⏩︎", + "⏩️", + "⏪︎", + "⏪️", + "⏭︎", + "⏭️", + "⏮︎", + "⏮️", + "⏯︎", + "⏯️", + "⏱︎", + "⏱️", + "⏲︎", + "⏲️", + "⏳︎", + "⏳️", + "⏸︎", + "⏸️", + "⏹︎", + "⏹️", + "⏺︎", + "⏺️", + "Ⓜ︎", + "Ⓜ️", + "▪︎", + "▪️", + "▫︎", + "▫️", + "▶︎", + "▶️", + "◀︎", + "◀️", + "◻︎", + "◻️", + "◼︎", + "◼️", + "◽︎", + "◽️", + "◾︎", + "◾️", + "☀︎", + "☀️", + "☁︎", + "☁️", + "☂︎", + "☂️", + "☃︎", + "☃️", + "☄︎", + "☄️", + "☎︎", + "☎️", + "☑︎", + "☑️", + "☔︎", + "☔️", + "☕︎", + "☕️", + "☘︎", + "☘️", + "☝︎", + "☝️", + "☠︎", + "☠️", + "☢︎", + "☢️", + "☣︎", + "☣️", + "☦︎", + "☦️", + "☪︎", + "☪️", + "☮︎", + "☮️", + "☯︎", + "☯️", + "☸︎", + "☸️", + "☹︎", + "☹️", + "☺︎", + "☺️", + "♀︎", + "♀️", + "♂︎", + "♂️", + "♈︎", + "♈️", + "♉︎", + "♉️", + "♊︎", + "♊️", + "♋︎", + "♋️", + "♌︎", + "♌️", + "♍︎", + "♍️", + "♎︎", + "♎️", + "♏︎", + "♏️", + "♐︎", + "♐️", + "♑︎", + "♑️", + "♒︎", + "♒️", + "♓︎", + "♓️", + "♟︎", + "♟️", + "♠︎", + "♠️", + "♣︎", + "♣️", + "♥︎", + "♥️", + "♦︎", + "♦️", + "♨︎", + "♨️", + "♻︎", + "♻️", + "♾︎", + "♾️", + "♿︎", + "♿️", + "⚒︎", + "⚒️", + "⚓︎", + "⚓️", + "⚔︎", + "⚔️", + "⚕︎", + "⚕️", + "⚖︎", + "⚖️", + "⚗︎", + "⚗️", + "⚙︎", + "⚙️", + "⚛︎", + "⚛️", + "⚜︎", + "⚜️", + "⚠︎", + "⚠️", + "⚡︎", + "⚡️", + "⚧︎", + "⚧️", + "⚪︎", + "⚪️", + "⚫︎", + "⚫️", + "⚰︎", + "⚰️", + "⚱︎", + "⚱️", + "⚽︎", + "⚽️", + "⚾︎", + "⚾️", + "⛄︎", + "⛄️", + "⛅︎", + "⛅️", + "⛈︎", + "⛈️", + "⛏︎", + "⛏️", + "⛑︎", + "⛑️", + "⛓︎", + "⛓️", + "⛔︎", + "⛔️", + "⛩︎", + "⛩️", + "⛪︎", + "⛪️", + "⛰︎", + "⛰️", + "⛱︎", + "⛱️", + "⛲︎", + "⛲️", + "⛳︎", + "⛳️", + "⛴︎", + "⛴️", + "⛵︎", + "⛵️", + "⛷︎", + "⛷️", + "⛸︎", + "⛸️", + "⛹︎", + "⛹️", + "⛺︎", + "⛺️", + "⛽︎", + "⛽️", + "✂︎", + "✂️", + "✈︎", + "✈️", + "✉︎", + "✉️", + "✌︎", + "✌️", + "✍︎", + "✍️", + "✏︎", + "✏️", + "✒︎", + "✒️", + "✔︎", + "✔️", + "✖︎", + "✖️", + "✝︎", + "✝️", + "✡︎", + "✡️", + "✳︎", + "✳️", + "✴︎", + "✴️", + "❄︎", + "❄️", + "❇︎", + "❇️", + "❓︎", + "❓️", + "❗︎", + "❗️", + "❣︎", + "❣️", + "❤︎", + "❤️", + "➡︎", + "➡️", + "⤴︎", + "⤴️", + "⤵︎", + "⤵️", + "⬅︎", + "⬅️", + "⬆︎", + "⬆️", + "⬇︎", + "⬇️", + "⬛︎", + "⬛️", + "⬜︎", + "⬜️", + "⭐︎", + "⭐️", + "⭕︎", + "⭕️", + "〰︎", + "〰️", + "〽︎", + "〽️", + "㊗︎", + "㊗️", + "㊙︎", + "㊙️", + "🀄︎", + "🀄️", + "🅰︎", + "🅰️", + "🅱︎", + "🅱️", + "🅾︎", + "🅾️", + "🅿︎", + "🅿️", + "🈂︎", + "🈂️", + "🈚︎", + "🈚️", + "🈯︎", + "🈯️", + "🈷︎", + "🈷️", + "🌍︎", + "🌍️", + "🌎︎", + "🌎️", + "🌏︎", + "🌏️", + "🌕︎", + "🌕️", + "🌜︎", + "🌜️", + "🌡︎", + "🌡️", + "🌤︎", + "🌤️", + "🌥︎", + "🌥️", + "🌦︎", + "🌦️", + "🌧︎", + "🌧️", + "🌨︎", + "🌨️", + "🌩︎", + "🌩️", + "🌪︎", + "🌪️", + "🌫︎", + "🌫️", + "🌬︎", + "🌬️", + "🌶︎", + "🌶️", + "🍸︎", + "🍸️", + "🍽︎", + "🍽️", + "🎓︎", + "🎓️", + "🎖︎", + "🎖️", + "🎗︎", + "🎗️", + "🎙︎", + "🎙️", + "🎚︎", + "🎚️", + "🎛︎", + "🎛️", + "🎞︎", + "🎞️", + "🎟︎", + "🎟️", + "🎧︎", + "🎧️", + "🎬︎", + "🎬️", + "🎭︎", + "🎭️", + "🎮︎", + "🎮️", + "🏂︎", + "🏂️", + "🏄︎", + "🏄️", + "🏆︎", + "🏆️", + "🏊︎", + "🏊️", + "🏋︎", + "🏋️", + "🏌︎", + "🏌️", + "🏍︎", + "🏍️", + "🏎︎", + "🏎️", + "🏔︎", + "🏔️", + "🏕︎", + "🏕️", + "🏖︎", + "🏖️", + "🏗︎", + "🏗️", + "🏘︎", + "🏘️", + "🏙︎", + "🏙️", + "🏚︎", + "🏚️", + "🏛︎", + "🏛️", + "🏜︎", + "🏜️", + "🏝︎", + "🏝️", + "🏞︎", + "🏞️", + "🏟︎", + "🏟️", + "🏠︎", + "🏠️", + "🏭︎", + "🏭️", + "🏳︎", + "🏳️", + "🏵︎", + "🏵️", + "🏷︎", + "🏷️", + "🐈︎", + "🐈️", + "🐕︎", + "🐕️", + "🐟︎", + "🐟️", + "🐦︎", + "🐦️", + "🐿︎", + "🐿️", + "👁︎", + "👁️", + "👂︎", + "👂️", + "👆︎", + "👆️", + "👇︎", + "👇️", + "👈︎", + "👈️", + "👉︎", + "👉️", + "👍︎", + "👍️", + "👎︎", + "👎️", + "👓︎", + "👓️", + "👪︎", + "👪️", + "👽︎", + "👽️", + "💣︎", + "💣️", + "💰︎", + "💰️", + "💳︎", + "💳️", + "💻︎", + "💻️", + "💿︎", + "💿️", + "📋︎", + "📋️", + "📚︎", + "📚️", + "📟︎", + "📟️", + "📤︎", + "📤️", + "📥︎", + "📥️", + "📦︎", + "📦️", + "📪︎", + "📪️", + "📫︎", + "📫️", + "📬︎", + "📬️", + "📭︎", + "📭️", + "📷︎", + "📷️", + "📹︎", + "📹️", + "📺︎", + "📺️", + "📻︎", + "📻️", + "📽︎", + "📽️", + "🔈︎", + "🔈️", + "🔍︎", + "🔍️", + "🔒︎", + "🔒️", + "🔓︎", + "🔓️", + "🕉︎", + "🕉️", + "🕊︎", + "🕊️", + "🕐︎", + "🕐️", + "🕑︎", + "🕑️", + "🕒︎", + "🕒️", + "🕓︎", + "🕓️", + "🕔︎", + "🕔️", + "🕕︎", + "🕕️", + "🕖︎", + "🕖️", + "🕗︎", + "🕗️", + "🕘︎", + "🕘️", + "🕙︎", + "🕙️", + "🕚︎", + "🕚️", + "🕛︎", + "🕛️", + "🕜︎", + "🕜️", + "🕝︎", + "🕝️", + "🕞︎", + "🕞️", + "🕟︎", + "🕟️", + "🕠︎", + "🕠️", + "🕡︎", + "🕡️", + "🕢︎", + "🕢️", + "🕣︎", + "🕣️", + "🕤︎", + "🕤️", + "🕥︎", + "🕥️", + "🕦︎", + "🕦️", + "🕧︎", + "🕧️", + "🕯︎", + "🕯️", + "🕰︎", + "🕰️", + "🕳︎", + "🕳️", + "🕴︎", + "🕴️", + "🕵︎", + "🕵️", + "🕶︎", + "🕶️", + "🕷︎", + "🕷️", + "🕸︎", + "🕸️", + "🕹︎", + "🕹️", + "🖇︎", + "🖇️", + "🖊︎", + "🖊️", + "🖋︎", + "🖋️", + "🖌︎", + "🖌️", + "🖍︎", + "🖍️", + "🖐︎", + "🖐️", + "🖥︎", + "🖥️", + "🖨︎", + "🖨️", + "🖱︎", + "🖱️", + "🖲︎", + "🖲️", + "🖼︎", + "🖼️", + "🗂︎", + "🗂️", + "🗃︎", + "🗃️", + "🗄︎", + "🗄️", + "🗑︎", + "🗑️", + "🗒︎", + "🗒️", + "🗓︎", + "🗓️", + "🗜︎", + "🗜️", + "🗝︎", + "🗝️", + "🗞︎", + "🗞️", + "🗡︎", + "🗡️", + "🗣︎", + "🗣️", + "🗨︎", + "🗨️", + "🗯︎", + "🗯️", + "🗳︎", + "🗳️", + "🗺︎", + "🗺️", + "😐︎", + "😐️", + "🚇︎", + "🚇️", + "🚍︎", + "🚍️", + "🚑︎", + "🚑️", + "🚔︎", + "🚔️", + "🚘︎", + "🚘️", + "🚭︎", + "🚭️", + "🚲︎", + "🚲️", + "🚹︎", + "🚹️", + "🚺︎", + "🚺️", + "🚼︎", + "🚼️", + "🛋︎", + "🛋️", + "🛍︎", + "🛍️", + "🛎︎", + "🛎️", + "🛏︎", + "🛏️", + "🛠︎", + "🛠️", + "🛡︎", + "🛡️", + "🛢︎", + "🛢️", + "🛣︎", + "🛣️", + "🛤︎", + "🛤️", + "🛥︎", + "🛥️", + "🛩︎", + "🛩️", + "🛰︎", + "🛰️", + "🛳︎", + "🛳️" + ] + } + ] +} diff --git a/PlaygroundBook/PrivateResources/JSON/planes.json b/PlaygroundBook/PrivateResources/JSON/planes.json new file mode 100644 index 0000000..808716a --- /dev/null +++ b/PlaygroundBook/PrivateResources/JSON/planes.json @@ -0,0 +1,470 @@ +[ + { + "number": 0, + "name": "Basic Multilingual Plane", + "shortName": "BMP", + "introduction": "The first plane, plane 0, the Basic Multilingual Plane (BMP) contains characters for almost all modern languages, and a large number of symbols. A primary objective for the BMP is to support the unification of prior character sets as well as characters for writing. Most of the assigned code points in the BMP are used to encode Chinese, Japanese, and Korean (CJK) characters.", + "subIntro": "99.9% allocated with 65,472 characters in 163 blocks", + "codePointStart": 0, + "blocks": [ + "basic-latin", + "latin-1-supplement", + "latin-extended-a", + "latin-extended-b", + "ipa-extensions", + "spacing-modifier-letters", + "combining-diacritical-marks", + "greek-coptic", + "cyrillic", + "cyrillic-supplement", + "armenian", + "hebrew", + "arabic", + "syrian", + "arabic-supplement", + "thaana", + "nko", + "samaritan", + "mandaic", + "syriac-supplement", + "arabic-extended-a", + "devanagari", + "bengali", + "gurmukhi", + "gujarati", + "oriya", + "tamil", + "telugu", + "kannada", + "malayalam", + "sinhala", + "thai", + "lao", + "tibetan", + "myanmar", + "georgian", + "hangul-jamo", + "ethiopic", + "ethiopic-supplement", + "cherokee", + "unified-canadian-aboriginal-syllabics", + "ogham", + "runic", + "tagalog", + "hanunoo", + "buhid", + "tagbanwa", + "khmer", + "mongolian", + "unified-canadian-aboriginal-syllabics-extended", + "limbu", + "tai-le", + "new-tai-lue", + "khmer-symbols", + "buginese", + "tai-tham", + "combining-diacritical-marks-extended", + "balinese", + "sundanese", + "batak", + "lepcha", + "ol-chiki", + "cyrillic-extended-c", + "georgian-extended", + "sundanese-supplement", + "vedic-extensions", + "phonetic-extensions", + "phonetic-extensions-supplement", + "combining-diacritical-marks-supplement", + "latin-extended-additional", + "greek-extended", + "general-punctuation", + "superscripts-and-subscripts", + "currency-symbols", + "combining-diacritical-marks-for-symbols", + "letterlike-symbols", + "number-forms", + "arrows", + "mathematical-operators", + "miscellaneous-technical", + "control-pictures", + "optical-character-recognition", + "enclosed-alphanumerics", + "box-drawing", + "block-elements", + "geometric-shapes", + "miscellaneous-symbols", + "dingbats", + "miscellaneous-mathematical-symbols-a", + "supplemental-arrows-a", + "braille-patterns", + "supplemental-arrows-b", + "miscellaneous-mathematical-symbols-b", + "supplemental-mathematical-operators", + "miscellaneous-symbols-and-arrows", + "glagolitic", + "latin-extended-c", + "coptic", + "georgian-supplement", + "tifinagh", + "ethiopic-extended", + "cyrillic-extended-a", + "supplemental-punctuation", + "cjk-radicals-supplement", + "kangxi-radicals", + "ideographic-description-characters", + "cjk-symbols-and-punctuation", + "hiragana", + "katakana", + "bopomofo", + "hangul-compatibility-jamo", + "kanbun", + "bopomofo-extended", + "cjk-strokes", + "katakana-phonetic-extensions", + "enclosed-cjk-letters-and-months", + "cjk-compatibility", + "cjk-unified-ideographs-extension-a", + "yijing-hexagram-symbols", + "cjk-unified-ideographs", + "yi-syllables", + "yi-radicals", + "lisu", + "vai", + "ciryllic-extended-b", + "bamum", + "modifier-tone-letters", + "latin-extended-d", + "syloty-nagri", + "common-indic-number-forms", + "phags-pa", + "saurashtra", + "devanagari-extended-characters", + "kayah-li", + "rejang", + "hangul-jamo-extended-a", + "javanese-alphabet", + "myanmar-extended-b", + "cham-alphabet", + "myanmar-extended-a", + "tai-viet", + "meitei-meyek-extensions", + "ethiopic-extended-a", + "latin-extended-e", + "cherokee-supplement", + "meetei-mayek", + "hangul-syllables", + "hangul-jamo-extended-b", + "high-surrogates", + "high-private-use-surrogates", + "low-surrogates", + "private-use-area", + "cjk-compatibility-ideographs", + "alphabetic-presentation-forms", + "arabic-presentation-forms-a", + "variation-selectors", + "vertical-forms", + "combining-half-marks", + "cjk-compatibility-forms", + "small-form-variants", + "arabic-presentation-forms-b", + "halfwidth-and-fullwidth-forms", + "specials" + ] + }, + { + "number": 1, + "name": "Supplementary Multilingual Plane", + "shortName": "SMP", + "introduction": "Plane 1, the Supplementary Multilingual Plane (SMP), contains historic scripts (except CJK ideographic), and symbols and notation used within certain fields. Scripts include Linear B, Egyptian hieroglyphs, and cuneiform scripts. It also includes English reform orthographies like Shavian and Deseret, and some modern scripts like Osage, Warang Citi, and Adlam. Symbols and notations include historic and modern musical notation; mathematical alphanumerics; shorthands; Emoji and other pictographic sets; and game symbols for playing cards, Mah Jongg, and dominoes.", + "subIntro": "37.7% allocated with 24,704 characters in 134 blocks", + "codePointStart": 65536, + "blocks": [ + "linear-b-syllabary", + "linear-b-ideograms", + "aegean-numbers", + "ancient-greek-numbers", + "ancient-symbols", + "phaistos-disc", + "lycian", + "carian", + "coptic-epact-numbers", + "old-italic", + "gothic", + "old-permic", + "ugaritic", + "old-persian", + "deseret", + "shavian", + "osmanya", + "osage", + "elbasan", + "caucasian-albanian", + "linear-a", + "cypriot-syllabary", + "imperial-aramaic", + "palmyrene", + "nabataean", + "hatran", + "phoenician", + "lydian", + "meroitic-hieroglyphs", + "meroitic-cursive", + "kharoshthi", + "old-south-arabian", + "old-north-arabian", + "manichaean", + "avestan", + "inscriptional-parthian", + "inscriptional-pahlavi", + "psalter-pahlavi", + "old-turkic", + "old-hungarian", + "hanifi-rohingya", + "rumi-numeral-symbols", + "yezidi", + "old-sogdian", + "sogdian", + "chorasmian", + "elymaic", + "brahmi", + "kaithi", + "sora-sompeng", + "chakma", + "mahajani", + "sharada", + "sinhala-archaic-numbers", + "khojki", + "multani", + "khudawadi", + "grantha", + "newa", + "tirhuta", + "siddham", + "modi", + "mongolian-supplement", + "takri", + "ahom", + "dogra", + "warang-citi", + "dives-akuru", + "nandinagari", + "zanabazar-square", + "soyombo", + "pau-cin-hau", + "bhaiksuki", + "marchen", + "masaram-gondi", + "gunjala-gondi", + "makasar", + "lisu-supplement", + "tamil-supplement", + "cuneiform", + "cuneiform-numbers-and-punctuation", + "early-dynastic-cuneiform", + "egyptian-hieroglyphs", + "egyptian-hieroglyph-format-controls", + "anatolian-hieroglyphs", + "bamum-supplement", + "mro", + "bassa-vah", + "pahawh-hmong", + "medefaidrin", + "miao", + "ideographic-symbols-and-punctuation", + "tangut", + "tangut-components", + "khitan-small-script", + "tangut-supplement", + "kana-supplement", + "kana-extended-a", + "small-kana-extension", + "nushu", + "duployan", + "shorthand-format-controls", + "byzantine-musical-symbols", + "musical-symbols", + "ancient-greek-musical-notation", + "mayan-numerals", + "tai-xuan-jing-symbols", + "counting-rod-numerals", + "mathematical-alphanumeric-symbols", + "sutton-sign-writing", + "glagolitic-supplement", + "nyiakeng-puachue-hmong", + "wancho", + "mende-kikakui", + "adlam", + "indic-siyaq-numbers", + "ottoman-siyaq-numbers", + "arabic-mathematical-alphabetic-symbols", + "mahjong-tiles", + "domino-tiles", + "playing-cards", + "enclosed-alphanumeric-supplement", + "enclosed-ideographic-supplement", + "miscellaneous-symbols-and-pictographs", + "emoticons", + "ornamental-dingbats", + "transport-and-map-symbols", + "alchemical-symbols", + "geometric-shapes-extended", + "supplemental-arrows-c", + "supplemental-symbols-and-pictographs", + "chess-symbols", + "symbols-and-pictographs-extended-a", + "symbols-for-legacy-computing" + ] + }, + { + "number": 2, + "name": "Supplementary Ideographic Plane", + "shortName": "SIP", + "introduction": "Plane 2, the Supplementary Ideographic Plane (SIP), is used for CJK Ideographs, that were not included in earlier character encoding standards.", + "subIntro": "92.9% allocated with 60,912 characters in 6 blocks", + "codePointStart": 131072, + "blocks": [ + "cjk-unified-ideographs-extension-b", + "cjk-unified-ideographs-extension-c", + "cjk-unified-ideographs-extension-d", + "cjk-unified-ideographs-extension-e", + "cjk-unified-ideographs-extension-f", + "cjk-compatibility-ideographs-supplement" + ] + }, + { + "number": 3, + "name": "Tertiary Ideographic Plane", + "shortName": "TIP", + "introduction": "Plane 3 is the Tertiary Ideographic Plane (TIP). CJK Unified Ideographs Extension G was added to the TIP in Unicode 13.0, released in March 2020. It also is tentatively allocated for Oracle Bone script and Small Seal Script.", + "subIntro": "7.54% allocated with 4,944 characters in 1 block", + "codePointStart": 196608, + "blocks": [ + "cjk-unified-ideographs-extension-g" + ] + }, + { + "number": 4, + "name": "Plane 4", + "shortName": "Unassigned", + "introduction": "No characters have yet been assigned to Planes 4.", + "subIntro": "No character assigned in this plane", + "codePointStart": 262144, + "blocks": [] + }, + { + "number": 5, + "name": "Plane 5", + "shortName": "Unassigned", + "introduction": "No characters have yet been assigned to Planes 5.", + "subIntro": "No character assigned in this plane", + "codePointStart": 327680, + "blocks": [] + }, + { + "number": 6, + "name": "Plane 6", + "shortName": "Unassigned", + "introduction": "No characters have yet been assigned to Planes 6.", + "subIntro": "No character assigned in this plane", + "codePointStart": 393216, + "blocks": [] + }, + { + "number": 7, + "name": "Plane 7", + "shortName": "Unassigned", + "introduction": "No characters have yet been assigned to Planes 7.", + "subIntro": "No character assigned in this plane", + "codePointStart": 458752, + "blocks": [] + }, + { + "number": 8, + "name": "Plane 8", + "shortName": "Unassigned", + "introduction": "No characters have yet been assigned to Planes 8.", + "subIntro": "No character assigned in this plane", + "codePointStart": 524288, + "blocks": [] + }, + { + "number": 9, + "name": "Plane 9", + "shortName": "Unassigned", + "introduction": "No characters have yet been assigned to Planes 9.", + "subIntro": "No character assigned in this plane", + "codePointStart": 589824, + "blocks": [] + }, + { + "number": 10, + "name": "Plane 10", + "shortName": "Unassigned", + "introduction": "No characters have yet been assigned to Planes 10.", + "subIntro": "No character assigned in this plane", + "codePointStart": 655360, + "blocks": [] + }, + { + "number": 11, + "name": "Plane 11", + "shortName": "Unassigned", + "introduction": "No characters have yet been assigned to Planes 11.", + "subIntro": "No character assigned in this plane", + "codePointStart": 720896, + "blocks": [] + }, + { + "number": 12, + "name": "Plane 12", + "shortName": "Unassigned", + "introduction": "No characters have yet been assigned to Planes 12.", + "subIntro": "No character assigned in this plane", + "codePointStart": 786432, + "blocks": [] + }, + { + "number": 13, + "name": "Plane 13", + "shortName": "Unassigned", + "introduction": "No characters have yet been assigned to Planes 13.", + "subIntro": "No character assigned in this plane", + "codePointStart": 851968, + "blocks": [] + }, + { + "number": 14, + "name": "Supplement­ary Special-purpose Plane", + "shortName": "SSP", + "introduction": "Plane 14, the Supplementary Special-purpose Plane (SSP), comprising the Tags and Variation Selectors Supplement, as of Unicode 13.0.", + "subIntro": "0.56% allocated with 368 characters in 2 blocks", + "codePointStart": 917504, + "blocks": [ + "tags", + "variation-selectors-supplement" + ] + }, + { + "number": 15, + "name": "Supplement­ary Private Use Area plane A", + "shortName": "SPUA-A", + "introduction": "Planes 15 is designated as \"Private Use Area\", also named Supplementary Private Use Area-A (SPUA-A), which is available for use by parties outside the ISO and the Unicode Consortium.", + "subIntro": "All characters are allocated for private use", + "codePointStart": 983040, + "blocks": [ + "supplementary-private-use-area-a" + ] + }, + { + "number": 16, + "name": "Supplement­ary Private Use Area plane B", + "shortName": "SPUA-B", + "introduction": "Planes 16 is designated as \"Private Use Area\", also named Supplementary Private Use Area-B (SPUA-B), which is available for use by parties outside the ISO and the Unicode Consortium.", + "subIntro": "All characters are allocated for private use", + "codePointStart": 1048576, + "blocks": [ + "supplementary-private-use-area-b" + ] + } +] diff --git a/PlaygroundBook/PrivateResources/JSON/sample_text_data.json b/PlaygroundBook/PrivateResources/JSON/sample_text_data.json new file mode 100644 index 0000000..a2ec6e1 --- /dev/null +++ b/PlaygroundBook/PrivateResources/JSON/sample_text_data.json @@ -0,0 +1,319 @@ +[ + "Alphabet", + [ + { + "lang": "Latin", + "content": "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z" + }, + { + "lang": "Latin Extended", + "content": "A B C Č Ć D Đ E F G H I J K L M N O P Q R S Š T U V W X Y Z Ž a b c č ć d đ e f g h i j k l m n o p q r s š t u v w x y z ž" + }, + { + "lang": "Greek", + "content": "Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ σ τ υ φ χ ψ ω" + }, + { + "lang": "Greek Extended", + "content": "Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ σ τ υ φ χ ψ ω ά Ά έ Έ έ Ή ί ϊ ΐ Ί ό Ό ύ ΰ ϋ Ύ Ϋ ὰ ά ὲ έ ὴ ή ὶ ί ὸ ό ὺ ύ ὼ ώ Ώ" + }, + { + "lang": "Chinese (Simplified)", + "content": "一 二 三 四 五 六 七 八 九 十 百 千 万 上 中 下 左 右 大 小 春 夏 秋 冬 东 南 西 北 金 木 水 火 土 天 地 日 月 星 黑 白 红 橙 黄 绿 蓝 靛 紫 食 住 衣 行 育 乐 忠 孝 仁 爱 信 义 和 平 子 曰 父 母 兄 弟 夫 妇 君 臣 马 牛 羊 鸡 犬 豕 喜 怒 哀 惧 恶 目 耳 口 手 足 见 闻 声 贝 车 雨 赤 青 言 语 鱼 鸟 羽 电 不 乃 之 乎 人 以 何 俱 伦 仪 先 光 入 具 初 则 匏 协 去 友 同 名 善 器 严 执 孟 孙 学 宜 容 专 少 山 师 席 常 幼 序 从 性 恩 恭 情 惰 应 成 所 才 扬 择 教 敬 数 文 断 方 于 族 昔 时 智 曾 有 朋 本 杼 某 梨 机 次 欲 此 岁 温 为 燕 玄 玉 琢 畜 当 相 知 石 祖 礼 稷 稻 谷 穷 窦 竹 粱 紊 丝 纲 习 老 者 而 能 自 至 与 苟 菽 处 融 亲 调 识 让 贵 身 近 运 过 道 远 迁 邻 长 非 革 音 顺 饲 养 首 香 高 麦 黍 龄" + }, + { + "lang": "Chinese (Traditional)", + "content": "一 二 三 四 五 六 七 八 九 十 百 千 萬 上 中 下 左 右 大 小 春 夏 秋 冬 東 南 西 北 金 木 水 火 土 天 地 日 月 星 黑 白 紅 橙 黃 綠 藍 靛 紫 食 住 衣 行 育 樂 忠 孝 仁 愛 信 義 和 平 子 曰 父 母 兄 弟 夫 婦 君 臣 馬 牛 羊 雞 犬 豕 喜 怒 哀 懼 惡 目 耳 口 手 足 見 聞 聲 貝 車 雨 赤 青 言 語 魚 鳥 羽 電 不 乃 之 乎 人 以 何 俱 倫 儀 先 光 入 具 初 則 匏 協 去 友 同 名 善 器 嚴 執 孟 孫 學 宜 容 專 少 山 師 席 常 幼 序 從 性 恩 恭 情 惰 應 成 所 才 揚 擇 教 敬 數 文 斷 方 於 族 昔 時 智 曾 有 朋 本 杼 某 梨 機 次 欲 此 歲 溫 為 燕 玄 玉 琢 畜 當 相 知 石 祖 禮 稷 稻 穀 窮 竇 竹 粱 紊 絲 綱 習 老 者 而 能 自 至 與 苟 菽 處 融 親 調 識 讓 貴 身 近 運 過 道 遠 遷 鄰 長 非 革 音 順 飼 養 首 香 高 麥 黍 齡" + }, + { + "lang": "Japanese", + "content": "一 二 三 四 五 六 七 八 九 十 百 千 上 下 左 右 中 大 小 月 日 年 早 木 林 山 川 土 空 田 天 生 花 草 虫 犬 人 名 女 男 子 目 耳 口 手 足 見 音 力 気 円 入 出 立 休 先 夕 本 文 字 学 校 村 町 森 正 水 火 玉 王 石 竹 糸 貝 車 金 雨 赤 青 白 数 多 少 万 半 形 太 細 広 長 点 丸 交 光 角 計 直 線 矢 弱 強 高 同 親 母 父 姉 兄 弟 妹 自 友 体 毛 頭 顔 首 心 時 曜 朝 昼 夜 分 週 春 夏 秋 冬 今 新 古 間 方 北 南 東 西 遠 近 前 後 内 外 場 地 国 園 谷 野 原 里 市 京 風 雪 雲 池 海 岩 星 室 戸 家 寺 通 門 道 話 言 答 声 聞 語 読 書 記 紙 画 絵 図 工 教 晴 思 考 知 才 理 算 作 元 食 肉 馬 牛 魚 鳥 羽 鳴 麦 米 茶 色 黄 黒 来 行 帰 歩 走 止 活 店 買 売 午 汽 弓 回 会 組 船 明 社 切 電 毎 合 当 台 楽 公 引 科 歌 刀 番 用 何" + }, + { + "lang": "Korean", + "content": "가 개 갸 거 게 겨 고 괴 괘 교 구 귀 궤 규 그 긔 기 나 내 냐 너 네 녀 노 뇌 놰 뇨 누 뉘 눼 뉴 느 늬 니 다 대 댜 더 데 뎌 도 되 돼 됴 두 뒤 뒈 류 드 듸 디 라 래 랴 러 레 려 로 뢰 뢔 료 루 뤼 뤠 류 르 릐 리 마 매 먀 머 메 며 모 뫼 뫠 묘 무 뮈 뭬 뮤 므 믜 미 바 배 뱌 버 베 벼 보 뵈 봬 뵤 부 뷔 붸 뷰 브 븨 비 사 새 샤 서 세 셔 소 쇠 쇄 쇼 수 쉬 쉐 슈 브 븨 비 아 애 야 어 에 여 오 외 왜 요 우 위 웨 유 으 의 이 자 재 쟈 저 제 져 조 죄 좨 죠 주 쥐 줴 쥬 즈 즤 지 차 채 챠 처 체 쳐 초 최 쵀 쵸 추 취 췌 츄 츠 츼 치 카 캐 캬 커 케 켜 코 쾨 쾌 쿄 쿠 퀴 퀘 큐 크 킈 키 타 태 탸 터 테 텨 토 퇴 퇘 툐 투 튀 퉤 튜 트 틔 티 파 패 퍄 퍼 페 펴 포 푀 퐤 표 푸 퓌 풰 퓨 프 픠 피 하 해 햐 허 헤 혀 호 회 홰 효 후 휘 훼 휴 흐 희 히" + }, + { + "lang": "Vietnamese", + "content": "A Ă Â B C D Đ E Ê G H I K L M N O Ô Ơ P Q R S T U Ư V X Y a ă â b c d đ e ê g h i k l m n o ô ơ p q r s t u ư v x y" + }, + { + "lang": "Cyrillic", + "content": "А Б В Г Д Е Ё Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я а б в г д е ё ж з и й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я" + }, + { + "lang": "Cyrillic Extended", + "content": "А Б В Г Ґ Д Ђ Е Ё Є Ж З Ѕ И І Ї Й Ј К Л Љ М Н Њ О П Р С Т Ћ У Ў Ф Х Ц Ч Џ Ш Щ Ъ Ы Ь Э Ю Я а б в г ґ д ђ е ё є ж з ѕ и і ї й ј к л љ м н њ о п р с т ћ у ў ф х ц ч џ ш щ ъ ы ь э ю я" + }, + { + "lang": "Arabic", + "content": "أ ب ت ث ج ح خ د ذ ر ز س ش ص ض ط ظ ع غ ف ق ك ل م ن ه و ي ء" + }, + { + "lang": "Bengali", + "content": "অ আ ই ঈ উ ঊ ঋ ঌ এ ঐ ও ঔ ক খ গ ঘ ঙ চ ছ জ ঝ ঞ ট ঠ ড ঢ ণ ত থ দ ধ ন প ফ ব ভ ম য র ল ব শ ষ স হ ড় ঢ় য় ৎ" + }, + { + "lang": "Devanagari", + "content": "आ ई ऊ ऋ ॠ ऌ ॡ ऐ औ ऎ अं अँ क ख ग घ ङ च छ ज झ ञ ट ठ ड ढ ण त थ द ध न प फ ब भ य र व ळ श ष स ह" + }, + { + "lang": "Gujarati", + "content": "અ આ ઇ ઈ ઉ ઊ એ ઐ ઓ ઔ અં અ: ઍ ઑ ત્ર ઋ રૂ ક ખ ગ ઘ ચ છ જ ઝ ટ ઠ ડ ઢ ણ ત થ દ ધ ન પ ફ બ ભ મ ય ર લ વ સ શ ષ હ ળ ક્ષ જ્ઞ" + }, + { + "lang": "Gurmukhi", + "content": "ਆ ਈ ਊ ਏ ਐ ਓ ਔ ਕ ਖ ਗ ਘ ਙ ਚ ਛ ਜ ਝ ਞ ਟ ਠ ਡ ਢ ਣ ਤ ਥ ਦ ਧ ਨ ਪ ਫ ਬ ਭ ਮ ਯ ਰ ਲ ਲ਼ ਵ ਸ਼ ਸ ਹ ੲ ੳ ੴ" + }, + { + "lang": "Hebrew", + "content": "א ב ג ד ה ו ז ח ט י כ ך ל מ ם נ ן ס ע פ ף צ ץ ק ר ש ת" + }, + { + "lang": "Kannada", + "content": "ಅ ಆ ಇ ಈ ಉ ಊ ಋ ೠ ಎ ಏ ಐ ಒ ಓ ಔ ಅಂ ಅಃ ಕ ಖ ಗ ಘ ಙ ಚ ಛ ಜ ಝ ಞ ಟ ಠ ಡ ಢ ಣ ತ ಥ ದ ಧ ನ ಪ ಫ ಬ ಭ ಮ ಯ ರ ಲ ವ ಶ ಷ ಸ ಹ ಳ ಕ್ಷ ಜ್ಞ" + }, + { + "lang": "Khmer", + "content": "ក ខ គ ឃ ង ច ឆ ជ ឈ ញ ដ ឋ ឌ ឍ ណ ត ថ ទ ធ ន ប ផ ព ភ ម យ រ ល វ ស ហ ឡ អ" + }, + { + "lang": "Malayalam", + "content": "അ ആ ഇ ഈ ഉ ഊ ഋ എ ഏ ഐ ഒ ഓ ഔ അം അഃ ക ഖ ഗ ഘ ങ ച ഛ ജ ഝ ഞ ട ഠ ഡ ഢ ണ ത ഥ ദ ധ ന പ ഫ ബ ഭ മ യ ര ല വ ശ ഷ സ ഹ ള ഴ റ" + }, + { + "lang": "Myanmar", + "content": "က ခ ဂ ဃ င စ ဆ ဇ ဈ ဉ ည ဋ ဌ ဍ ဎ ဏ တ ထ ဒ ဓ န ပ ဖ ဗ ဘ မ ယ ရ လ ဝ သ ဟ ဠ အ ဣ ဤ ဥ ဦ ဧ ဩ ဪ ဿ ၌ ၍ ၎ ၏" + }, + { + "lang": "Oriya", + "content": "ଅ ଆ ଇ ଈ ଉ ଊ ଋ ୠ ଌ ୡ ଏ ଐ ଓ ଔ କ ଖ ଗ ଘ ଙ ଚ ଛ ଜ ଝ ଞ ଟ ଠ ଡ ଢ ଣ ତ ଥ ଦ ଧ ନ ପ ଫ ବ ଵ ଭ ମ ଯ ର ଳ ୱ ଶ ଷ ସ ହ ୟ ଲ" + }, + { + "lang": "Sinhala", + "content": "අ ආ ඇ ඈ ඉ ඊ උ ඌ ඍ ඎ එ ඒ ඓ ඔ ඕ ඖ අං අඃ ක ඛ ග ඝ ඞ ඟ ච ඡ ජ ඣ ඥ ඤ ට ඨ ඩ ඪ ණ ඬ ත ථ ද ධ න ඳ ප ඵ බ භ ම ඹ ය ර ල ව ශ ෂ ස හ ළ ෆ" + }, + { + "lang": "Tamil", + "content": "க் ங் ச் ஞ் ட் ண் த் ந் ப் ம் ய் ர் ல் வ் ழ் ள் ற் ன் ஶ் ஜ் ஷ் ஸ் ஹ் க்ஷ் அ ஆ இ ஈ உ ஊ எ ஏ ஐ ஒ ஓ ஔ" + }, + { + "lang": "Telugu", + "content": "అ ఆ ఇ ఈ ఉ ఊ ఋ ఎ ఏ ఐ ఒ ఓ ఔ అం అః క ఖ గ ఘ జ్ఞ చ ఛ జ ఝ ఞ ట ఠ డ ఢ ణ త థ ద ధ న ప ఫ బ భ మ య ర ల వ ళ శ ష స హ ఱ" + }, + { + "lang": "Thai", + "content": "ก ข ค ฆ ง จ ฉ ช ซ ฌ ญ ฎ ฏ ฐ ฑ ฒ ณ ด ต ถ ท ธ น บ ป ผ ฝ พ ฟ ภ ม ย ร ล ว ศ ษ ส ห ฬ อ ฮ ะ า เ ใ ไ โ ฤ ฤๅ" + }, + { + "lang": "Tibetan", + "content": "ཀ ཁ ག ང ཅ ཆ ཇ ཉ ཏ ཐ ད ན པ ཕ བ མ ཙ ཚ ཛ ཝ ཞ ཟ འ ཡ ར ལ ཤ ས ཧ ཨ" + } + ], + "Sentence", + [ + { + "lang": "Latin", + "content": "Almost before we knew it, we had left the ground." + }, + { + "lang": "Greek", + "content": "Ήταν απλώς θέμα χρόνου." + }, + { + "lang": "Chinese (Simplified)", + "content": "他们所有的设备和仪器彷佛都是有生命的。" + }, + { + "lang": "Chinese (Traditional)", + "content": "他們所有的設備和儀器彷彿都是有生命的。" + }, + { + "lang": "Japanese", + "content": "彼らの機器や装置はすべて生命体だ。" + }, + { + "lang": "Korean", + "content": "그들의 장비와 기구는 모두 살아 있다." + }, + { + "lang": "Vietnamese", + "content": "Bầu trời trong xanh thăm thẳm, không một gợn mây." + }, + { + "lang": "Cyrillic", + "content": "Алая вспышка осветила силуэт зазубренного крыла." + }, + { + "lang": "Cyrillic Extended", + "content": "Видовище перед нашими очима справді вражало." + }, + { + "lang": "Arabic", + "content": "الحب سماء لا تمطر غير الأحلام." + }, + { + "lang": "Bengali", + "content": "আগুনের শিখা নিভে গিয়েছিল, আর তিনি জানলা দিয়ে তারাদের দিকে তাকালেন৷" + }, + { + "lang": "Devanagari", + "content": "अंतरिक्ष यान से दूर नीचे पृथ्वी शानदार ढंग से जगमगा रही थी ।" + }, + { + "lang": "Gujarati", + "content": "અમને તેની જાણ થાય તે પહેલાં જ, અમે જમીન છોડી દીધી હતી." + }, + { + "lang": "Gurmukhi", + "content": "ਸਵਾਲ ਸਿਰਫ਼ ਸਮੇਂ ਦਾ ਸੀ।" + }, + { + "lang": "Hebrew", + "content": "אז הגיע הלילה של כוכב השביט הראשון." + }, + { + "lang": "Kannada", + "content": "ಇದು ಕೇವಲ ಸಮಯದ ಪ್ರಶ್ನೆಯಾಗಿದೆ." + }, + { + "lang": "Khmer", + "content": "ខ្ញុំបានមើលព្យុះ ដែលមានភាពស្រស់ស្អាតណាស់ ប៉ុន្តែគួរឲ្យខ្លាច" + }, + { + "lang": "Malayalam", + "content": "അവരുടെ എല്ലാ ഉപകരണങ്ങളും യന്ത്രങ്ങളും ഏതെങ്കിലും രൂപത്തിൽ സജീവമാണ്." + }, + { + "lang": "Myanmar", + "content": "သူတို့ရဲ့ စက်ပစ္စည်းတွေ၊ ကိရိယာတွေ အားလုံး အသက်ရှင်ကြတယ်။" + }, + { + "lang": "Oriya", + "content": "ଏହା କେବଳ ଏକ ସମୟ କଥା ହିଁ ଥିଲା." + }, + { + "lang": "Sinhala", + "content": "එය කාලය පිළිබඳ ප්‍රශ්නයක් පමණක් විය." + }, + { + "lang": "Tamil", + "content": "அந்திமாலையில், அலைகள் வேகமாக வீசத் தொடங்கின." + }, + { + "lang": "Telugu", + "content": "ఆ రాత్రి మొదటిసారిగా ఒక నక్షత్రం నేలరాలింది." + }, + { + "lang": "Thai", + "content": "การเดินทางขากลับคงจะเหงา" + }, + { + "lang": "Tibetan", + "content": "ཁོ་ཚོའི་སྒྲིག་ཆས་དང་ལག་ཆ་ཡོད་ཚད་གསོན་པོ་རེད།" + } + ], + "Paragraph", + [ + { + "lang": "Latin", + "content": "A peep at some distant orb has power to raise and purify our thoughts like a strain of sacred music, or a noble picture, or a passage from the grander poets. It always does one good." + }, + { + "lang": "Greek", + "content": "Ήταν ένα υπέροχο ταξίδι και μέσω αυτού γνώρισα και αγάπησα πολλούς που δεν θα ξαναδώ ποτέ. Γιατί η ζωή δεν είναι απέραντη και ο καθένας πρέπει να εκπληρώσει το χρέος του για την ασφάλεια και την ευημερία του Ριντάουτ. Παρόλα αυτά, ταξιδεύαμε πολύ, πάντα. Αλλά υπήρχαν τόσα εκατομμύρια και τόσο λίγα χρόνια." + }, + { + "lang": "Chinese (Simplified)", + "content": "远方的星体犹如圣乐、名画或伟大诗人的诗句,一眼瞬间就能让我们的思想升华与净化。它总是能给人带来积极的影响。" + }, + { + "lang": "Chinese (Traditional)", + "content": "遠方的星體猶如聖樂、名畫或偉大詩人的詩句,一眼瞬間就能讓我們的思想昇華與淨化。它總是能給人帶來好的影響。" + }, + { + "lang": "Japanese", + "content": "各部位を正確に作るには時間がかかるので、当初の意図とは異なるが、巨大な人体を作ることにした。高さは約 8 フィートで、これに釣り合う体格だ。これを決断し、数か月にわたって材料を集め整理した後、作業を開始した。" + }, + { + "lang": "Korean", + "content": "저 멀리 있는 별을 바라보는 일은 신성한 음악이나 고귀한 그림 또는 위대한 시인의 글귀와 같이 우리의 생각을 자극하고 정화하는 힘을 갖고 있어. 언제나 효과가 좋지." + }, + { + "lang": "Vietnamese", + "content": "Chúng tôi đã đạt tới độ cao rất lớn trong khí quyển vì bầu trời tối đen và các vì sao không còn lấp lánh. Ảo giác về đường chân trời khiến đám mây ảm đạm bên dưới lõm xuống và chiếc xe như trôi bồng bềnh giữa quả cầu khổng lồ tăm tối." + }, + { + "lang": "Cyrillic", + "content": "Высокая гравитация выматывала его, но мышцы изо всех сил пытались приспособиться. Обессиленный, он уже не валился в постель сразу после занятий. Кошмары, не покидавшие его, стали только хуже." + }, + { + "lang": "Cyrillic Extended", + "content": "Вочевидь, ми високо піднялися в атмосферу, тому що небо було вугільно-чорним і зірки перестали мерехтіти. За принципом тієї ж ілюзії, коли горизонт моря піднімається до рівня глядача на схилі гори, темна хмара внизу розділилася, і здавалося, що судно пливе всередині величезної чорної сфери, посипаної вгорі сріблом." + }, + { + "lang": "Arabic", + "content": "وللناس مذاهبهم المختلفة في التخفف من الهموم والتخلص من الأحزان، فمنهم من يتسلى عنها بالقراءة، ومنهم من يتسلى عنها بالرياضة، ومنهم من يتسلى عنها بالاستماع للموسيقى والغناء، ومنهم من يذهب غير هذه المذاهب كلها لينسى نفسه ويفر من حياته الحاضرة وما تثقله به من الأعباء." + }, + { + "lang": "Bengali", + "content": "আপাতঃদৃষ্টিতে আমরা বায়ুমন্ডলের একটি সু-উচ্চতায় পৌঁছেছিলাম, যেখানে আকাশের রঙ সম্পূর্ণ কালো, এবং তারাদের ঝিকিমিকি আর দেখা যাচ্ছিল না৷ ঠিক যেমন উর্ধিত সমুদ্রের দিগন্ত দেখে পাহাড়ের চূড়ায় দাঁড়িয়ে থাকা একজন দর্শকের অক্ষিবিভ্রম হয়, ঠিক তেমনই নিচে কৃষ্ণবর্ণের মেঘগুলিকে উল্টানো বাটির মতো দেখতে লাগছিল, আর জাহাজটা যেন এক অসীম অন্ধকার কোনো গোলকপিণ্ডের মাঝখানে ভেসে যাচ্ছিল, যার উপরের অর্ধাংশ রূপা দিয়ে আবৃত৷" + }, + { + "lang": "Devanagari", + "content": "किसी दूरवर्ती पिंड की झलक में किसी पवित्र संगीत की झंकार या किसी बेहतरीन चित्र या किसी महान कवि के वाक्यों के समान हमारे विचारों को उन्नत बनाने और परिष्कृत करने की शक्ति होती है । इससे हमेशा कुछ उचित होता है ।" + }, + { + "lang": "Gujarati", + "content": "કેમકે કે હું હજી પણ વધતાં વેગથી ઉપર જઈ રહ્યો હતો, એટલે રાત અને દિવસનું અંતર એક નિરંતર અંધકારમાં બદલાઈ ગયું; આકાશનો રંગ આશ્વર્યજનક રીતે ઘેરા વાદળી રંગનું થઈ ગયું, જે સવાર થવાના સમય જેવા શાનદાર ચમકદાર રંગનો હોય છે અને સૂર્યની ચમક આગની લહેર, અંતરિક્ષમાં એક ચમકદાર કિરણ બની ગઈ; ચંદ્ર હળવી અને ચમકદાર પટ્ટી જેવો બની ગયો અને મને કોઈ તારો દેખાઈ રહ્યો ન હતો, માત્ર વાદળી રંગમાં સતત ચમકતો વૃત્ત બાકી હતો." + }, + { + "lang": "Gurmukhi", + "content": "ਮੈਂ ਕਿਵੇਂ ਨਾ ਕਿਵੇਂ ਮੰਗਲ ਗ੍ਰਹਿ ਦਾ ਚਿਹਰਾ ਵੇਖਾਂਗਾ, ਅਤੇ ਇਹ ਕਾਫ਼ੀ ਦੁਰਲੱਭ ਅਨੁਭਵ ਹੋਵੇਗਾ। ਮੈਨੂੰ ਇੰਝ ਲੱਗਦਾ ਹੈ ਕਿ ਕਿਸੇ ਸ਼ਕਤੀਸ਼ਾਲੀ ਟੈਲੀਸਕੋਪ ਤੋਂ ਅਸਮਾਨੀ ਪਿੰਡਾਂ ਨੂੰ ਵੇਖਣਾ, ਨਾਲ ਹੀ ਸੰਸਾਰ ਭਰ ਦੀ ਯਾਤਰਾ ਕਰਨਾ ਆਧੁਨਿਕ ਸਿੱਖਿਆ ਦਾ ਇੱਕ ਹਿੱਸਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ।" + }, + { + "lang": "Hebrew", + "content": "אראה את פני מאדים, כך או כך, וזו תהיה חוויה נדירה. דומני שהמחזה של הגופים השמיימים מבעד לטלסקופ מדוייק, כמו גם טיול מסביב לעולם, צריכים להיות חלק מחינוך ליברלי." + }, + { + "lang": "Kannada", + "content": "ಆದರೂ ನಾನು ಮಂಗಳನ ಮುಖ ನೋಡಿದೆ ಹಾಗೂ ಅದು ಒಂದು ಅಪರೂಪದ ಅನುಭವ ಆಗಿತ್ತು. ಉತ್ತಮ ದೂರದರ್ಶಕದ ಮೂಲಕ ಆಕಾಶಕಾಯಗಳನ್ನು ವೀಕ್ಷಿಸಬಹುದು ಹಾಗೂ ಜಗತ್ತಿನ ಸುತ್ತ ಒಂದು ಪ್ರವಾಸವು ಒಂದು ಶಿಕ್ಷಣದ ಒಂದು ಭಾಗವಾಗಿ ರೂಪುಗೊಳ್ಳುವಂತೆ ನನಗೆ ತೋರುತ್ತದೆ." + }, + { + "lang": "Khmer", + "content": "ខណៈពេលដែលខ្ញុំបន្តដំណើរ ហើយកំពុងបង្កើតល្បឿន ទិដ្ឋភាពថ្ងៃ និងយប់បានរួមបញ្ចូលគ្នាបង្កើតបានផ្ទៃពណ៌ប្រផេះ។ ផ្ទៃមេឃមានពណ៌ខៀវដ៏ស្រស់ត្រកាល ដែលជាពណ៌ដ៏ស្រស់ស្អាតដូចនៅពេលថ្ងៃរៀបលិច។ ព្រះអាទិត្យដែលមានចលនាបានក្លាយជាខ្សែនៃពន្លឺភ្លើង ប្រៀបបីដូចជាខ្សែរាងកោងដ៏អស្ចារ្យនៅក្នុងលំហ។ ព្រះច័ន្ទមានពន្លឺព្រាលៗ។ ខ្ញុំមិនឃើញផ្កាយមួយដួងសោះ ប៉ុន្តែខ្ញុំឃើញរង្វង់ដែលភ្លឺជាងនេះបញ្ចេញពន្លឺពណ៌ខៀវភ្លឹបភ្លែត។" + }, + { + "lang": "Malayalam", + "content": "ചൊവ്വയുടെ ഉപരിതലം കാണുക എന്നതാണെൻ്റെ ആഗ്രഹം, അത് തീർത്തും ഒരു അപൂർവ്വ അനുഭവമായിരിക്കും. ലോക പര്യടനം നടത്തുന്നത് പോലെ ടെലിസ്‌കോപ്പിലൂടെ ആകാശഗോളങ്ങളെ നിരീക്ഷിക്കുന്നതും സ്വതന്ത്ര വിദ്യാഭ്യാസത്തിൻ്റെ ഭാഗമാകണം എന്നാണ് എൻ്റെ കാഴ്‌ചപ്പാട്." + }, + { + "lang": "Myanmar", + "content": "အဝေးက တစုံတခုကို လှမ်းချောင်းကြည့်ရတာဟာ မှော်ဝင်တေးသွားတစ်ပုဒ်လို၊ ဂန္ထဝင်ပန်းချီကားတစ်ချပ်လို၊ ကဗျာကဝိများရဲ့ လက်ရာလေးတွေလို စိတ်ကို ရင့်ကျက်တည်ငြိမ်စေတဲ့ တန်ခိုးသတ္တိမျိုး ရှိလာစေတယ်။ အမြဲတမ်း တစ်ခုခု ကောင်းတာ ရှိနေမှာပါ။" + }, + { + "lang": "Oriya", + "content": "ଯେକୌଣସି ପ୍ରକାରେ, ମୁଁ ମଙ୍ଗଳ ଗ୍ରହର ମୁହଁ ଦେଖିବି, ଏବଂ ତାହା ଏକ ବିରଳ ଅନୁଭୂତି ହେବ. ଏହା ମତେ ଜଣାପଡୁଛି ଯେ ଏକ ସୂକ୍ଷ୍ମ ଦୂରବିକ୍ଷଣ ଯନ୍ତ୍ର ମାଧ୍ୟମରେ ସ୍ୱର୍ଗୀୟ ପଦାର୍ଥଗୁଡିକର ଏକ ଦର୍ଶନ, ତଥା ବିଶ୍ୱ ଚାରିପଟେ ଏକ ଭ୍ରମଣ, ଏକ ସ୍ୱାଧୀନ ଶିକ୍ଷାର ଅଂଶବିଶେଷ ହେବା ଉଚିତ୍‌." + }, + { + "lang": "Sinhala", + "content": "මා කෙසේ හෝ අඟහරු ලොව දැකිය යුතු අතර, අය දුලබ අත්දැකීමක් වනු ඇත. මට සිතෙන අන්දමට,කදිම දුරේක්ෂයක් හරහා ආකාශ වස්තු දැකීම මෙන්ම, ලොව වටා සංචාරයක්ද, මධ්‍යස්ථ අධ්‍යාපනයක කොටසක් විය යුතුය." + }, + { + "lang": "Tamil", + "content": "உண்மையில் அது ஒரு சிறந்த பயணமாகும், அதில் பல அன்பான நெஞ்சம் கொண்டவர்களைச் சந்தித்தேன். ஆனால், இனி என்னால் அவர்களைச் சந்திக்க முடியுமா என்பது சந்தேகமே. மனிதனின் வாழ்க்கை மிகவும் சிறியது, ஆதலால் ஒவ்வொருவரும் அச்சுறுத்தப்படும் அல்லது மறைந்துவரும் இனங்களின் நல்வாழ்வு மற்றும் பாதுகாப்பை நிலைநிறுத்துவதற்கான கடமையைக் கண்டிப்பாக செய்ய வேண்டும். ஆயினும், நான் அமைத்துக் கொண்ட அனைத்தையும் அடைய, எப்போதுமே நாங்கள் அதிகம் பயணித்தோம். ஆனால் அறிந்துகொள்ள பல லட்சக்கணக்கான விஷயங்களும், அதற்கு ஒரு சில வருடங்களும் மட்டுமே இருந்தன." + }, + { + "lang": "Telugu", + "content": "అయిదు చదరపు అడుగుల ఎత్తు గల వంగరంగులో ఉండే గడ్డి ఇసుకలో వారి వైపుగా వస్తున్నట్లు కనిపించింది. అది బాగా దగ్గరకు వచ్చాక అది గడ్డి కాదని, అందులో గడ్డి పరకలు కాకుండా కేవలం వంగరంగు వేర్లు ఉన్నాయని అతడు గ్రహించాడు. ఆ మొత్తం భాగంలోని ప్రతి చిన్న మొక్క వేర్లు అంచు లేని బండిచక్రపు ఆకుల లాగా తిరుగుతున్నాయి." + }, + { + "lang": "Thai", + "content": "การได้เห็นวัตถุทรงกลมจากระยะไกลมันมีพลังยกระดับและชำระล้างความคิดของเราได้เฉกเช่นเดียวกับเนื้อเพลงอันศักดิ์สิทธิ์ รูปเคารพ หรือบทกลอนจากกวีผู้ยิ่งใหญ่ มันทำให้เรารู้สึกดีเสมอ" + }, + { + "lang": "Tibetan", + "content": "རྒྱང་རིང་གི་སྐར་ཕུང་དེ་ནི་ལྷའི་གླུ་དབྱངས་དང་རིན་ཐང་ཅན་གྱི་རི་མོ་སོགས་རླབས་ཆེན་གྱི་སྙན་ངག་པའི་སྙན་རྩོམ་ཇི་བཞིན་སྐད་ཅིག་དེར་ང་ཚོའི་ཡིད་སེམས་འགུག་ཞིང་གཙང་སེལ་གཏོང་པ་མ་ཟད་ནམ་ཡང་བཟང་སྐུལ་བྱེད།" + } + ] +] diff --git a/PlaygroundBook/PrivateResources/UnicodeData.momd/UnicodeData.mom b/PlaygroundBook/PrivateResources/UnicodeData.momd/UnicodeData.mom new file mode 100644 index 0000000..e7a9ffe Binary files /dev/null and b/PlaygroundBook/PrivateResources/UnicodeData.momd/UnicodeData.mom differ diff --git a/PlaygroundBook/PrivateResources/UnicodeData.momd/UnicodeData.omo b/PlaygroundBook/PrivateResources/UnicodeData.momd/UnicodeData.omo new file mode 100644 index 0000000..469a402 Binary files /dev/null and b/PlaygroundBook/PrivateResources/UnicodeData.momd/UnicodeData.omo differ diff --git a/PlaygroundBook/PrivateResources/UnicodeData.momd/VersionInfo.plist b/PlaygroundBook/PrivateResources/UnicodeData.momd/VersionInfo.plist new file mode 100644 index 0000000..a7a6c6d Binary files /dev/null and b/PlaygroundBook/PrivateResources/UnicodeData.momd/VersionInfo.plist differ diff --git a/PlaygroundBook/PrivateResources/UnicodeData.sqlite b/PlaygroundBook/PrivateResources/UnicodeData.sqlite new file mode 100644 index 0000000..012eff3 Binary files /dev/null and b/PlaygroundBook/PrivateResources/UnicodeData.sqlite differ diff --git a/PlaygroundBook/PrivateResources/UnicodeData.xcdatamodeld/UnicodeData.xcdatamodel/contents b/PlaygroundBook/PrivateResources/UnicodeData.xcdatamodeld/UnicodeData.xcdatamodel/contents new file mode 100644 index 0000000..c1c5438 --- /dev/null +++ b/PlaygroundBook/PrivateResources/UnicodeData.xcdatamodeld/UnicodeData.xcdatamodel/contents @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/PlaygroundBook/PrivateResources/XIBs/CharactersCollectionHeaderView.xib b/PlaygroundBook/PrivateResources/XIBs/CharactersCollectionHeaderView.xib new file mode 100644 index 0000000..95cb2cb --- /dev/null +++ b/PlaygroundBook/PrivateResources/XIBs/CharactersCollectionHeaderView.xib @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlaygroundBook/PrivateResources/XIBs/CharactersCollectionViewCell.xib b/PlaygroundBook/PrivateResources/XIBs/CharactersCollectionViewCell.xib new file mode 100644 index 0000000..647886e --- /dev/null +++ b/PlaygroundBook/PrivateResources/XIBs/CharactersCollectionViewCell.xib @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlaygroundBook/PrivateResources/XIBs_LiveViewTestApp/CharactersCollectionHeaderView.xib b/PlaygroundBook/PrivateResources/XIBs_LiveViewTestApp/CharactersCollectionHeaderView.xib new file mode 100644 index 0000000..a52adc7 --- /dev/null +++ b/PlaygroundBook/PrivateResources/XIBs_LiveViewTestApp/CharactersCollectionHeaderView.xib @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlaygroundBook/PrivateResources/XIBs_LiveViewTestApp/CharactersCollectionViewCell.xib b/PlaygroundBook/PrivateResources/XIBs_LiveViewTestApp/CharactersCollectionViewCell.xib new file mode 100644 index 0000000..0130557 --- /dev/null +++ b/PlaygroundBook/PrivateResources/XIBs_LiveViewTestApp/CharactersCollectionViewCell.xib @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlaygroundBook/PrivateResources/en.lproj/ManifestPlist.strings b/PlaygroundBook/PrivateResources/en.lproj/ManifestPlist.strings new file mode 100644 index 0000000..9c9f7a1 --- /dev/null +++ b/PlaygroundBook/PrivateResources/en.lproj/ManifestPlist.strings @@ -0,0 +1,9 @@ +/* +See LICENSE folder for this template’s licensing information. + +Abstract: +Contains English localizations of values in Manifest.plist files in the playground book. +*/ + +/* The localized name of the playground book. */ +"PlaygroundBookName" = "Playground Book"; diff --git a/README.md b/README.md new file mode 100644 index 0000000..7536e98 --- /dev/null +++ b/README.md @@ -0,0 +1,153 @@ +# Playground Book Xcode Project # + +## Overview ## + +Welcome to the Playground Book Xcode project! This Xcode project is set up to produce two things: + +- A playground book +- An app for debugging the live view + +In support of this, there are five targets in this Xcode project: + +- **PlaygroundBook**: Produces a playground book as its output +- **BookCore**: Compiles the *BookCore* auxiliary module, an auxiliary module that contains the implementation of the live view and any other author-only functionality +- **BookAPI**: Compiles the *BookAPI* auxiliary module, an auxiliary module that is automatically imported into all user code throughout the book +- **UserModule**: Compiles the *UserModule* user module, a blank user module where users may write code +- **LiveViewTestApp**: Produces an app which uses the `Book_Sources` module to show the live view similarly to how it would be shown in Swift Playgrounds + +This project includes the PlaygroundSupport and PlaygroundBluetooth frameworks from Swift Playgrounds to allow the BookCore, BookAPI, UserModule, and LiveViewTestApp targets to take full advantage of those APIs. The supporting content included with this template, including these frameworks, requires Xcode 12.2 to build. Attempting to use this template with another version of Xcode may result in build errors. + +For more information about the playground book file format, see the *[Swift Playgrounds authoring documentation](https://developer.apple.com/go/?id=swift-playgroundbook-authoring)*. + +## First Steps ## + +To get started with this Xcode project, you need to make a few changes to personalize it for your playground book. + +1. Open `BuildSettings.xcconfig` (in the “Config Files” group in the Project navigator) and make the following modifications: + + - Set `BUNDLE_IDENTIFIER_PREFIX` to a value appropriate for your team + - Set `PLAYGROUND_BOOK_FILE_NAME` to a value appropriate for your playground book + +You may also modify `PLAYGROUND_BOOK_CONTENT_VERSION` to set the `ContentVersion` in the book's `Manifest.plist`. `PLAYGROUND_BOOK_CONTENT_IDENTIFIER` may also be modified to customize to set a specific `ContentIdentifier` in the book's `Manifest.plist`. (It defaults to a value based on `BUNDLE_IDENTIFIER_PREFIX` and `PLAYGROUND_BOOK_FILE_NAME`.) + +2. Open `ManifestPlist.strings` (in the “PrivateResources” group in the “PlaygroundBook” group in the Project navigator) and modify the values of the strings to be appropriate for your playground book. + +3. Open the Project Editor, select the LiveViewTestApp target, and select the appropriate Team in the Signing section. (This step is not required if you are only testing with the iOS Simulator.) + +Once you've finished configuring the project, you can build the PlaygroundBook target, which will produce your playground book as a product. (You can access it by opening the “Products” group in the Project navigator, and then right-clicking and selecting “Show in Finder”. From there, you can use AirDrop or other methods to copy the playground book to an iPad running Swift Playgrounds, or double-click it to import it into Swift Playgrounds on your Mac.) + +## Common Tasks ## + +This Xcode project is structured both in Xcode and on-disk in a very particular way to ensure that the book is assembled correctly. Below are guides for accomplishing some common tasks when creating your playground book. + +### Adding a New Auxiliary or User Module ### + +An auxiliary module is a Swift module which vends an API to the playground book without exposing the sources. A user module is a Swift module where the sources are fully editable by users. (For more information about auxiliary and user modules, see the *[Swift Playgrounds authoring documentation](https://developer.apple.com/go/?id=swift-playgroundbook-authoring)*.) + +To create an auxiliary module or user module, you need to create the right folder structure on-disk. You also need to create a static library target which builds the sources to get editor features such as code completion and live issues. This can be accomplished by performing the following steps: + + 1. In the Project navigator, expand the “PlaygroundBook” group + 2. If you are adding an auxiliary module, expand the “Modules” group. If you are adding a user module, expand the “UserModules” group. + 3. Control-click (or right-click) on the “Modules” or “UserModules” group + 4. Choose “New Group” from the context menu + 5. Name the newly-created group `ModuleName.playgroundmodule`, where “ModuleName” is the name of the module you are creatinng + 6. Control-click (or right-click) on the newly-created group + 7. Choose "New Group" from the context menu + 8. Name the newly-created group `Sources` + 9. Control-click (or right-click) on the newly-created group + 10. Choose “New File…” from the context menu, and create a new Swift file using the assistant + 11. Select the top-level Xcode project from the Project navigator + 12. In the Editor menu, select “Add Target…” + 13. Select the iOS platform at the top of the assistant, choose the “Static Library” template, and click the Next button + 14. In the “Product Name” field, put the name of the module, matching the name of the “ModuleName” you chose in step 5 + 15. Fill in the other fields and click the Finish button + 16. In the Project editor, select the project itself, and then select the Info tab + 17. In the Configurations outline view, expand all build configurations + 18. For each build configuration, in the row for the newly-added target, click “None” and choose “ModuleOverridingBuildSettings” from the pop-up menu + 19. In the Project editor, select the newly-added target, and then select the Build Settings tab + 20. Click “All” in the filter bar to show all build settings + 21. Click on a build setting to select it, and then open the Edit menu and click “Select All” + 22. Press the Delete key on your keyboard to remove all build setting overrides at the target level + 23. Select the Build Phases tab + 24. Expand the “Compile Sources” build phase + 25. Remove the source file that is listed in the “Compile Sources” build phase + 26. From the Project navigator, drag the source file added in step 10 into the “Compile Sources” build phase + 27. In the Project navigator, locate the group named after the target added in steps 12—15 (i.e. “ModuleName” **without** the “.playgroundbook” extension) + 28. Control-click (or right-click) on this group + 29. Choose “Delete” from the context menu + 30. Select “Move to Trash” in the confirmation dialog to delete the source files + +These steps give you a configured, standalone auxiliary or user module. As you’re using the module you’ve just created, it’s important to confirm the following things: + + - The source files for the auxiliary or user module **must** be inside of the “Sources” folder in the “ModuleName.playgroundmodule” folder in the “Modules” or “UserModules” folder on-disk. If not, they will not be copied into the final playground book + - You must specify explicit target dependencies in the Project editor to ensure that the modules build in the correct order + - If you want to use this module from LiveViewTestApp, you must specify an explicit target dependency from LiveViewTestApp to this module + +### Deleting an Auxiliary or User Module ### + +To delete an auxiliary module or user module, you need to delete three things: the target for the library which builds the module, the group in the Xcode project for the module, and the on-disk folder for the module. This can be accomplished by performing the following steps: + + 1. Select the top-level Xcode project from the Project navigator + 2. Select the target for the module you wish to delete + 3. Control-click (or right-click) on the target for the module + 4. Choose “Delete” from the context menu and confirm the deletion + 5. Control-click (or right-click) on the group for the module you wish to delete in the Project navigator (e.g. ModuleName.playgroundmodule) + 6. Choose “Delete” from the context menu + 7. Select “Move to Trash” in the confirmation dialog to delete the source files + 8. Expand the “Supporting Content” group (a child of the “PlaygroundBook” group) in the Project navigator + 9. Find the module you wish to delete in the “Modules” or “UserModules” folder + 10. Control-click (or right-click) on the folder for the module you wish to delete + 11. Choose “Delete” from the context menu and confirm the deletion + +### Renaming an Auxiliary or User Module ### + +To rename an auxiliary or user module, rename both the “ModuleName.playgroundmodule” group and “ModuleName” target to the new name (e.g. “NewModuleName.playgroundmodule” and “NewModuleName”, respectively). If you are renaming an auxiliary module which is in the `UserAutoImportedAuxiliaryModules` array in the book-level Manifest.plist, you will need to rename it there as well; otherwise, no further changes are required. + +### Adding Sources to an Auxiliary or User Module ### + +In order to work correctly throughout this project, source files must be added to the Xcode project, to the module's static library target, and to the `ModuleName.playgroundmodule/Sources` folder on disk. To add a new source file, you can either use the *File > New > File…* menu item, or right-click on the “Sources” group in the “ModuleName.playgroundmodule” group in the Project navigator and select *New File…*. + +In the assistant, select the appropriate template (either “Swift File” or “Cocoa Touch Class”, typically). When the assistant presents a sheet to save the new file, ensure the following is true: + + - The destination of the save sheet is the `Sources` directory inside of the `ModuleName.playgroundmodule` directory (where “ModuleName” is the name of the module to which you are adding a source file) + - The new source file is being added to the ModuleName target (and no others) + +Adding a source file to the previously-mentioned “Sources” group should default to a location where both of those are true. + +If a source file is not in the correct `Sources` directory on-disk, then it will not be copied into the correct location in the final playground book and will not be usable in Swift Playgrounds. + +If a source file is not a member of the module's static library target, then it will not be compiled in Xcode. This means that syntax highlighting, code completion, and other editor features will not work in that source file, and the API it provides will not be usable in other source files in the project. + +**Note**: Only Swift sources are supported in playground books. This Xcode project will not enforce that requirement, but if any non-Swift source files are present in the final playground book, Swift Playgrounds will ignore them. + +### Adding Book-Level PrivateResources ### + +To add content to the book-level `PrivateResources` directory, add the resource file to the Xcode project, and ensure it is a member of the “Copy Bundle Resources” build phase of the PlaygroundBook target. It will be compiled if necessary (as is the case for xibs, storyboards, asset catalogs, and some other resource types) and then copied in to the playground book's `PrivateResources` directory. + +### Adding Book-Level PublicResources ### + +This Xcode project does not support book-level `PublicResources` by default. To add a `PublicResources` directory to your book, do the following: + + 1. Create `PublicResources` directory in Finder in the `PlaygroundBook` directory + 2. Add the `PublicResources` directory to the “PlaygroundBook” group in Xcode as a folder reference (not as a group reference) + 3. Add the `PublicResources` directory to the “Copy Book Contents” build phase in the PlaygroundBook target in the Project editor + +Unlike `PrivateResources`, the contents of the `PublicResources` directory are only copied. They will not be compiled; if you use compiled resources, you must treat them as `PrivateResources`. + +### Adding Chapters, Pages, or Chapter- or Page-Level Content ## + +This Xcode project has limited support for editing the chapters and pages in your playground book. The `Chapters` directory is present in the Xcode project as a folder. You may add `.playgroundchapter` packages there, and they will automatically be copied into the final playground book. + +When adding a chapter or a page, you must also edit the book's or chapter's `Manifest.plist` file to reference the new chapter or page. + +This Xcode project does not support syntax highlighting, code completion, live issues, or other advanced editor features inside of chapters and pages. It is therefore recommended that as much source code as possible be included in the auxiliary modules, and that the chapters and pages have as little source code as possible. + +### Testing the Live View ### + +This Xcode project includes support for testing your playground book's live view. This testing support assumes that the live view for your playground book is implemented in BookCore auxiliary module. If it is implemented elsewhere (i.e. in a page's `Contents.swift` or `LiveView.swift` file), then it cannot easily be tested using this mechanism. + +To test your live view, run the LiveViewTestApp app. This app, which works on iPad, in the iOS Simulator, and as a Mac Catalyst app, is capable of displaying a live view in a manner similar to how Swift Playgrounds would display it. Most notably, LiveViewTestApp will correctly configure the live view safe area layout guides exposed by the PlaygroundSupport framework. + +To configure your live view, implement the `setUpLiveView()` method in `AppDelegate.swift`. This should return a `UIView` or `UIViewController` which is ready to be used as the live view. + +By default, LiveViewTestApp will run your live view in full screen. LiveViewTestApp is also able to run your live view in a side-by-side view (as if it were in Swift Playgrounds with the source code editor either next to or below the live view). To enable this, make the `liveViewConfiguration` property in `AppDelegate.swift` return `.sideBySide` instead of `.fullScreen`. diff --git a/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Headers/LVHHostViewController.h b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Headers/LVHHostViewController.h new file mode 100644 index 0000000..f221593 --- /dev/null +++ b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Headers/LVHHostViewController.h @@ -0,0 +1,23 @@ +// +// LiveViewHostViewController.h +// LiveViewHost +// +// Copyright © 2018 Apple Inc. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// \brief A view controller which is capable of directly hosting a live view. +/// +/// \c LVHHostViewController should only be used directly if not using the default \c LiveViewHost.AppDelegate class. +@interface LVHHostViewController : UIViewController + +@property (readwrite, nonatomic, nullable) UIViewController *liveView; + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Headers/LVHInitialization.h b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Headers/LVHInitialization.h new file mode 100644 index 0000000..30494c3 --- /dev/null +++ b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Headers/LVHInitialization.h @@ -0,0 +1,14 @@ +// +// LVHInitialization.h +// LiveViewHost +// +// Copyright © 2018 Apple Inc. All rights reserved. +// + +#import + +/// \brief Initializes the LiveViewHost framework. +/// +/// This function is automatically called by \c LiveViewHost.AppDelegate. +/// If you do not use \c LiveViewHost.AppDelegate, then you should call this function. +FOUNDATION_EXPORT void LVHInitialize(void); diff --git a/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Headers/LiveViewHost.h b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Headers/LiveViewHost.h new file mode 100644 index 0000000..d15c6b6 --- /dev/null +++ b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Headers/LiveViewHost.h @@ -0,0 +1,17 @@ +// +// LiveViewHost.h +// LiveViewHost +// +// Copyright © 2018 Apple Inc. All rights reserved. +// + +#import + +//! Project version number for LiveViewHost. +FOUNDATION_EXPORT double LiveViewHostVersionNumber; + +//! Project version string for LiveViewHost. +FOUNDATION_EXPORT const unsigned char LiveViewHostVersionString[]; + +#import +#import diff --git a/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Info.plist b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Info.plist new file mode 100644 index 0000000..5854630 Binary files /dev/null and b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Info.plist differ diff --git a/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/LiveViewHost b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/LiveViewHost new file mode 100755 index 0000000..5aa7273 Binary files /dev/null and b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/LiveViewHost differ diff --git a/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64-apple-ios.swiftdoc b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64-apple-ios.swiftdoc new file mode 100644 index 0000000..6ed6179 Binary files /dev/null and b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64-apple-ios.swiftdoc differ diff --git a/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64-apple-ios.swiftmodule b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64-apple-ios.swiftmodule new file mode 100644 index 0000000..da8467c Binary files /dev/null and b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64-apple-ios.swiftmodule differ diff --git a/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64.swiftdoc b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64.swiftdoc new file mode 100644 index 0000000..6ed6179 Binary files /dev/null and b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64.swiftdoc differ diff --git a/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64.swiftmodule b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64.swiftmodule new file mode 100644 index 0000000..da8467c Binary files /dev/null and b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64.swiftmodule differ diff --git a/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Modules/module.modulemap b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Modules/module.modulemap new file mode 100644 index 0000000..3ec427e --- /dev/null +++ b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module LiveViewHost { + umbrella header "LiveViewHost.h" + + export * + module * { export * } +} diff --git a/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/_CodeSignature/CodeResources b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/_CodeSignature/CodeResources new file mode 100644 index 0000000..d508bda --- /dev/null +++ b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/_CodeSignature/CodeResources @@ -0,0 +1,237 @@ + + + + + files + + Headers/LVHHostViewController.h + + AVBCm+Pjqu5dwCiJK0BJCC6b/G0= + + Headers/LVHInitialization.h + + mUQufV90MvxQRrsoZqK+1euqMe8= + + Headers/LiveViewHost.h + + reIdyOuLg8dnksaMgZwZT3LWWHc= + + Info.plist + + ePmYk1UIYF6BVuY5zPmGvo1nFtI= + + Modules/LiveViewHost.swiftmodule/arm64-apple-ios.swiftdoc + + rwWspoF4W/JA5hL7jFUxF3VKasY= + + Modules/LiveViewHost.swiftmodule/arm64-apple-ios.swiftmodule + + 9GPHNNJIRXQuYn27h+2tfslGHZI= + + Modules/LiveViewHost.swiftmodule/arm64.swiftdoc + + rwWspoF4W/JA5hL7jFUxF3VKasY= + + Modules/LiveViewHost.swiftmodule/arm64.swiftmodule + + 9GPHNNJIRXQuYn27h+2tfslGHZI= + + Modules/module.modulemap + + wbBJ01HD+QOtrC3j8UYGj4MLjkc= + + version.plist + + hbb3KZd2ZL3RvqiXyhFKSIkudk0= + + + files2 + + Headers/LVHHostViewController.h + + hash + + AVBCm+Pjqu5dwCiJK0BJCC6b/G0= + + hash2 + + BjNwqDDUCBe6d1Vue/J009znkzmQlJayYrk7ZJ6U06E= + + + Headers/LVHInitialization.h + + hash + + mUQufV90MvxQRrsoZqK+1euqMe8= + + hash2 + + ehXAHnNyJrLTAVieZWH02su+pjNFKO2MzXFa7G7RG6U= + + + Headers/LiveViewHost.h + + hash + + reIdyOuLg8dnksaMgZwZT3LWWHc= + + hash2 + + wanDFq4ApyHB5E7dbuWBr/hVxldpQrNLioR3wKSFAI4= + + + Modules/LiveViewHost.swiftmodule/arm64-apple-ios.swiftdoc + + hash + + rwWspoF4W/JA5hL7jFUxF3VKasY= + + hash2 + + x4+5mP1nGiFmBDCo7oQPnlM1mcveaDf1NaOWh9Wj+NE= + + + Modules/LiveViewHost.swiftmodule/arm64-apple-ios.swiftmodule + + hash + + 9GPHNNJIRXQuYn27h+2tfslGHZI= + + hash2 + + x5e0APaNK/0fST9Kt53FQFnCoDfmVFwcO7uhkKOOqbc= + + + Modules/LiveViewHost.swiftmodule/arm64.swiftdoc + + hash + + rwWspoF4W/JA5hL7jFUxF3VKasY= + + hash2 + + x4+5mP1nGiFmBDCo7oQPnlM1mcveaDf1NaOWh9Wj+NE= + + + Modules/LiveViewHost.swiftmodule/arm64.swiftmodule + + hash + + 9GPHNNJIRXQuYn27h+2tfslGHZI= + + hash2 + + x5e0APaNK/0fST9Kt53FQFnCoDfmVFwcO7uhkKOOqbc= + + + Modules/module.modulemap + + hash + + wbBJ01HD+QOtrC3j8UYGj4MLjkc= + + hash2 + + oCZJDkJCW/8imzJCKOZeEEtXZrQHkn5YWvOfK4yoYBc= + + + version.plist + + hash + + hbb3KZd2ZL3RvqiXyhFKSIkudk0= + + hash2 + + 6Odj4rjUtxjlXHLzycE+O2A1o0hqjSMi3jrdXregQlo= + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/version.plist b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/version.plist new file mode 100644 index 0000000..d0ec3e1 --- /dev/null +++ b/SupportingContent/OtherFrameworks/iphoneos/LiveViewHost.framework/version.plist @@ -0,0 +1,18 @@ + + + + + BuildAliasOf + SerenityBookTemplate + BuildVersion + 2 + CFBundleShortVersionString + 1.0 + CFBundleVersion + 59.1 + ProjectName + SerenityBookTemplate + SourceVersion + 25000000000000 + + diff --git a/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Headers/LVHHostViewController.h b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Headers/LVHHostViewController.h new file mode 100644 index 0000000..f221593 --- /dev/null +++ b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Headers/LVHHostViewController.h @@ -0,0 +1,23 @@ +// +// LiveViewHostViewController.h +// LiveViewHost +// +// Copyright © 2018 Apple Inc. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// \brief A view controller which is capable of directly hosting a live view. +/// +/// \c LVHHostViewController should only be used directly if not using the default \c LiveViewHost.AppDelegate class. +@interface LVHHostViewController : UIViewController + +@property (readwrite, nonatomic, nullable) UIViewController *liveView; + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Headers/LVHInitialization.h b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Headers/LVHInitialization.h new file mode 100644 index 0000000..30494c3 --- /dev/null +++ b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Headers/LVHInitialization.h @@ -0,0 +1,14 @@ +// +// LVHInitialization.h +// LiveViewHost +// +// Copyright © 2018 Apple Inc. All rights reserved. +// + +#import + +/// \brief Initializes the LiveViewHost framework. +/// +/// This function is automatically called by \c LiveViewHost.AppDelegate. +/// If you do not use \c LiveViewHost.AppDelegate, then you should call this function. +FOUNDATION_EXPORT void LVHInitialize(void); diff --git a/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Headers/LiveViewHost.h b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Headers/LiveViewHost.h new file mode 100644 index 0000000..d15c6b6 --- /dev/null +++ b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Headers/LiveViewHost.h @@ -0,0 +1,17 @@ +// +// LiveViewHost.h +// LiveViewHost +// +// Copyright © 2018 Apple Inc. All rights reserved. +// + +#import + +//! Project version number for LiveViewHost. +FOUNDATION_EXPORT double LiveViewHostVersionNumber; + +//! Project version string for LiveViewHost. +FOUNDATION_EXPORT const unsigned char LiveViewHostVersionString[]; + +#import +#import diff --git a/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Info.plist b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Info.plist new file mode 100644 index 0000000..9c995f4 Binary files /dev/null and b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Info.plist differ diff --git a/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/LiveViewHost b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/LiveViewHost new file mode 100755 index 0000000..9f756e0 Binary files /dev/null and b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/LiveViewHost differ diff --git a/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64-apple-ios-simulator.swiftdoc b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64-apple-ios-simulator.swiftdoc new file mode 100644 index 0000000..2a899c9 Binary files /dev/null and b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64-apple-ios-simulator.swiftdoc differ diff --git a/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64-apple-ios-simulator.swiftmodule b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64-apple-ios-simulator.swiftmodule new file mode 100644 index 0000000..be7bb7e Binary files /dev/null and b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64-apple-ios-simulator.swiftmodule differ diff --git a/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64.swiftdoc b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64.swiftdoc new file mode 100644 index 0000000..2a899c9 Binary files /dev/null and b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64.swiftdoc differ diff --git a/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64.swiftmodule b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64.swiftmodule new file mode 100644 index 0000000..be7bb7e Binary files /dev/null and b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/arm64.swiftmodule differ diff --git a/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-simulator.swiftdoc b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-simulator.swiftdoc new file mode 100644 index 0000000..169b8aa Binary files /dev/null and b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-simulator.swiftdoc differ diff --git a/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-simulator.swiftmodule b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-simulator.swiftmodule new file mode 100644 index 0000000..ff6f4af Binary files /dev/null and b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-simulator.swiftmodule differ diff --git a/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/x86_64.swiftdoc b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/x86_64.swiftdoc new file mode 100644 index 0000000..169b8aa Binary files /dev/null and b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/x86_64.swiftdoc differ diff --git a/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/x86_64.swiftmodule b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/x86_64.swiftmodule new file mode 100644 index 0000000..ff6f4af Binary files /dev/null and b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/LiveViewHost.swiftmodule/x86_64.swiftmodule differ diff --git a/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/module.modulemap b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/module.modulemap new file mode 100644 index 0000000..3ec427e --- /dev/null +++ b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module LiveViewHost { + umbrella header "LiveViewHost.h" + + export * + module * { export * } +} diff --git a/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/_CodeSignature/CodeResources b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/_CodeSignature/CodeResources new file mode 100644 index 0000000..a26732f --- /dev/null +++ b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/_CodeSignature/CodeResources @@ -0,0 +1,297 @@ + + + + + files + + Headers/LVHHostViewController.h + + AVBCm+Pjqu5dwCiJK0BJCC6b/G0= + + Headers/LVHInitialization.h + + mUQufV90MvxQRrsoZqK+1euqMe8= + + Headers/LiveViewHost.h + + reIdyOuLg8dnksaMgZwZT3LWWHc= + + Info.plist + + gpPK+1HlRr6gUYbMG89DvDQd5tc= + + Modules/LiveViewHost.swiftmodule/arm64-apple-ios-simulator.swiftdoc + + 4xhkA///uXryWsiR+Gr6TkoExLo= + + Modules/LiveViewHost.swiftmodule/arm64-apple-ios-simulator.swiftmodule + + ehscJbjaJRIQ+dirQJWougLYdbI= + + Modules/LiveViewHost.swiftmodule/arm64.swiftdoc + + 4xhkA///uXryWsiR+Gr6TkoExLo= + + Modules/LiveViewHost.swiftmodule/arm64.swiftmodule + + ehscJbjaJRIQ+dirQJWougLYdbI= + + Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-simulator.swiftdoc + + uEh6AiYP6uR6213jwFB9YYSp6JA= + + Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-simulator.swiftmodule + + ylcKf0NWbmSpEvhotLFpggXqmhE= + + Modules/LiveViewHost.swiftmodule/x86_64.swiftdoc + + uEh6AiYP6uR6213jwFB9YYSp6JA= + + Modules/LiveViewHost.swiftmodule/x86_64.swiftmodule + + ylcKf0NWbmSpEvhotLFpggXqmhE= + + Modules/module.modulemap + + wbBJ01HD+QOtrC3j8UYGj4MLjkc= + + version.plist + + hbb3KZd2ZL3RvqiXyhFKSIkudk0= + + + files2 + + Headers/LVHHostViewController.h + + hash + + AVBCm+Pjqu5dwCiJK0BJCC6b/G0= + + hash2 + + BjNwqDDUCBe6d1Vue/J009znkzmQlJayYrk7ZJ6U06E= + + + Headers/LVHInitialization.h + + hash + + mUQufV90MvxQRrsoZqK+1euqMe8= + + hash2 + + ehXAHnNyJrLTAVieZWH02su+pjNFKO2MzXFa7G7RG6U= + + + Headers/LiveViewHost.h + + hash + + reIdyOuLg8dnksaMgZwZT3LWWHc= + + hash2 + + wanDFq4ApyHB5E7dbuWBr/hVxldpQrNLioR3wKSFAI4= + + + Modules/LiveViewHost.swiftmodule/arm64-apple-ios-simulator.swiftdoc + + hash + + 4xhkA///uXryWsiR+Gr6TkoExLo= + + hash2 + + u1/TYcgYRMERzjaw6WXidEUkQH3ADAJbagD9i6GRLMo= + + + Modules/LiveViewHost.swiftmodule/arm64-apple-ios-simulator.swiftmodule + + hash + + ehscJbjaJRIQ+dirQJWougLYdbI= + + hash2 + + YP/fEru/NkwqGAflA12hViqRRvf8MGQ+EeycpXOjbSE= + + + Modules/LiveViewHost.swiftmodule/arm64.swiftdoc + + hash + + 4xhkA///uXryWsiR+Gr6TkoExLo= + + hash2 + + u1/TYcgYRMERzjaw6WXidEUkQH3ADAJbagD9i6GRLMo= + + + Modules/LiveViewHost.swiftmodule/arm64.swiftmodule + + hash + + ehscJbjaJRIQ+dirQJWougLYdbI= + + hash2 + + YP/fEru/NkwqGAflA12hViqRRvf8MGQ+EeycpXOjbSE= + + + Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-simulator.swiftdoc + + hash + + uEh6AiYP6uR6213jwFB9YYSp6JA= + + hash2 + + n0rHXMVkgTwVIaG/Tzp+2XqVZVghBXS1GSzB6XsrLXA= + + + Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-simulator.swiftmodule + + hash + + ylcKf0NWbmSpEvhotLFpggXqmhE= + + hash2 + + E7820qowQTfX8BLDOuYfWR8Kb96p8EgAhG33m8WgIsw= + + + Modules/LiveViewHost.swiftmodule/x86_64.swiftdoc + + hash + + uEh6AiYP6uR6213jwFB9YYSp6JA= + + hash2 + + n0rHXMVkgTwVIaG/Tzp+2XqVZVghBXS1GSzB6XsrLXA= + + + Modules/LiveViewHost.swiftmodule/x86_64.swiftmodule + + hash + + ylcKf0NWbmSpEvhotLFpggXqmhE= + + hash2 + + E7820qowQTfX8BLDOuYfWR8Kb96p8EgAhG33m8WgIsw= + + + Modules/module.modulemap + + hash + + wbBJ01HD+QOtrC3j8UYGj4MLjkc= + + hash2 + + oCZJDkJCW/8imzJCKOZeEEtXZrQHkn5YWvOfK4yoYBc= + + + version.plist + + hash + + hbb3KZd2ZL3RvqiXyhFKSIkudk0= + + hash2 + + 6Odj4rjUtxjlXHLzycE+O2A1o0hqjSMi3jrdXregQlo= + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/version.plist b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/version.plist new file mode 100644 index 0000000..d0ec3e1 --- /dev/null +++ b/SupportingContent/OtherFrameworks/iphonesimulator/LiveViewHost.framework/version.plist @@ -0,0 +1,18 @@ + + + + + BuildAliasOf + SerenityBookTemplate + BuildVersion + 2 + CFBundleShortVersionString + 1.0 + CFBundleVersion + 59.1 + ProjectName + SerenityBookTemplate + SourceVersion + 25000000000000 + + diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Headers b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Headers new file mode 120000 index 0000000..a177d2a --- /dev/null +++ b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Headers @@ -0,0 +1 @@ +Versions/Current/Headers \ No newline at end of file diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/LiveViewHost b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/LiveViewHost new file mode 120000 index 0000000..5925f22 --- /dev/null +++ b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/LiveViewHost @@ -0,0 +1 @@ +Versions/Current/LiveViewHost \ No newline at end of file diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Modules b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Modules new file mode 120000 index 0000000..5736f31 --- /dev/null +++ b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Modules @@ -0,0 +1 @@ +Versions/Current/Modules \ No newline at end of file diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Resources b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Resources new file mode 120000 index 0000000..953ee36 --- /dev/null +++ b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Resources @@ -0,0 +1 @@ +Versions/Current/Resources \ No newline at end of file diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Headers/LVHHostViewController.h b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Headers/LVHHostViewController.h new file mode 100644 index 0000000..f221593 --- /dev/null +++ b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Headers/LVHHostViewController.h @@ -0,0 +1,23 @@ +// +// LiveViewHostViewController.h +// LiveViewHost +// +// Copyright © 2018 Apple Inc. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// \brief A view controller which is capable of directly hosting a live view. +/// +/// \c LVHHostViewController should only be used directly if not using the default \c LiveViewHost.AppDelegate class. +@interface LVHHostViewController : UIViewController + +@property (readwrite, nonatomic, nullable) UIViewController *liveView; + +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Headers/LVHInitialization.h b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Headers/LVHInitialization.h new file mode 100644 index 0000000..30494c3 --- /dev/null +++ b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Headers/LVHInitialization.h @@ -0,0 +1,14 @@ +// +// LVHInitialization.h +// LiveViewHost +// +// Copyright © 2018 Apple Inc. All rights reserved. +// + +#import + +/// \brief Initializes the LiveViewHost framework. +/// +/// This function is automatically called by \c LiveViewHost.AppDelegate. +/// If you do not use \c LiveViewHost.AppDelegate, then you should call this function. +FOUNDATION_EXPORT void LVHInitialize(void); diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Headers/LiveViewHost.h b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Headers/LiveViewHost.h new file mode 100644 index 0000000..d15c6b6 --- /dev/null +++ b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Headers/LiveViewHost.h @@ -0,0 +1,17 @@ +// +// LiveViewHost.h +// LiveViewHost +// +// Copyright © 2018 Apple Inc. All rights reserved. +// + +#import + +//! Project version number for LiveViewHost. +FOUNDATION_EXPORT double LiveViewHostVersionNumber; + +//! Project version string for LiveViewHost. +FOUNDATION_EXPORT const unsigned char LiveViewHostVersionString[]; + +#import +#import diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/LiveViewHost b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/LiveViewHost new file mode 100755 index 0000000..5f9adc1 Binary files /dev/null and b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/LiveViewHost differ diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/arm64-apple-ios-macabi.swiftdoc b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/arm64-apple-ios-macabi.swiftdoc new file mode 100644 index 0000000..f122ab2 Binary files /dev/null and b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/arm64-apple-ios-macabi.swiftdoc differ diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/arm64-apple-ios-macabi.swiftmodule b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/arm64-apple-ios-macabi.swiftmodule new file mode 100644 index 0000000..2daf2d8 Binary files /dev/null and b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/arm64-apple-ios-macabi.swiftmodule differ diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/arm64.swiftdoc b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/arm64.swiftdoc new file mode 100644 index 0000000..f122ab2 Binary files /dev/null and b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/arm64.swiftdoc differ diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/arm64.swiftmodule b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/arm64.swiftmodule new file mode 100644 index 0000000..2daf2d8 Binary files /dev/null and b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/arm64.swiftmodule differ diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-macabi.swiftdoc b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-macabi.swiftdoc new file mode 100644 index 0000000..7e87218 Binary files /dev/null and b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-macabi.swiftdoc differ diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-macabi.swiftmodule b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-macabi.swiftmodule new file mode 100644 index 0000000..30aa340 Binary files /dev/null and b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-macabi.swiftmodule differ diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/x86_64.swiftdoc b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/x86_64.swiftdoc new file mode 100644 index 0000000..7e87218 Binary files /dev/null and b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/x86_64.swiftdoc differ diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/x86_64.swiftmodule b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/x86_64.swiftmodule new file mode 100644 index 0000000..30aa340 Binary files /dev/null and b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/LiveViewHost.swiftmodule/x86_64.swiftmodule differ diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/module.modulemap b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/module.modulemap new file mode 100644 index 0000000..3ec427e --- /dev/null +++ b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module LiveViewHost { + umbrella header "LiveViewHost.h" + + export * + module * { export * } +} diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Resources/Info.plist b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Resources/Info.plist new file mode 100644 index 0000000..78ce74b Binary files /dev/null and b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Resources/Info.plist differ diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Resources/version.plist b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Resources/version.plist new file mode 100644 index 0000000..d0ec3e1 --- /dev/null +++ b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/Resources/version.plist @@ -0,0 +1,18 @@ + + + + + BuildAliasOf + SerenityBookTemplate + BuildVersion + 2 + CFBundleShortVersionString + 1.0 + CFBundleVersion + 59.1 + ProjectName + SerenityBookTemplate + SourceVersion + 25000000000000 + + diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/_CodeSignature/CodeResources b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/_CodeSignature/CodeResources new file mode 100644 index 0000000..df04e10 --- /dev/null +++ b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/A/_CodeSignature/CodeResources @@ -0,0 +1,223 @@ + + + + + files + + Resources/Info.plist + + tu49pWrWLKKfNggdJsBZqwofCIo= + + Resources/version.plist + + hbb3KZd2ZL3RvqiXyhFKSIkudk0= + + + files2 + + Headers/LVHHostViewController.h + + hash2 + + BjNwqDDUCBe6d1Vue/J009znkzmQlJayYrk7ZJ6U06E= + + + Headers/LVHInitialization.h + + hash2 + + ehXAHnNyJrLTAVieZWH02su+pjNFKO2MzXFa7G7RG6U= + + + Headers/LiveViewHost.h + + hash2 + + wanDFq4ApyHB5E7dbuWBr/hVxldpQrNLioR3wKSFAI4= + + + Modules/LiveViewHost.swiftmodule/arm64-apple-ios-macabi.swiftdoc + + hash2 + + TnxXPFpZqEdIRFn1/czVnYAR0d2JgIlMfivIHSNxEsQ= + + + Modules/LiveViewHost.swiftmodule/arm64-apple-ios-macabi.swiftmodule + + hash2 + + 9wpYS6f1AXwUJnVYEjFOiPTNoSlZ7GP3gY6oo+RRMiE= + + + Modules/LiveViewHost.swiftmodule/arm64.swiftdoc + + hash2 + + TnxXPFpZqEdIRFn1/czVnYAR0d2JgIlMfivIHSNxEsQ= + + + Modules/LiveViewHost.swiftmodule/arm64.swiftmodule + + hash2 + + 9wpYS6f1AXwUJnVYEjFOiPTNoSlZ7GP3gY6oo+RRMiE= + + + Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-macabi.swiftdoc + + hash2 + + WvvWHfj5sYdsdAC0FMS6D02q5nLEU6WnUO153zga0qs= + + + Modules/LiveViewHost.swiftmodule/x86_64-apple-ios-macabi.swiftmodule + + hash2 + + yJ1fswpqNo/yrLK7RanEyPSt7ZXwUj0lq3TMt5fhQZQ= + + + Modules/LiveViewHost.swiftmodule/x86_64.swiftdoc + + hash2 + + WvvWHfj5sYdsdAC0FMS6D02q5nLEU6WnUO153zga0qs= + + + Modules/LiveViewHost.swiftmodule/x86_64.swiftmodule + + hash2 + + yJ1fswpqNo/yrLK7RanEyPSt7ZXwUj0lq3TMt5fhQZQ= + + + Modules/module.modulemap + + hash2 + + oCZJDkJCW/8imzJCKOZeEEtXZrQHkn5YWvOfK4yoYBc= + + + Resources/Info.plist + + hash2 + + 1Hxo3gz1lrWVjkF+u0O+ael8gzE4RPzm4kaOxpqysGQ= + + + Resources/version.plist + + hash2 + + 6Odj4rjUtxjlXHLzycE+O2A1o0hqjSMi3jrdXregQlo= + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/Current b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/Current new file mode 120000 index 0000000..8c7e5a6 --- /dev/null +++ b/SupportingContent/OtherFrameworks/maccatalyst/LiveViewHost.framework/Versions/Current @@ -0,0 +1 @@ +A \ No newline at end of file diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Assets.car b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Assets.car new file mode 100644 index 0000000..a6cc029 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Assets.car differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Headers/PlaygroundBluetooth.h b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Headers/PlaygroundBluetooth.h new file mode 100644 index 0000000..caf3b65 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Headers/PlaygroundBluetooth.h @@ -0,0 +1,6 @@ +// +// PlaygroundBluetooth.h +// PlaygroundBluetooth +// +// Copyright © 2017 Apple Inc. All rights reserved. +// diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Info.plist b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Info.plist new file mode 100644 index 0000000..4ad255f Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Info.plist differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios.swiftdoc b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios.swiftdoc new file mode 100644 index 0000000..d680024 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios.swiftmodule b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios.swiftmodule new file mode 100644 index 0000000..cb80dec Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftdoc b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftdoc new file mode 100644 index 0000000..d680024 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftmodule b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftmodule new file mode 100644 index 0000000..cb80dec Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Modules/module.modulemap b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Modules/module.modulemap new file mode 100644 index 0000000..9f53d45 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module PlaygroundBluetooth { + umbrella header "PlaygroundBluetooth.h" + + export * + module * { export * } +} diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/PlaygroundBluetooth b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/PlaygroundBluetooth new file mode 100755 index 0000000..6350e18 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/PlaygroundBluetooth differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/_CodeSignature/CodeResources b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/_CodeSignature/CodeResources new file mode 100644 index 0000000..91435cd --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/_CodeSignature/CodeResources @@ -0,0 +1,244 @@ + + + + + files + + Assets.car + + jTcAGgNDX3CvZ8YDAllSmkzOL8E= + + Headers/PlaygroundBluetooth.h + + b9Jq2RsaKy8FFfAfHWtmLfNOzbk= + + Info.plist + + efc27IXSiL4BI4wCfcSNMUNHefw= + + Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios.swiftdoc + + IQFXmBlY+PcC9mOAZMM8TUZDkpQ= + + Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios.swiftmodule + + i5byuV/reugRROhbVbKjeOCarGc= + + Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftdoc + + IQFXmBlY+PcC9mOAZMM8TUZDkpQ= + + Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftmodule + + i5byuV/reugRROhbVbKjeOCarGc= + + Modules/module.modulemap + + GdvBNEmovuLN9v8ssg4TCp8HEt0= + + en.lproj/Localizable.strings + + hash + + uD1Rs4m418/14rMR9TxSI1PvUqY= + + optional + + + version.plist + + 1bIXGF0k3LOpygZsXfFrez/AGWw= + + + files2 + + Assets.car + + hash + + jTcAGgNDX3CvZ8YDAllSmkzOL8E= + + hash2 + + 5S8UoEVOrc4wYfjn8ulX5kCPDIIeSuTUa3H1rP9z77Q= + + + Headers/PlaygroundBluetooth.h + + hash + + b9Jq2RsaKy8FFfAfHWtmLfNOzbk= + + hash2 + + VDUOD3BofKO+uoA++HBb7jZVu7/GOv3WkB7av4+S7Sk= + + + Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios.swiftdoc + + hash + + IQFXmBlY+PcC9mOAZMM8TUZDkpQ= + + hash2 + + cVrtyUyH3jQUliYCnLvavC9YYhLjvhTP5Zs3cw0qGI8= + + + Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios.swiftmodule + + hash + + i5byuV/reugRROhbVbKjeOCarGc= + + hash2 + + I04ak/DWBq5H2IB68NGRG6/bK6x00VzPNHIlQzXAnjs= + + + Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftdoc + + hash + + IQFXmBlY+PcC9mOAZMM8TUZDkpQ= + + hash2 + + cVrtyUyH3jQUliYCnLvavC9YYhLjvhTP5Zs3cw0qGI8= + + + Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftmodule + + hash + + i5byuV/reugRROhbVbKjeOCarGc= + + hash2 + + I04ak/DWBq5H2IB68NGRG6/bK6x00VzPNHIlQzXAnjs= + + + Modules/module.modulemap + + hash + + GdvBNEmovuLN9v8ssg4TCp8HEt0= + + hash2 + + KDH6BlM0dvMkACFfCQRFXvI+E0o9C3s3ONGwS5qgFpU= + + + en.lproj/Localizable.strings + + hash + + uD1Rs4m418/14rMR9TxSI1PvUqY= + + hash2 + + vm73WMbhgAHzrp6YlQ2VE0vlaSHMcIB5o8w7MsIhRuQ= + + optional + + + version.plist + + hash + + 1bIXGF0k3LOpygZsXfFrez/AGWw= + + hash2 + + kpzFRE1qXcps/GI1x8Dr1o+ldNZ68lkRVJgTZ31DDH4= + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/en.lproj/Localizable.strings b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/en.lproj/Localizable.strings new file mode 100644 index 0000000..3fd528e Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/en.lproj/Localizable.strings differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/version.plist b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/version.plist new file mode 100644 index 0000000..239ac4d --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundBluetooth.framework/version.plist @@ -0,0 +1,18 @@ + + + + + BuildAliasOf + SerenityBookTemplate + BuildVersion + 2 + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + ProjectName + SerenityBookTemplate + SourceVersion + 25000000000000 + + diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Headers/PlaygroundSupport.h b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Headers/PlaygroundSupport.h new file mode 100644 index 0000000..39b1b32 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Headers/PlaygroundSupport.h @@ -0,0 +1,17 @@ +//===--- PlaygroundSupport.h ----------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This header is intentionally left blank. +// It exists for the aid of code completion, which currently can only complete +// Clang modules. +// +//===----------------------------------------------------------------------===// diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Info.plist b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Info.plist new file mode 100644 index 0000000..426557d Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Info.plist differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios.swiftdoc b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios.swiftdoc new file mode 100644 index 0000000..201969d Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios.swiftmodule b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios.swiftmodule new file mode 100644 index 0000000..f7ccaa9 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64.swiftdoc b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64.swiftdoc new file mode 100644 index 0000000..201969d Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64.swiftmodule b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64.swiftmodule new file mode 100644 index 0000000..f7ccaa9 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Modules/module.modulemap b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Modules/module.modulemap new file mode 100644 index 0000000..e68b6e3 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module PlaygroundSupport { + umbrella header "PlaygroundSupport.h" + + export * + module * { export * } +} diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/PlaygroundSupport b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/PlaygroundSupport new file mode 100755 index 0000000..ab531c6 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/PlaygroundSupport differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/_CodeSignature/CodeResources b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/_CodeSignature/CodeResources new file mode 100644 index 0000000..807fc3c --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/_CodeSignature/CodeResources @@ -0,0 +1,207 @@ + + + + + files + + Headers/PlaygroundSupport.h + + 2SwypGm3tb652fC4juUmhfbphuI= + + Info.plist + + I0Rn31K4APNPKtXwgP2bGHPm7Sk= + + Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios.swiftdoc + + 8ditQhp5lLlDXBVdCl7uzBI3Ho4= + + Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios.swiftmodule + + 5NQ9jab2imc6gR10q7u+wOwAupA= + + Modules/PlaygroundSupport.swiftmodule/arm64.swiftdoc + + 8ditQhp5lLlDXBVdCl7uzBI3Ho4= + + Modules/PlaygroundSupport.swiftmodule/arm64.swiftmodule + + 5NQ9jab2imc6gR10q7u+wOwAupA= + + Modules/module.modulemap + + naQq1dmrWEVVlq+/T3hlDhMKT4Y= + + version.plist + + hbb3KZd2ZL3RvqiXyhFKSIkudk0= + + + files2 + + Headers/PlaygroundSupport.h + + hash + + 2SwypGm3tb652fC4juUmhfbphuI= + + hash2 + + BI1I38xyR02dzowCn/A+j24a00lB3p03Vw7tZc5Qylo= + + + Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios.swiftdoc + + hash + + 8ditQhp5lLlDXBVdCl7uzBI3Ho4= + + hash2 + + 8CEShohUhjeS004GrAEI9MJbzHrNmuuEtcSc9uazIHU= + + + Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios.swiftmodule + + hash + + 5NQ9jab2imc6gR10q7u+wOwAupA= + + hash2 + + X3HU+Yugi521Aao1mRJ2qh/mZmqHJIPoTjZoV2gGQcY= + + + Modules/PlaygroundSupport.swiftmodule/arm64.swiftdoc + + hash + + 8ditQhp5lLlDXBVdCl7uzBI3Ho4= + + hash2 + + 8CEShohUhjeS004GrAEI9MJbzHrNmuuEtcSc9uazIHU= + + + Modules/PlaygroundSupport.swiftmodule/arm64.swiftmodule + + hash + + 5NQ9jab2imc6gR10q7u+wOwAupA= + + hash2 + + X3HU+Yugi521Aao1mRJ2qh/mZmqHJIPoTjZoV2gGQcY= + + + Modules/module.modulemap + + hash + + naQq1dmrWEVVlq+/T3hlDhMKT4Y= + + hash2 + + 9OPknZ22wbFiKF2TyTByy43k3bQHOdYthbB+Gw+CykI= + + + version.plist + + hash + + hbb3KZd2ZL3RvqiXyhFKSIkudk0= + + hash2 + + 6Odj4rjUtxjlXHLzycE+O2A1o0hqjSMi3jrdXregQlo= + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/version.plist b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/version.plist new file mode 100644 index 0000000..d0ec3e1 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/iphoneos/PlaygroundSupport.framework/version.plist @@ -0,0 +1,18 @@ + + + + + BuildAliasOf + SerenityBookTemplate + BuildVersion + 2 + CFBundleShortVersionString + 1.0 + CFBundleVersion + 59.1 + ProjectName + SerenityBookTemplate + SourceVersion + 25000000000000 + + diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Assets.car b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Assets.car new file mode 100644 index 0000000..a6cc029 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Assets.car differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Headers/PlaygroundBluetooth.h b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Headers/PlaygroundBluetooth.h new file mode 100644 index 0000000..caf3b65 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Headers/PlaygroundBluetooth.h @@ -0,0 +1,6 @@ +// +// PlaygroundBluetooth.h +// PlaygroundBluetooth +// +// Copyright © 2017 Apple Inc. All rights reserved. +// diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Info.plist b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Info.plist new file mode 100644 index 0000000..c42bece Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Info.plist differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-simulator.swiftdoc b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-simulator.swiftdoc new file mode 100644 index 0000000..2b8fbe3 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-simulator.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-simulator.swiftmodule b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-simulator.swiftmodule new file mode 100644 index 0000000..c2f199c Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-simulator.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftdoc b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftdoc new file mode 100644 index 0000000..2b8fbe3 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftmodule b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftmodule new file mode 100644 index 0000000..c2f199c Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-simulator.swiftdoc b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-simulator.swiftdoc new file mode 100644 index 0000000..68fbc5a Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-simulator.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-simulator.swiftmodule b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-simulator.swiftmodule new file mode 100644 index 0000000..87bf5e0 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-simulator.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftdoc b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftdoc new file mode 100644 index 0000000..68fbc5a Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftmodule b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftmodule new file mode 100644 index 0000000..87bf5e0 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/module.modulemap b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/module.modulemap new file mode 100644 index 0000000..9f53d45 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module PlaygroundBluetooth { + umbrella header "PlaygroundBluetooth.h" + + export * + module * { export * } +} diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/PlaygroundBluetooth b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/PlaygroundBluetooth new file mode 100755 index 0000000..ceb4ebd Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/PlaygroundBluetooth differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/_CodeSignature/CodeResources b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/_CodeSignature/CodeResources new file mode 100644 index 0000000..0b3d121 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/_CodeSignature/CodeResources @@ -0,0 +1,304 @@ + + + + + files + + Assets.car + + jTcAGgNDX3CvZ8YDAllSmkzOL8E= + + Headers/PlaygroundBluetooth.h + + b9Jq2RsaKy8FFfAfHWtmLfNOzbk= + + Info.plist + + dy8NULqC5iOiGU2Eb/wkHPGYlj4= + + Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-simulator.swiftdoc + + BUGYx5dgXR7kIrLYQG8emrO9khQ= + + Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-simulator.swiftmodule + + kxSTPfB5zhvdi+Zm0q0yZy0CBag= + + Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftdoc + + BUGYx5dgXR7kIrLYQG8emrO9khQ= + + Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftmodule + + kxSTPfB5zhvdi+Zm0q0yZy0CBag= + + Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-simulator.swiftdoc + + jQVMdQJUFBzd4ActRa1r1vnoKbE= + + Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-simulator.swiftmodule + + mwi5j1z5NTW8AOhmWol5gLCLu0c= + + Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftdoc + + jQVMdQJUFBzd4ActRa1r1vnoKbE= + + Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftmodule + + mwi5j1z5NTW8AOhmWol5gLCLu0c= + + Modules/module.modulemap + + GdvBNEmovuLN9v8ssg4TCp8HEt0= + + en.lproj/Localizable.strings + + hash + + uD1Rs4m418/14rMR9TxSI1PvUqY= + + optional + + + version.plist + + 1bIXGF0k3LOpygZsXfFrez/AGWw= + + + files2 + + Assets.car + + hash + + jTcAGgNDX3CvZ8YDAllSmkzOL8E= + + hash2 + + 5S8UoEVOrc4wYfjn8ulX5kCPDIIeSuTUa3H1rP9z77Q= + + + Headers/PlaygroundBluetooth.h + + hash + + b9Jq2RsaKy8FFfAfHWtmLfNOzbk= + + hash2 + + VDUOD3BofKO+uoA++HBb7jZVu7/GOv3WkB7av4+S7Sk= + + + Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-simulator.swiftdoc + + hash + + BUGYx5dgXR7kIrLYQG8emrO9khQ= + + hash2 + + ml45ZsuN3fFRr+T9MBMDDR8g9w06EVet6K39m1DvSFg= + + + Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-simulator.swiftmodule + + hash + + kxSTPfB5zhvdi+Zm0q0yZy0CBag= + + hash2 + + rw/FJezMfqGPDpmZxoVSBINNFDrYzX9/9Aul83iAyHI= + + + Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftdoc + + hash + + BUGYx5dgXR7kIrLYQG8emrO9khQ= + + hash2 + + ml45ZsuN3fFRr+T9MBMDDR8g9w06EVet6K39m1DvSFg= + + + Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftmodule + + hash + + kxSTPfB5zhvdi+Zm0q0yZy0CBag= + + hash2 + + rw/FJezMfqGPDpmZxoVSBINNFDrYzX9/9Aul83iAyHI= + + + Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-simulator.swiftdoc + + hash + + jQVMdQJUFBzd4ActRa1r1vnoKbE= + + hash2 + + CzI6Gm9YTPqEdy5bApjzl3mBDyPW/0wmhgNEn748Uso= + + + Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-simulator.swiftmodule + + hash + + mwi5j1z5NTW8AOhmWol5gLCLu0c= + + hash2 + + tQyvsE7sH64D3A4U1uZSyl+4R/garMqRXXG9bTvfdlE= + + + Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftdoc + + hash + + jQVMdQJUFBzd4ActRa1r1vnoKbE= + + hash2 + + CzI6Gm9YTPqEdy5bApjzl3mBDyPW/0wmhgNEn748Uso= + + + Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftmodule + + hash + + mwi5j1z5NTW8AOhmWol5gLCLu0c= + + hash2 + + tQyvsE7sH64D3A4U1uZSyl+4R/garMqRXXG9bTvfdlE= + + + Modules/module.modulemap + + hash + + GdvBNEmovuLN9v8ssg4TCp8HEt0= + + hash2 + + KDH6BlM0dvMkACFfCQRFXvI+E0o9C3s3ONGwS5qgFpU= + + + en.lproj/Localizable.strings + + hash + + uD1Rs4m418/14rMR9TxSI1PvUqY= + + hash2 + + vm73WMbhgAHzrp6YlQ2VE0vlaSHMcIB5o8w7MsIhRuQ= + + optional + + + version.plist + + hash + + 1bIXGF0k3LOpygZsXfFrez/AGWw= + + hash2 + + kpzFRE1qXcps/GI1x8Dr1o+ldNZ68lkRVJgTZ31DDH4= + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/en.lproj/Localizable.strings b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/en.lproj/Localizable.strings new file mode 100644 index 0000000..3fd528e Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/en.lproj/Localizable.strings differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/version.plist b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/version.plist new file mode 100644 index 0000000..239ac4d --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundBluetooth.framework/version.plist @@ -0,0 +1,18 @@ + + + + + BuildAliasOf + SerenityBookTemplate + BuildVersion + 2 + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + ProjectName + SerenityBookTemplate + SourceVersion + 25000000000000 + + diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Headers/PlaygroundSupport.h b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Headers/PlaygroundSupport.h new file mode 100644 index 0000000..39b1b32 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Headers/PlaygroundSupport.h @@ -0,0 +1,17 @@ +//===--- PlaygroundSupport.h ----------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This header is intentionally left blank. +// It exists for the aid of code completion, which currently can only complete +// Clang modules. +// +//===----------------------------------------------------------------------===// diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Info.plist b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Info.plist new file mode 100644 index 0000000..7ca56b7 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Info.plist differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-simulator.swiftdoc b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-simulator.swiftdoc new file mode 100644 index 0000000..ac47bdd Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-simulator.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-simulator.swiftmodule b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-simulator.swiftmodule new file mode 100644 index 0000000..fffdd44 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-simulator.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64.swiftdoc b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64.swiftdoc new file mode 100644 index 0000000..ac47bdd Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64.swiftmodule b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64.swiftmodule new file mode 100644 index 0000000..fffdd44 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/arm64.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-simulator.swiftdoc b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-simulator.swiftdoc new file mode 100644 index 0000000..bc7e72d Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-simulator.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-simulator.swiftmodule b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-simulator.swiftmodule new file mode 100644 index 0000000..3de607b Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-simulator.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/x86_64.swiftdoc b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/x86_64.swiftdoc new file mode 100644 index 0000000..bc7e72d Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/x86_64.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/x86_64.swiftmodule b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/x86_64.swiftmodule new file mode 100644 index 0000000..3de607b Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/PlaygroundSupport.swiftmodule/x86_64.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/module.modulemap b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/module.modulemap new file mode 100644 index 0000000..e68b6e3 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module PlaygroundSupport { + umbrella header "PlaygroundSupport.h" + + export * + module * { export * } +} diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/PlaygroundSupport b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/PlaygroundSupport new file mode 100755 index 0000000..5a22843 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/PlaygroundSupport differ diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/_CodeSignature/CodeResources b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/_CodeSignature/CodeResources new file mode 100644 index 0000000..e61cf06 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/_CodeSignature/CodeResources @@ -0,0 +1,267 @@ + + + + + files + + Headers/PlaygroundSupport.h + + 2SwypGm3tb652fC4juUmhfbphuI= + + Info.plist + + KAnKBvZF/MTD1nxtagCERaY+xIU= + + Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-simulator.swiftdoc + + u8uwDuXN4A6A3vtPVt+V9aef/Zw= + + Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-simulator.swiftmodule + + j1XV1jXMnCpgfMIsAUbNshYZ0Js= + + Modules/PlaygroundSupport.swiftmodule/arm64.swiftdoc + + u8uwDuXN4A6A3vtPVt+V9aef/Zw= + + Modules/PlaygroundSupport.swiftmodule/arm64.swiftmodule + + j1XV1jXMnCpgfMIsAUbNshYZ0Js= + + Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-simulator.swiftdoc + + 0CYCD9yZ2YegpqDJXvDtKkVEb3U= + + Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-simulator.swiftmodule + + IBahxh3wlYucG0WdcL5Nub76fl8= + + Modules/PlaygroundSupport.swiftmodule/x86_64.swiftdoc + + 0CYCD9yZ2YegpqDJXvDtKkVEb3U= + + Modules/PlaygroundSupport.swiftmodule/x86_64.swiftmodule + + IBahxh3wlYucG0WdcL5Nub76fl8= + + Modules/module.modulemap + + naQq1dmrWEVVlq+/T3hlDhMKT4Y= + + version.plist + + hbb3KZd2ZL3RvqiXyhFKSIkudk0= + + + files2 + + Headers/PlaygroundSupport.h + + hash + + 2SwypGm3tb652fC4juUmhfbphuI= + + hash2 + + BI1I38xyR02dzowCn/A+j24a00lB3p03Vw7tZc5Qylo= + + + Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-simulator.swiftdoc + + hash + + u8uwDuXN4A6A3vtPVt+V9aef/Zw= + + hash2 + + 6vWw390goucAZWgqUcGWXAnScP/y9Q/T9YzyOqC51gA= + + + Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-simulator.swiftmodule + + hash + + j1XV1jXMnCpgfMIsAUbNshYZ0Js= + + hash2 + + hiW/iAtQxUDhcEsH9Sy8ZbogY4me7LED4Ylf4342juI= + + + Modules/PlaygroundSupport.swiftmodule/arm64.swiftdoc + + hash + + u8uwDuXN4A6A3vtPVt+V9aef/Zw= + + hash2 + + 6vWw390goucAZWgqUcGWXAnScP/y9Q/T9YzyOqC51gA= + + + Modules/PlaygroundSupport.swiftmodule/arm64.swiftmodule + + hash + + j1XV1jXMnCpgfMIsAUbNshYZ0Js= + + hash2 + + hiW/iAtQxUDhcEsH9Sy8ZbogY4me7LED4Ylf4342juI= + + + Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-simulator.swiftdoc + + hash + + 0CYCD9yZ2YegpqDJXvDtKkVEb3U= + + hash2 + + UG4k3gLjFKcFk6Zm+H6NTXaDj5Zh1lNUb8lRTdS8ghY= + + + Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-simulator.swiftmodule + + hash + + IBahxh3wlYucG0WdcL5Nub76fl8= + + hash2 + + 60nCC7GLglySAMXgJhrif/SCVm8Vk2DwU4R7kCgB1DU= + + + Modules/PlaygroundSupport.swiftmodule/x86_64.swiftdoc + + hash + + 0CYCD9yZ2YegpqDJXvDtKkVEb3U= + + hash2 + + UG4k3gLjFKcFk6Zm+H6NTXaDj5Zh1lNUb8lRTdS8ghY= + + + Modules/PlaygroundSupport.swiftmodule/x86_64.swiftmodule + + hash + + IBahxh3wlYucG0WdcL5Nub76fl8= + + hash2 + + 60nCC7GLglySAMXgJhrif/SCVm8Vk2DwU4R7kCgB1DU= + + + Modules/module.modulemap + + hash + + naQq1dmrWEVVlq+/T3hlDhMKT4Y= + + hash2 + + 9OPknZ22wbFiKF2TyTByy43k3bQHOdYthbB+Gw+CykI= + + + version.plist + + hash + + hbb3KZd2ZL3RvqiXyhFKSIkudk0= + + hash2 + + 6Odj4rjUtxjlXHLzycE+O2A1o0hqjSMi3jrdXregQlo= + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/version.plist b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/version.plist new file mode 100644 index 0000000..d0ec3e1 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/iphonesimulator/PlaygroundSupport.framework/version.plist @@ -0,0 +1,18 @@ + + + + + BuildAliasOf + SerenityBookTemplate + BuildVersion + 2 + CFBundleShortVersionString + 1.0 + CFBundleVersion + 59.1 + ProjectName + SerenityBookTemplate + SourceVersion + 25000000000000 + + diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Headers b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Headers new file mode 120000 index 0000000..a177d2a --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Headers @@ -0,0 +1 @@ +Versions/Current/Headers \ No newline at end of file diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Modules b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Modules new file mode 120000 index 0000000..5736f31 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Modules @@ -0,0 +1 @@ +Versions/Current/Modules \ No newline at end of file diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/PlaygroundBluetooth b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/PlaygroundBluetooth new file mode 120000 index 0000000..e076ef3 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/PlaygroundBluetooth @@ -0,0 +1 @@ +Versions/Current/PlaygroundBluetooth \ No newline at end of file diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Resources b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Resources new file mode 120000 index 0000000..953ee36 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Resources @@ -0,0 +1 @@ +Versions/Current/Resources \ No newline at end of file diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Headers/PlaygroundBluetooth.h b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Headers/PlaygroundBluetooth.h new file mode 100644 index 0000000..caf3b65 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Headers/PlaygroundBluetooth.h @@ -0,0 +1,6 @@ +// +// PlaygroundBluetooth.h +// PlaygroundBluetooth +// +// Copyright © 2017 Apple Inc. All rights reserved. +// diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-macabi.swiftdoc b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-macabi.swiftdoc new file mode 100644 index 0000000..defca34 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-macabi.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-macabi.swiftmodule b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-macabi.swiftmodule new file mode 100644 index 0000000..50868c2 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-macabi.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftdoc b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftdoc new file mode 100644 index 0000000..defca34 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftmodule b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftmodule new file mode 100644 index 0000000..50868c2 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-macabi.swiftdoc b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-macabi.swiftdoc new file mode 100644 index 0000000..8cf75e8 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-macabi.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-macabi.swiftmodule b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-macabi.swiftmodule new file mode 100644 index 0000000..93b26dd Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-macabi.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftdoc b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftdoc new file mode 100644 index 0000000..8cf75e8 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftmodule b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftmodule new file mode 100644 index 0000000..93b26dd Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/module.modulemap b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/module.modulemap new file mode 100644 index 0000000..9f53d45 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module PlaygroundBluetooth { + umbrella header "PlaygroundBluetooth.h" + + export * + module * { export * } +} diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/PlaygroundBluetooth b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/PlaygroundBluetooth new file mode 100755 index 0000000..50799d1 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/PlaygroundBluetooth differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Resources/Assets.car b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Resources/Assets.car new file mode 100644 index 0000000..473c67c Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Resources/Assets.car differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Resources/Info.plist b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Resources/Info.plist new file mode 100644 index 0000000..8ac0ab3 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Resources/Info.plist differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Resources/en.lproj/Localizable.strings b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Resources/en.lproj/Localizable.strings new file mode 100644 index 0000000..3fd528e Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Resources/en.lproj/Localizable.strings differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Resources/version.plist b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Resources/version.plist new file mode 100644 index 0000000..239ac4d --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/Resources/version.plist @@ -0,0 +1,18 @@ + + + + + BuildAliasOf + SerenityBookTemplate + BuildVersion + 2 + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + ProjectName + SerenityBookTemplate + SourceVersion + 25000000000000 + + diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/_CodeSignature/CodeResources b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/_CodeSignature/CodeResources new file mode 100644 index 0000000..ab456de --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/A/_CodeSignature/CodeResources @@ -0,0 +1,238 @@ + + + + + files + + Resources/Assets.car + + xZ6hLXvXuf8/3Nl3570xomzn/qc= + + Resources/Info.plist + + o6jOqlOaACHJhZvWjWyoS6NEnAo= + + Resources/en.lproj/Localizable.strings + + hash + + uD1Rs4m418/14rMR9TxSI1PvUqY= + + optional + + + Resources/version.plist + + 1bIXGF0k3LOpygZsXfFrez/AGWw= + + + files2 + + Headers/PlaygroundBluetooth.h + + hash2 + + VDUOD3BofKO+uoA++HBb7jZVu7/GOv3WkB7av4+S7Sk= + + + Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-macabi.swiftdoc + + hash2 + + 2wyp04IP96ooI+JGbALQ1QylDImajjo6eNPRFPXJ1Vo= + + + Modules/PlaygroundBluetooth.swiftmodule/arm64-apple-ios-macabi.swiftmodule + + hash2 + + A2KopREyz0kkzN70Tzby4CwKCDSyaF5TwMks+rirl8Q= + + + Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftdoc + + hash2 + + 2wyp04IP96ooI+JGbALQ1QylDImajjo6eNPRFPXJ1Vo= + + + Modules/PlaygroundBluetooth.swiftmodule/arm64.swiftmodule + + hash2 + + A2KopREyz0kkzN70Tzby4CwKCDSyaF5TwMks+rirl8Q= + + + Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-macabi.swiftdoc + + hash2 + + ILjAwtzRvyykz0xn4vwG17iSw18AL8fUcrn0YbBdZzI= + + + Modules/PlaygroundBluetooth.swiftmodule/x86_64-apple-ios-macabi.swiftmodule + + hash2 + + HalNma/yktm7IDGn0Z2jTexS9Q47btHlwRIQGNloDB8= + + + Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftdoc + + hash2 + + ILjAwtzRvyykz0xn4vwG17iSw18AL8fUcrn0YbBdZzI= + + + Modules/PlaygroundBluetooth.swiftmodule/x86_64.swiftmodule + + hash2 + + HalNma/yktm7IDGn0Z2jTexS9Q47btHlwRIQGNloDB8= + + + Modules/module.modulemap + + hash2 + + KDH6BlM0dvMkACFfCQRFXvI+E0o9C3s3ONGwS5qgFpU= + + + Resources/Assets.car + + hash2 + + Xc0JsA1RlBe+CRq5/8T6cOJmkgnRBy0ARGF/5Bm1re0= + + + Resources/Info.plist + + hash2 + + QsvmrwrfvsIbZ0DGsOFCTOuxaScTV3b3/iyka0arG74= + + + Resources/en.lproj/Localizable.strings + + hash2 + + vm73WMbhgAHzrp6YlQ2VE0vlaSHMcIB5o8w7MsIhRuQ= + + optional + + + Resources/version.plist + + hash2 + + kpzFRE1qXcps/GI1x8Dr1o+ldNZ68lkRVJgTZ31DDH4= + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/Current b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/Current new file mode 120000 index 0000000..8c7e5a6 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundBluetooth.framework/Versions/Current @@ -0,0 +1 @@ +A \ No newline at end of file diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Headers b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Headers new file mode 120000 index 0000000..a177d2a --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Headers @@ -0,0 +1 @@ +Versions/Current/Headers \ No newline at end of file diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Modules b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Modules new file mode 120000 index 0000000..5736f31 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Modules @@ -0,0 +1 @@ +Versions/Current/Modules \ No newline at end of file diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/PlaygroundSupport b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/PlaygroundSupport new file mode 120000 index 0000000..b89634f --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/PlaygroundSupport @@ -0,0 +1 @@ +Versions/Current/PlaygroundSupport \ No newline at end of file diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Resources b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Resources new file mode 120000 index 0000000..953ee36 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Resources @@ -0,0 +1 @@ +Versions/Current/Resources \ No newline at end of file diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Headers/PlaygroundSupport.h b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Headers/PlaygroundSupport.h new file mode 100644 index 0000000..39b1b32 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Headers/PlaygroundSupport.h @@ -0,0 +1,17 @@ +//===--- PlaygroundSupport.h ----------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This header is intentionally left blank. +// It exists for the aid of code completion, which currently can only complete +// Clang modules. +// +//===----------------------------------------------------------------------===// diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-macabi.swiftdoc b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-macabi.swiftdoc new file mode 100644 index 0000000..45d35a0 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-macabi.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-macabi.swiftmodule b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-macabi.swiftmodule new file mode 100644 index 0000000..04f21a2 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-macabi.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/arm64.swiftdoc b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/arm64.swiftdoc new file mode 100644 index 0000000..45d35a0 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/arm64.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/arm64.swiftmodule b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/arm64.swiftmodule new file mode 100644 index 0000000..04f21a2 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/arm64.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-macabi.swiftdoc b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-macabi.swiftdoc new file mode 100644 index 0000000..b3cd0a6 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-macabi.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-macabi.swiftmodule b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-macabi.swiftmodule new file mode 100644 index 0000000..704c1f6 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-macabi.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/x86_64.swiftdoc b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/x86_64.swiftdoc new file mode 100644 index 0000000..b3cd0a6 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/x86_64.swiftdoc differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/x86_64.swiftmodule b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/x86_64.swiftmodule new file mode 100644 index 0000000..704c1f6 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/PlaygroundSupport.swiftmodule/x86_64.swiftmodule differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/module.modulemap b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/module.modulemap new file mode 100644 index 0000000..e68b6e3 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module PlaygroundSupport { + umbrella header "PlaygroundSupport.h" + + export * + module * { export * } +} diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/PlaygroundSupport b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/PlaygroundSupport new file mode 100755 index 0000000..9aa248d Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/PlaygroundSupport differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Resources/Info.plist b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Resources/Info.plist new file mode 100644 index 0000000..6d90765 Binary files /dev/null and b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Resources/Info.plist differ diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Resources/version.plist b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Resources/version.plist new file mode 100644 index 0000000..d0ec3e1 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/Resources/version.plist @@ -0,0 +1,18 @@ + + + + + BuildAliasOf + SerenityBookTemplate + BuildVersion + 2 + CFBundleShortVersionString + 1.0 + CFBundleVersion + 59.1 + ProjectName + SerenityBookTemplate + SourceVersion + 25000000000000 + + diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/_CodeSignature/CodeResources b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/_CodeSignature/CodeResources new file mode 100644 index 0000000..0b9fe08 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/A/_CodeSignature/CodeResources @@ -0,0 +1,209 @@ + + + + + files + + Resources/Info.plist + + 9HgpeaOpfeViGr/LNcakznu8oak= + + Resources/version.plist + + hbb3KZd2ZL3RvqiXyhFKSIkudk0= + + + files2 + + Headers/PlaygroundSupport.h + + hash2 + + BI1I38xyR02dzowCn/A+j24a00lB3p03Vw7tZc5Qylo= + + + Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-macabi.swiftdoc + + hash2 + + mS4V7gmPmCiRw65/0yoMXcq8QJxc88QKb7zOMNVTF6M= + + + Modules/PlaygroundSupport.swiftmodule/arm64-apple-ios-macabi.swiftmodule + + hash2 + + X0PBUlNCGmWTx6/02n4ELVNTVzEjYh/FQ4zClSq82yI= + + + Modules/PlaygroundSupport.swiftmodule/arm64.swiftdoc + + hash2 + + mS4V7gmPmCiRw65/0yoMXcq8QJxc88QKb7zOMNVTF6M= + + + Modules/PlaygroundSupport.swiftmodule/arm64.swiftmodule + + hash2 + + X0PBUlNCGmWTx6/02n4ELVNTVzEjYh/FQ4zClSq82yI= + + + Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-macabi.swiftdoc + + hash2 + + /tXbGSZ8BDv8PqEj4P5U/fduAxgVBnVK83mfCZ4hpsQ= + + + Modules/PlaygroundSupport.swiftmodule/x86_64-apple-ios-macabi.swiftmodule + + hash2 + + OO+xZ4FzzkkhEuMyz4+lvSI7NWo4f0i7S05sZy6monU= + + + Modules/PlaygroundSupport.swiftmodule/x86_64.swiftdoc + + hash2 + + /tXbGSZ8BDv8PqEj4P5U/fduAxgVBnVK83mfCZ4hpsQ= + + + Modules/PlaygroundSupport.swiftmodule/x86_64.swiftmodule + + hash2 + + OO+xZ4FzzkkhEuMyz4+lvSI7NWo4f0i7S05sZy6monU= + + + Modules/module.modulemap + + hash2 + + 9OPknZ22wbFiKF2TyTByy43k3bQHOdYthbB+Gw+CykI= + + + Resources/Info.plist + + hash2 + + HtYWgKjSUJT7jLziSK1DEZZLEe1tWTt1ySUZBsyoyec= + + + Resources/version.plist + + hash2 + + 6Odj4rjUtxjlXHLzycE+O2A1o0hqjSMi3jrdXregQlo= + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/Current b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/Current new file mode 120000 index 0000000..8c7e5a6 --- /dev/null +++ b/SupportingContent/PlaygroundsFrameworks/maccatalyst/PlaygroundSupport.framework/Versions/Current @@ -0,0 +1 @@ +A \ No newline at end of file diff --git a/SupportingContent/Tools/expandBuildSettingReferences b/SupportingContent/Tools/expandBuildSettingReferences new file mode 100755 index 0000000..6c0da8e Binary files /dev/null and b/SupportingContent/Tools/expandBuildSettingReferences differ