3 | * ---------------------------------------------------------------
4 | *
5 | * #ade5fc
6 | * #a2fca2
7 | * #c6b4f0
8 | * #d36363
9 | * #fcc28c
10 | * #fc9b9b
11 | * #ffa
12 | * #fff
13 | * #333
14 | * #62c8f3
15 | * #888
16 | *
17 | */.hljs{display:block;overflow-x:auto;padding:.5em;background:#333;color:#fff}.hljs-name,.hljs-strong{font-weight:700}.hljs-code,.hljs-emphasis{font-style:italic}.hljs-tag{color:#62c8f3}.hljs-selector-class,.hljs-selector-id,.hljs-template-variable,.hljs-variable{color:#ade5fc}.hljs-bullet,.hljs-string{color:#a2fca2}.hljs-attribute,.hljs-built_in,.hljs-builtin-name,.hljs-quote,.hljs-section,.hljs-title,.hljs-type{color:#ffa}.hljs-bullet,.hljs-number,.hljs-symbol{color:#d36363}.hljs-keyword,.hljs-literal,.hljs-selector-tag{color:#fcc28c}.hljs-code,.hljs-comment,.hljs-deletion{color:#888}.hljs-link,.hljs-regexp{color:#c6b4f0}.hljs-meta{color:#fc9b9b}.hljs-deletion{background-color:#fc9b9b;color:#333}.hljs-addition{background-color:#a2fca2;color:#333}.hljs a{color:inherit}.hljs a:focus,.hljs a:hover{color:inherit;text-decoration:underline}
--------------------------------------------------------------------------------
/Sources/Assets/styles/vulcan.css:
--------------------------------------------------------------------------------
1 | /*!
2 | Theme: vulcan
3 | Author: Andrey Varfolomeev
4 | License: ~ MIT (or more permissive) [via base16-schemes-source]
5 | Maintainer: @highlightjs/core-team
6 | Version: 2021.05.0
7 | */pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#5b778c;background:#041523}.hljs ::selection{color:#003552}.hljs-comment{color:#7a5759}.hljs-tag{color:#6b6977}.hljs-operator,.hljs-punctuation,.hljs-subst{color:#5b778c}.hljs-operator{opacity:.7}.hljs-bullet,.hljs-deletion,.hljs-name,.hljs-selector-tag,.hljs-template-variable,.hljs-variable{color:#818591}.hljs-attr,.hljs-link,.hljs-literal,.hljs-number,.hljs-symbol,.hljs-variable.constant_{color:#9198a3}.hljs-class .hljs-title,.hljs-title,.hljs-title.class_{color:#adb4b9}.hljs-strong{font-weight:700;color:#adb4b9}.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-code,.hljs-doctag,.hljs-function .hljs-title,.hljs-keyword.hljs-atrule,.hljs-quote,.hljs-regexp,.hljs-section,.hljs-string,.hljs-title.class_.inherited__,.hljs-title.function_,.ruby .hljs-property{color:#977d7c}.diff .hljs-meta,.hljs-keyword,.hljs-template-tag,.hljs-type{color:#9198a3}.hljs-emphasis{color:#9198a3;font-style:italic}.hljs-meta,.hljs-meta .hljs-keyword,.hljs-meta .hljs-string{color:#977d7c}.hljs-meta .hljs-keyword,.hljs-meta-keyword{font-weight:700}
--------------------------------------------------------------------------------
/Sources/Assets/styles/xcode-dusk.css:
--------------------------------------------------------------------------------
1 | /*!
2 | Theme: XCode Dusk
3 | Author: Elsa Gonsiorowski (https://github.com/gonsie)
4 | License: ~ MIT (or more permissive) [via base16-schemes-source]
5 | Maintainer: @highlightjs/core-team
6 | Version: 2021.05.0
7 | */
8 | .hljs{display:block;overflow-x:auto;padding:1em;color:#939599;background:#282b35}.xml .hljs-meta{color:#939599;}.hljs-comment{color:#686a71}.hljs-tag{color:#7e8086}.hljs-operator,.hljs-punctuation,.hljs-subst{color:#939599}.hljs-operator{opacity:.7}.hljs-bullet,.hljs-deletion,.hljs-name,.hljs-selector-tag,.hljs-template-variable,.hljs-variable{color:#b21889}.hljs-attr,.hljs-link,.hljs-literal,.hljs-number,.hljs-symbol,.hljs-variable.constant_{color:#786dc5}.hljs-class .hljs-title,.hljs-title,.hljs-title.class_{color:#438288}.hljs-strong{font-weight:700;color:#438288}.hljs-addition,.hljs-code,.hljs-string,.hljs-title.class_.inherited__{color:#df0002}.hljs-built_in,.hljs-doctag,.hljs-keyword.hljs-atrule,.hljs-quote,.hljs-regexp{color:#00a0be}.hljs-attribute,.hljs-function .hljs-title,.hljs-section,.hljs-title.function_,.ruby .hljs-property{color:#790ead}.diff .hljs-meta,.hljs-keyword,.hljs-template-tag,.hljs-type{color:#b21889}.hljs-emphasis{color:#b21889;font-style:italic}.hljs-meta,.hljs-meta .hljs-keyword,.hljs-meta .hljs-string{color:#c77c48}.hljs-meta .hljs-keyword,.hljs-meta-keyword{font-weight:700}
--------------------------------------------------------------------------------
/Sources/Assets/styles/silk-dark.css:
--------------------------------------------------------------------------------
1 | /*!
2 | Theme: Silk Dark
3 | Author: Gabriel Fontes (https://github.com/Misterio77)
4 | License: ~ MIT (or more permissive) [via base16-schemes-source]
5 | Maintainer: @highlightjs/core-team
6 | Version: 2021.05.0
7 | */pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#c7dbdd;background:#0e3c46}.hljs ::selection{color:#2a5054}.hljs-comment{color:#587073}.hljs-tag{color:#9dc8cd}.hljs-operator,.hljs-punctuation,.hljs-subst{color:#c7dbdd}.hljs-operator{opacity:.7}.hljs-bullet,.hljs-deletion,.hljs-name,.hljs-selector-tag,.hljs-template-variable,.hljs-variable{color:#fb6953}.hljs-attr,.hljs-link,.hljs-literal,.hljs-number,.hljs-symbol,.hljs-variable.constant_{color:#fcab74}.hljs-class .hljs-title,.hljs-title,.hljs-title.class_{color:#fce380}.hljs-strong{font-weight:700;color:#fce380}.hljs-addition,.hljs-code,.hljs-string,.hljs-title.class_.inherited__{color:#73d8ad}.hljs-built_in,.hljs-doctag,.hljs-keyword.hljs-atrule,.hljs-quote,.hljs-regexp{color:#3fb2b9}.hljs-attribute,.hljs-function .hljs-title,.hljs-section,.hljs-title.function_,.ruby .hljs-property{color:#46bddd}.diff .hljs-meta,.hljs-keyword,.hljs-template-tag,.hljs-type{color:#756b8a}.hljs-emphasis{color:#756b8a;font-style:italic}.hljs-meta,.hljs-meta .hljs-keyword,.hljs-meta .hljs-string{color:#9b647b}.hljs-meta .hljs-keyword,.hljs-meta-keyword{font-weight:700}
--------------------------------------------------------------------------------
/Sources/Assets/styles/silk-light.css:
--------------------------------------------------------------------------------
1 | /*!
2 | Theme: Silk Light
3 | Author: Gabriel Fontes (https://github.com/Misterio77)
4 | License: ~ MIT (or more permissive) [via base16-schemes-source]
5 | Maintainer: @highlightjs/core-team
6 | Version: 2021.05.0
7 | */pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#385156;background:#e9f1ef}.hljs ::selection{color:#90b7b6}.hljs-comment{color:#5c787b}.hljs-tag{color:#4b5b5f}.hljs-operator,.hljs-punctuation,.hljs-subst{color:#385156}.hljs-operator{opacity:.7}.hljs-bullet,.hljs-deletion,.hljs-name,.hljs-selector-tag,.hljs-template-variable,.hljs-variable{color:#cf432e}.hljs-attr,.hljs-link,.hljs-literal,.hljs-number,.hljs-symbol,.hljs-variable.constant_{color:#d27f46}.hljs-class .hljs-title,.hljs-title,.hljs-title.class_{color:#cfad25}.hljs-strong{font-weight:700;color:#cfad25}.hljs-addition,.hljs-code,.hljs-string,.hljs-title.class_.inherited__{color:#6ca38c}.hljs-built_in,.hljs-doctag,.hljs-keyword.hljs-atrule,.hljs-quote,.hljs-regexp{color:#329ca2}.hljs-attribute,.hljs-function .hljs-title,.hljs-section,.hljs-title.function_,.ruby .hljs-property{color:#39aac9}.diff .hljs-meta,.hljs-keyword,.hljs-template-tag,.hljs-type{color:#6e6582}.hljs-emphasis{color:#6e6582;font-style:italic}.hljs-meta,.hljs-meta .hljs-keyword,.hljs-meta .hljs-string{color:#865369}.hljs-meta .hljs-keyword,.hljs-meta-keyword{font-weight:700}
--------------------------------------------------------------------------------
/Sources/Assets/styles/night-owl.css:
--------------------------------------------------------------------------------
1 | .hljs{display:block;overflow-x:auto;padding:.5em;background:#011627;color:#d6deeb}.hljs-keyword{color:#c792ea;font-style:italic}.hljs-built_in{color:#addb67;font-style:italic}.hljs-type{color:#82aaff}.hljs-literal{color:#ff5874}.hljs-number{color:#f78c6c}.hljs-regexp{color:#5ca7e4}.hljs-string{color:#ecc48d}.hljs-subst{color:#d3423e}.hljs-symbol{color:#82aaff}.hljs-class{color:#ffcb8b}.hljs-function{color:#82aaff}.hljs-title{color:#dcdcaa;font-style:italic}.hljs-params{color:#7fdbca}.hljs-comment{color:#637777;font-style:italic}.hljs-doctag{color:#7fdbca}.hljs-meta{color:#82aaff}.hljs-meta-keyword{color:#82aaff}.hljs-meta-string{color:#ecc48d}.hljs-section{color:#82b1ff}.hljs-builtin-name,.hljs-name,.hljs-tag{color:#7fdbca}.hljs-attr{color:#7fdbca}.hljs-attribute{color:#80cbc4}.hljs-variable{color:#addb67}.hljs-bullet{color:#d9f5dd}.hljs-code{color:#80cbc4}.hljs-emphasis{color:#c792ea;font-style:italic}.hljs-strong{color:#addb67;font-weight:700}.hljs-formula{color:#c792ea}.hljs-link{color:#ff869a}.hljs-quote{color:#697098;font-style:italic}.hljs-selector-tag{color:#ff6363}.hljs-selector-id{color:#fad430}.hljs-selector-class{color:#addb67;font-style:italic}.hljs-selector-attr,.hljs-selector-pseudo{color:#c792ea;font-style:italic}.hljs-template-tag{color:#c792ea}.hljs-template-variable{color:#addb67}.hljs-addition{color:#addb67ff;font-style:italic}.hljs-deletion{color:#ef535090;font-style:italic}
--------------------------------------------------------------------------------
/Sources/Assets/styles/snazzy.css:
--------------------------------------------------------------------------------
1 | /*!
2 | Theme: Snazzy
3 | Author: Chawye Hsu (https://github.com/chawyehsu) based on Hyper Snazzy Theme (https://github.com/sindresorhus/hyper-snazzy)
4 | License: ~ MIT (or more permissive) [via base16-schemes-source]
5 | Maintainer: @highlightjs/core-team
6 | Version: 2021.05.0
7 | */pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#e2e4e5;background:#282a36}.hljs ::selection{color:#43454f}.hljs-comment{color:#78787e}.hljs-tag{color:#a5a5a9}.hljs-operator,.hljs-punctuation,.hljs-subst{color:#e2e4e5}.hljs-operator{opacity:.7}.hljs-bullet,.hljs-deletion,.hljs-name,.hljs-selector-tag,.hljs-template-variable,.hljs-variable{color:#ff5c57}.hljs-attr,.hljs-link,.hljs-literal,.hljs-number,.hljs-symbol,.hljs-variable.constant_{color:#ff9f43}.hljs-class .hljs-title,.hljs-title,.hljs-title.class_{color:#f3f99d}.hljs-strong{font-weight:700;color:#f3f99d}.hljs-addition,.hljs-code,.hljs-string,.hljs-title.class_.inherited__{color:#5af78e}.hljs-built_in,.hljs-doctag,.hljs-keyword.hljs-atrule,.hljs-quote,.hljs-regexp{color:#9aedfe}.hljs-attribute,.hljs-function .hljs-title,.hljs-section,.hljs-title.function_,.ruby .hljs-property{color:#57c7ff}.diff .hljs-meta,.hljs-keyword,.hljs-template-tag,.hljs-type{color:#ff6ac1}.hljs-emphasis{color:#ff6ac1;font-style:italic}.hljs-meta,.hljs-meta .hljs-keyword,.hljs-meta .hljs-string{color:#b2643c}.hljs-meta .hljs-keyword,.hljs-meta-keyword{font-weight:700}
--------------------------------------------------------------------------------
/Sources/Assets/LICENCE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause Licence
2 |
3 | Copyright (c) 2006-23, Ivan Sagalaev.
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/Sources/Assets/styles/grayscale.css:
--------------------------------------------------------------------------------
1 | .hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#fff}.hljs-comment,.hljs-quote{color:#777;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number{color:#777}.hljs-doctag,.hljs-formula,.hljs-string{color:#333;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAJ0lEQVQIW2O8e/fufwYGBgZBQUEQxcCIIfDu3Tuwivfv30NUoAsAALHpFMMLqZlPAAAAAElFTkSuQmCC) repeat}.hljs-section,.hljs-selector-id,.hljs-title{color:#000;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-name,.hljs-type{color:#333;font-weight:700}.hljs-tag{color:#333}.hljs-regexp{color:#333;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAYAAADA+m62AAAAPUlEQVQYV2NkQAN37979r6yszIgujiIAU4RNMVwhuiQ6H6wQl3XI4oy4FMHcCJPHcDS6J2A2EqUQpJhohQDexSef15DBCwAAAABJRU5ErkJggg==) repeat}.hljs-bullet,.hljs-link,.hljs-symbol{color:#000;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAKElEQVQIW2NkQAO7d+/+z4gsBhJwdXVlhAvCBECKwIIwAbhKZBUwBQA6hBpm5efZsgAAAABJRU5ErkJggg==) repeat}.hljs-built_in,.hljs-builtin-name{color:#000;text-decoration:underline}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{color:#fff;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAADCAYAAABS3WWCAAAAE0lEQVQIW2MMDQ39zzhz5kwIAQAyxweWgUHd1AAAAABJRU5ErkJggg==) repeat}.hljs-addition{color:#000;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAALUlEQVQYV2N89+7dfwYk8P79ewZBQUFkIQZGOiu6e/cuiptQHAPl0NtNxAQBAM97Oejj3Dg7AAAAAElFTkSuQmCC) repeat}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
--------------------------------------------------------------------------------
/Sources/Assets/styles/nord.css:
--------------------------------------------------------------------------------
1 | .hljs{display:block;overflow-x:auto;padding:.5em;background:#2e3440}.hljs,.hljs-subst{color:#d8dee9}.hljs-selector-tag{color:#81a1c1}.hljs-selector-id{color:#8fbcbb;font-weight:700}.hljs-selector-class{color:#8fbcbb}.hljs-selector-attr{color:#8fbcbb}.hljs-selector-pseudo{color:#88c0d0}.hljs-addition{background-color:#A3BE8C80}.hljs-deletion{background-color:#BF616A80}.hljs-built_in,.hljs-type{color:#8fbcbb}.hljs-class{color:#8fbcbb}.hljs-function{color:#88c0d0}.hljs-function>.hljs-title{color:#88c0d0}.hljs-keyword,.hljs-literal,.hljs-symbol{color:#81a1c1}.hljs-number{color:#b48ead}.hljs-regexp{color:#ebcb8b}.hljs-string{color:#a3be8c}.hljs-title{color:#8fbcbb}.hljs-params{color:#d8dee9}.hljs-bullet{color:#81a1c1}.hljs-code{color:#8fbcbb}.hljs-emphasis{font-style:italic}.hljs-formula{color:#8fbcbb}.hljs-strong{font-weight:700}.hljs-link:hover{text-decoration:underline}.hljs-quote{color:#4c566a}.hljs-comment{color:#4c566a}.hljs-doctag{color:#8fbcbb}.hljs-meta,.hljs-meta-keyword{color:#5e81ac}.hljs-meta-string{color:#a3be8c}.hljs-attr{color:#8fbcbb}.hljs-attribute{color:#d8dee9}.hljs-builtin-name{color:#81a1c1}.hljs-name{color:#81a1c1}.hljs-section{color:#88c0d0}.hljs-tag{color:#81a1c1}.hljs-variable{color:#d8dee9}.hljs-template-variable{color:#d8dee9}.hljs-template-tag{color:#5e81ac}.abnf .hljs-attribute{color:#88c0d0}.abnf .hljs-symbol{color:#ebcb8b}.apache .hljs-attribute{color:#88c0d0}.apache .hljs-section{color:#81a1c1}.arduino .hljs-built_in{color:#88c0d0}.aspectj .hljs-meta{color:#d08770}.aspectj>.hljs-title{color:#88c0d0}.bnf .hljs-attribute{color:#8fbcbb}.clojure .hljs-name{color:#88c0d0}.clojure .hljs-symbol{color:#ebcb8b}.coq .hljs-built_in{color:#88c0d0}.cpp .hljs-meta-string{color:#8fbcbb}.css .hljs-built_in{color:#88c0d0}.css .hljs-keyword{color:#d08770}.diff .hljs-meta{color:#8fbcbb}.ebnf .hljs-attribute{color:#8fbcbb}.glsl .hljs-built_in{color:#88c0d0}.groovy .hljs-meta:not(:first-child){color:#d08770}.haxe .hljs-meta{color:#d08770}.java .hljs-meta{color:#d08770}.ldif .hljs-attribute{color:#8fbcbb}.lisp .hljs-name{color:#88c0d0}.lua .hljs-built_in{color:#88c0d0}.moonscript .hljs-built_in{color:#88c0d0}.nginx .hljs-attribute{color:#88c0d0}.nginx .hljs-section{color:#5e81ac}.pf .hljs-built_in{color:#88c0d0}.processing .hljs-built_in{color:#88c0d0}.scss .hljs-keyword{color:#81a1c1}.stylus .hljs-keyword{color:#81a1c1}.swift .hljs-meta{color:#d08770}.vim .hljs-built_in{color:#88c0d0;font-style:italic}.yaml .hljs-meta{color:#d08770}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
92 | .swiftpm/
93 |
--------------------------------------------------------------------------------
/LICENCE.md:
--------------------------------------------------------------------------------
1 | # HighighterSwift
2 |
3 | Copyright © 2023, Tony Smith (@smittytone) — [MIT Licence](#mid-licence)
4 | Portions copyright © 2016, Juan Pablo Illanes — [MIT Licence](#mid-licence)
5 | Portions copyright © 2006-23, Ivan Sagalaev and Contributors — [BSD 3-Clause Licence](#bsd-3-clause-licence)
6 |
7 | ---
8 |
9 | ### MIT Licence
10 |
11 | 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:
12 |
13 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14 |
15 | 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.
16 |
17 | ---
18 |
19 | ### BSD 3-Clause Licence
20 |
21 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
22 |
23 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
24 |
25 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
26 |
27 | * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
28 |
29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/Tests/HighlighterTests/HighlighterTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import Highlighter
3 |
4 | final class HighlighterTests: XCTestCase {
5 |
6 | var hr: Highlighter? = nil
7 |
8 |
9 | override func setUp() {
10 |
11 | self.hr = Highlighter.init()
12 | XCTAssert(hr != nil)
13 | }
14 |
15 |
16 | func testBadLanguage() {
17 |
18 | // Test trapping of a bad language name
19 |
20 | let result: NSAttributedString? = self.hr!.highlight("", as: "fintlewoodlewix@1")
21 | XCTAssert(result == nil)
22 | }
23 |
24 |
25 | func testSetThemeBad() {
26 |
27 | // Test trapping of a bad theme name
28 |
29 | let result: Bool = self.hr!.setTheme("fintlewoodlewix@1")
30 | XCTAssert(!result)
31 | }
32 |
33 |
34 | func testSetThemeDefaultFont() {
35 |
36 | let _ = self.hr!.setTheme("agate")
37 | let font: NSFont = self.hr!.theme.codeFont
38 | XCTAssert(font.fontName == "Courier" && font.pointSize == 14.0)
39 | }
40 |
41 |
42 | func testAvailableThemes() {
43 |
44 | // Test Highlight.js contains at least one theme
45 |
46 | let result: [String] = self.hr!.availableThemes()
47 | XCTAssert(result.count > 0)
48 | }
49 |
50 |
51 | func testSupportedLanguages() {
52 |
53 | // Test Highlight.js supports at least one language
54 |
55 | let result: [String] = self.hr!.supportedLanguages()
56 | XCTAssert(result.count > 0)
57 | }
58 |
59 |
60 | func testColourFromHexStringGood() {
61 |
62 | // Test colour decoding -- all should be processed as valid colours
63 |
64 | // Six-digit RGB
65 | var result: NSColor = self.hr!.theme.colourFromHexString("#808000")
66 | XCTAssert(result.redComponent > 0.49 &&
67 | result.redComponent < 0.56 &&
68 | result.greenComponent > 0.49 &&
69 | result.greenComponent < 0.56 &&
70 | result.blueComponent == 0.0
71 | )
72 |
73 | // Three-digit RGB
74 | result = self.hr!.theme.colourFromHexString("#444")
75 | XCTAssert(result.redComponent > 0.2 &&
76 | result.redComponent < 0.29 &&
77 | result.blueComponent > 0.2 &&
78 | result.greenComponent < 0.29 &&
79 | result.blueComponent > 0.2 &&
80 | result.blueComponent < 0.29
81 | )
82 |
83 | // Eight-digit RGB + Alpha
84 | result = self.hr!.theme.colourFromHexString("#80800080")
85 | XCTAssert(result.alphaComponent > 0.49 &&
86 | result.alphaComponent < 0.56
87 | )
88 |
89 | // CSS entity
90 | result = self.hr!.theme.colourFromHexString("red")
91 | XCTAssert(result.redComponent == 1.0 &&
92 | result.blueComponent == 0.0 &&
93 | result.greenComponent == 0.0
94 | )
95 | }
96 |
97 |
98 | func testColourFromHexStringBad() {
99 |
100 | // Test colour decoding -- all should be trapped as bad colours
101 |
102 | // Unknown CSS entity
103 | var result = self.hr!.theme.colourFromHexString("olive")
104 | XCTAssert(result == NSColor.gray)
105 |
106 | // Bad hex value 1
107 | result = self.hr!.theme.colourFromHexString("#ZZZ")
108 | XCTAssert(result.redComponent == 0.0 &&
109 | result.blueComponent == 0.0 &&
110 | result.greenComponent == 0.0
111 | )
112 |
113 | // Bad hex value 2
114 | result = self.hr!.theme.colourFromHexString("#aaaaa")
115 | XCTAssert(result == NSColor.gray)
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HighlighterSwift 1.1.2
2 |
3 | This library provides a Swift wrapper for the popular [Highlight.js](https://highlightjs.org/) code highlighting utility.
4 |
5 | 
6 |
7 | It is a more up-to-date version of Juan Pablo Illanes’ [Highlightr](https://github.com/raspu/Highlightr) and relies heavily upon code from that project, which is unfortunately no longer fully maintained.
8 |
9 | ### Improvements and Changes
10 |
11 | Highlightr makes use of Highlight.js 9.13.4, but the most recent release of the JavaScript library in June 2021, when HighlighterSwift was developed, was version 11.0.1. The use of Highlight.js 9.x is no longer supported or recommended by the Hightlight.js team. Version 1.0.x of HighlighterSwift made use of the stable Highlight.js 10.7.3. Version 1.1.0 moves to Highlight.js 11.5.0.
12 |
13 | HighlighterSwift adds support for alpha values in CSS colours, eg. `#808080AA`, not present in Highlightr.
14 |
15 | Unlike Highlightr, HighlighterSwift parses Highlight.js themes for separate declarations of the same style. For example, Hybrid contains the following CSS:
16 |
17 | ```css
18 | .hljs{display:block;overflow-x:auto;padding:.5em;background:#1d1f21}.hljs span::selection,.hljs::selection{background:#373b41}.hljs{color:#c5c8c6}
19 | ```
20 |
21 | The `hljs.color` attribute is added to `hljs.display`, `hljs.overflow-x`, `hljs.padding` and `hljs.background`, it doesn’t replace them.
22 |
23 | HighlighterSwift was designed from the ground up as a Swift Package. Support for legacy package managers is not included. Highlightr supports CocoaPods and Carthage.
24 |
25 | HighlighterSwift is more deeply commented and the code is presented in a more consistent style.
26 |
27 | A number of functions have been given extra parameters, primarily to add font selection when setting themes and initiating Theme objects. Redundant code has been removed. Some parameters have been renamed.
28 |
29 | Unit tests have been added, and more will come, I hope.
30 |
31 | 
32 |
33 | #### Why not update Highlightr?
34 |
35 | HighlighterSwift was created to meet the needs of a specific project, which was originally conceived with a modified version Hightlightr in mind. Some of the changes listed above are breaking, and so I feel it’s not appropriate to just inflict them on the Hightlightr source, especially when there are many outstanding pull requests yet to be addressed. But I’m not opposed to pulling in my changes if the community requests that.
36 |
37 | HighlighterSwift is released under the same [licence](#licence) as Highlightr, allowing developers to select either, both or a mix of the two.
38 |
39 | ## Platform Support
40 |
41 | HighlighterSwift supports macOS 10.14 and up, and iOS 12 and up. iOS support is untested, however.
42 |
43 | ## Installation
44 |
45 | To add HighlighterSwift to your project, use Xcode to add it as a Swift Package at this repo’s URL. The library contains the Highlight.js code and themes.
46 |
47 | **Note*- This project was begun to support another, so some themes have been modified slightly to meet the needs of that other project. For example, background images have been removed from the Brown Paper, Greyscale, Schoolbook and Pojoacque themes (Highlight.js is also starting to do this); the two Kimbies have been renamed for consistency; colours have been formalised as hex values.
48 |
49 | ## Usage
50 |
51 | Instantiate a *Highlighter- object. Its *init()- function returns an optional, which will be `nil` if the `Highlight.min.js` could not be found or is non-functional, or the `Default` theme CSS file is missing:
52 |
53 | ```swift
54 | if let highlighter: Highlighter = Highlighter.init() {
55 | ...
56 | }
57 | ```
58 |
59 | You can set a specific theme using the *setTheme()- function:
60 |
61 | ```swift
62 | highlighter.setTheme("atom-one-light")
63 | ```
64 |
65 | You can apply your chosen font at this time too rather than fall back on the default, 14pt Courier:
66 |
67 | ```swift
68 | highlighter.setTheme("atom-one-light", withFont: "Menlo-Regular", ofSize: 16.0)
69 | ```
70 |
71 | You can set or change your preferred font later by using *setCodeFont()*, which takes an NSFont or UIFont instance configured for the font and text size you want, and is called on the Highlighter instance’s *theme- property:
72 |
73 | ```swift
74 | let font: NSFont = NSFont.init(name: "Menlo-Regular", size: 12.0)!
75 | highlighter.theme.setCodeFont(font)
76 | ```
77 |
78 | Finally, get an optional NSAttributedString containing the formatted code:
79 |
80 | ```swift
81 | if let displayString: NSAttributedString = highlighter.highlight(codeString, as: "swift") {
82 | myTextView.textStorage!.addAttributedString(displayString)
83 | }
84 | ```
85 |
86 | 
87 |
88 | The second parameter is the name of language you’re rendering. If you leave out this parameter, or pass `nil`, Highlighter will use Highlight.js’ language detection feature.
89 |
90 | You can get a list of supported languages by the name they are known to Highlight.js by calling *supportedLanguages()- — it returns an array of strings.
91 |
92 | The function *availableThemes()- returns a list of the installed themes.
93 |
94 | ## Release Notes
95 |
96 | - 1.1.2 *15 March 2023*
97 | - Include missing languages.
98 | - 1.1.1 *14 March 2023*
99 | - Update to Highlight.js 11.7.0.
100 | - 1.1.0 *26 April 2022*
101 | - Update to Highlight.js 11.5.0.
102 | - Include all Highlight.js languages.
103 | - 1.0.1 *23 July 2021*
104 | - Correct list of available themes in `package.swift`.
105 | - 1.0.0 *15 July 2021*
106 | - Initial public release.
107 |
108 | ## Licences
109 |
110 | HighlighterSwift, like Highlightr before it, is released under the terms of the MIT Licence. Hightlight.js is released under the BSD 3-Clause Licence.
111 |
112 | HighlighterSwift is © 2023, Tony Smith. Portions are © 2016, Juan Pablo Illanes. Other portions are © 2006-2023, Ivan Sagalaev.
113 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.10
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Highlighter",
8 | platforms: [
9 | .macOS(.v14),
10 | .iOS(.v17),
11 | .tvOS(.v17),
12 | .visionOS(.v1)
13 | ],
14 | products: [
15 | .library(
16 | name: "Highlighter",
17 | targets: ["Highlighter"])
18 | ],
19 | dependencies: [
20 | // None
21 | ],
22 | targets: [
23 | .target(
24 | name: "Highlighter",
25 | dependencies: [],
26 | path: "Sources",
27 | exclude: ["Assets/LICENCE"],
28 | resources: [
29 | .copy("Assets/highlight.min.js"),
30 | .copy("Assets/styles/a11y-dark.css"),
31 | .copy("Assets/styles/a11y-light.css"),
32 | .copy("Assets/styles/agate.css"),
33 | .copy("Assets/styles/an-old-hope.css"),
34 | .copy("Assets/styles/androidstudio.css"),
35 | .copy("Assets/styles/arduino-light.css"),
36 | .copy("Assets/styles/arta.css"),
37 | .copy("Assets/styles/ascetic.css"),
38 | .copy("Assets/styles/atelier-cave-dark.css"),
39 | .copy("Assets/styles/atelier-cave-light.css"),
40 | .copy("Assets/styles/atelier-dune-dark.css"),
41 | .copy("Assets/styles/atelier-dune-light.css"),
42 | .copy("Assets/styles/atelier-estuary-dark.css"),
43 | .copy("Assets/styles/atelier-estuary-light.css"),
44 | .copy("Assets/styles/atelier-forest-dark.css"),
45 | .copy("Assets/styles/atelier-forest-light.css"),
46 | .copy("Assets/styles/atelier-heath-dark.css"),
47 | .copy("Assets/styles/atelier-heath-light.css"),
48 | .copy("Assets/styles/atelier-lakeside-dark.css"),
49 | .copy("Assets/styles/atelier-lakeside-light.css"),
50 | .copy("Assets/styles/atelier-plateau-dark.css"),
51 | .copy("Assets/styles/atelier-plateau-light.css"),
52 | .copy("Assets/styles/atelier-savanna-dark.css"),
53 | .copy("Assets/styles/atelier-savanna-light.css"),
54 | .copy("Assets/styles/atelier-seaside-dark.css"),
55 | .copy("Assets/styles/atelier-seaside-light.css"),
56 | .copy("Assets/styles/atelier-sulphurpool-dark.css"),
57 | .copy("Assets/styles/atelier-sulphurpool-light.css"),
58 | .copy("Assets/styles/atom-one-dark-reasonable.css"),
59 | .copy("Assets/styles/atom-one-dark.css"),
60 | .copy("Assets/styles/atom-one-light.css"),
61 | .copy("Assets/styles/brown-paper.css"),
62 | .copy("Assets/styles/codepen-embed.css"),
63 | .copy("Assets/styles/color-brewer.css"),
64 | .copy("Assets/styles/darcula.css"),
65 | .copy("Assets/styles/dark.css"),
66 | .copy("Assets/styles/default.css"),
67 | .copy("Assets/styles/docco.css"),
68 | .copy("Assets/styles/dracula.css"),
69 | .copy("Assets/styles/far.css"),
70 | .copy("Assets/styles/foundation.css"),
71 | .copy("Assets/styles/github-gist.css"),
72 | .copy("Assets/styles/github.css"),
73 | .copy("Assets/styles/gml.css"),
74 | .copy("Assets/styles/googlecode.css"),
75 | .copy("Assets/styles/gradient-light.css"),
76 | .copy("Assets/styles/gradient-dark.css"),
77 | .copy("Assets/styles/grayscale.css"),
78 | .copy("Assets/styles/gruvbox-dark.css"),
79 | .copy("Assets/styles/gruvbox-light.css"),
80 | .copy("Assets/styles/hopscotch.css"),
81 | .copy("Assets/styles/hybrid.css"),
82 | .copy("Assets/styles/idea.css"),
83 | .copy("Assets/styles/ir-black.css"),
84 | .copy("Assets/styles/isbl-editor-dark.css"),
85 | .copy("Assets/styles/isbl-editor-light.css"),
86 | .copy("Assets/styles/kimbie-dark.css"),
87 | .copy("Assets/styles/kimbie-light.css"),
88 | .copy("Assets/styles/lightfair.css"),
89 | .copy("Assets/styles/lioshi.css"),
90 | .copy("Assets/styles/magula.css"),
91 | .copy("Assets/styles/mono-blue.css"),
92 | .copy("Assets/styles/monokai-sublime.css"),
93 | .copy("Assets/styles/monokai.css"),
94 | .copy("Assets/styles/night-owl.css"),
95 | .copy("Assets/styles/nnfx-light.css"),
96 | .copy("Assets/styles/nnfx-dark.css"),
97 | .copy("Assets/styles/nord.css"),
98 | .copy("Assets/styles/obsidian.css"),
99 | .copy("Assets/styles/ocean.css"),
100 | .copy("Assets/styles/paraiso-dark.css"),
101 | .copy("Assets/styles/paraiso-light.css"),
102 | .copy("Assets/styles/pojoaque.css"),
103 | .copy("Assets/styles/purebasic.css"),
104 | .copy("Assets/styles/qtcreator_dark.css"),
105 | .copy("Assets/styles/qtcreator_light.css"),
106 | .copy("Assets/styles/railscasts.css"),
107 | .copy("Assets/styles/rainbow.css"),
108 | .copy("Assets/styles/routeros.css"),
109 | .copy("Assets/styles/school-book.css"),
110 | .copy("Assets/styles/shades-of-purple.css"),
111 | .copy("Assets/styles/solarized-dark.css"),
112 | .copy("Assets/styles/solarized-light.css"),
113 | .copy("Assets/styles/srcery.css"),
114 | .copy("Assets/styles/stackoverflow-light.css"),
115 | .copy("Assets/styles/stackoverflow-dark.css"),
116 | .copy("Assets/styles/sunburst.css"),
117 | .copy("Assets/styles/tomorrow-night-blue.css"),
118 | .copy("Assets/styles/tomorrow-night-bright.css"),
119 | .copy("Assets/styles/tomorrow-night-eighties.css"),
120 | .copy("Assets/styles/tomorrow-night.css"),
121 | .copy("Assets/styles/tomorrow.css"),
122 | .copy("Assets/styles/vs.css"),
123 | .copy("Assets/styles/vs2015.css"),
124 | .copy("Assets/styles/xcode.css"),
125 | .copy("Assets/styles/xcode-dusk.css"),
126 | .copy("Assets/styles/xt256.css"),
127 | .copy("Assets/styles/zenburn.css"),
128 | .copy("Assets/styles/snazzy.css"),
129 | .copy("Assets/styles/silk-light.css"),
130 | .copy("Assets/styles/silk-dark.css"),
131 | .copy("Assets/styles/vulcan.css")
132 | ]),
133 | .testTarget(
134 | name: "HighlighterTests",
135 | dependencies: ["Highlighter"])
136 | ]
137 | )
138 |
--------------------------------------------------------------------------------
/Sources/Highlighter/HTMLUtils.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Highlighter.swift
3 | * Copyright 2023, Tony Smith
4 | * Copyright 2016, Juan-Pablo Illanes
5 | *
6 | * Licence: MIT
7 | */
8 |
9 |
10 | import Foundation
11 |
12 |
13 | /**
14 | Utility class for processing certain HTML entities.
15 | */
16 | internal class HTMLUtils {
17 |
18 | /**
19 | Decode the HTML character entity to the corresponding Unicode character.
20 |
21 | Unicode character, return `nil` for invalid input. For example:
22 |
23 | * `decode("@")` returns `"@"`
24 | * `decode("€")` returns `"€"`
25 | * `decode("<")` returns `"<"`
26 | * `decode("&foo;")` returns `nil`
27 |
28 | - Parameters:
29 | - entity: The HTML entity code.
30 |
31 | - Returns: The entity as a Swift Character, or `nil` if it could not be decoded.
32 | */
33 | class func decode(_ entity: String) -> Character? {
34 |
35 | if entity.lowercased().hasPrefix("") {
36 | return decodeNumeric(String(entity[entity.index(entity.startIndex, offsetBy: 3)...]), base: 16)
37 | } else if entity.hasPrefix("") {
38 | return decodeNumeric(String(entity[entity.index(entity.startIndex, offsetBy: 2)...]), base: 10)
39 | } else {
40 | return characterEntities[entity]
41 | }
42 | }
43 |
44 |
45 | /**
46 | Decode a numerically encoded HTML character entity to the corresponding Unicode character.
47 |
48 | Unicode character, return `nil` for invalid input. For example:
49 |
50 | * `decodeNumeric("@")` returns `"@"`,
51 | * `decodeNumeric("€")` returns `"€"`
52 |
53 | - Parameters:
54 | - entityValue: The HTML entity numeric code.
55 | - base: The numeric base of the value.
56 |
57 | - Returns: The entity as a Swift Character, or `nil` if it could not be decoded.
58 | */
59 | class func decodeNumeric(_ entityValue: String, base: Int32) -> Character? {
60 |
61 | let code: UInt32 = UInt32(strtoul(entityValue, nil, base))
62 | return Character(UnicodeScalar(code)!)
63 | }
64 |
65 | }
66 |
67 |
68 | /**
69 | Dictionary of HTML entities and the characters they refer to.
70 | */
71 | private let characterEntities: [String: Character] = [
72 |
73 | // XML predefined entities
74 | """ : "\"",
75 | "&" : "&",
76 | "'" : "'",
77 | "<" : "<",
78 | ">" : ">",
79 |
80 | // HTML character entity references
81 | " " : "\u{00A0}",
82 | "¡" : "\u{00A1}",
83 | "¢" : "\u{00A2}",
84 | "£" : "\u{00A3}",
85 | "¤" : "\u{00A4}",
86 | "¥" : "\u{00A5}",
87 | "¦" : "\u{00A6}",
88 | "§" : "\u{00A7}",
89 | "¨" : "\u{00A8}",
90 | "©" : "\u{00A9}",
91 | "ª" : "\u{00AA}",
92 | "«" : "\u{00AB}",
93 | "¬" : "\u{00AC}",
94 | "" : "\u{00AD}",
95 | "®" : "\u{00AE}",
96 | "¯" : "\u{00AF}",
97 | "°" : "\u{00B0}",
98 | "±" : "\u{00B1}",
99 | "²" : "\u{00B2}",
100 | "³" : "\u{00B3}",
101 | "´" : "\u{00B4}",
102 | "µ" : "\u{00B5}",
103 | "¶" : "\u{00B6}",
104 | "·" : "\u{00B7}",
105 | "¸" : "\u{00B8}",
106 | "¹" : "\u{00B9}",
107 | "º" : "\u{00BA}",
108 | "»" : "\u{00BB}",
109 | "¼" : "\u{00BC}",
110 | "½" : "\u{00BD}",
111 | "¾" : "\u{00BE}",
112 | "¿" : "\u{00BF}",
113 | "À" : "\u{00C0}",
114 | "Á" : "\u{00C1}",
115 | "Â" : "\u{00C2}",
116 | "Ã" : "\u{00C3}",
117 | "Ä" : "\u{00C4}",
118 | "Å" : "\u{00C5}",
119 | "Æ" : "\u{00C6}",
120 | "Ç" : "\u{00C7}",
121 | "È" : "\u{00C8}",
122 | "É" : "\u{00C9}",
123 | "Ê" : "\u{00CA}",
124 | "Ë" : "\u{00CB}",
125 | "Ì" : "\u{00CC}",
126 | "Í" : "\u{00CD}",
127 | "Î" : "\u{00CE}",
128 | "Ï" : "\u{00CF}",
129 | "Ð" : "\u{00D0}",
130 | "Ñ" : "\u{00D1}",
131 | "Ò" : "\u{00D2}",
132 | "Ó" : "\u{00D3}",
133 | "Ô" : "\u{00D4}",
134 | "Õ" : "\u{00D5}",
135 | "Ö" : "\u{00D6}",
136 | "×" : "\u{00D7}",
137 | "Ø" : "\u{00D8}",
138 | "Ù" : "\u{00D9}",
139 | "Ú" : "\u{00DA}",
140 | "Û" : "\u{00DB}",
141 | "Ü" : "\u{00DC}",
142 | "Ý" : "\u{00DD}",
143 | "Þ" : "\u{00DE}",
144 | "ß" : "\u{00DF}",
145 | "à" : "\u{00E0}",
146 | "á" : "\u{00E1}",
147 | "â" : "\u{00E2}",
148 | "ã" : "\u{00E3}",
149 | "ä" : "\u{00E4}",
150 | "å" : "\u{00E5}",
151 | "æ" : "\u{00E6}",
152 | "ç" : "\u{00E7}",
153 | "è" : "\u{00E8}",
154 | "é" : "\u{00E9}",
155 | "ê" : "\u{00EA}",
156 | "ë" : "\u{00EB}",
157 | "ì" : "\u{00EC}",
158 | "í" : "\u{00ED}",
159 | "î" : "\u{00EE}",
160 | "ï" : "\u{00EF}",
161 | "ð" : "\u{00F0}",
162 | "ñ" : "\u{00F1}",
163 | "ò" : "\u{00F2}",
164 | "ó" : "\u{00F3}",
165 | "ô" : "\u{00F4}",
166 | "õ" : "\u{00F5}",
167 | "ö" : "\u{00F6}",
168 | "÷" : "\u{00F7}",
169 | "ø" : "\u{00F8}",
170 | "ù" : "\u{00F9}",
171 | "ú" : "\u{00FA}",
172 | "û" : "\u{00FB}",
173 | "ü" : "\u{00FC}",
174 | "ý" : "\u{00FD}",
175 | "þ" : "\u{00FE}",
176 | "ÿ" : "\u{00FF}",
177 | "Œ" : "\u{0152}",
178 | "œ" : "\u{0153}",
179 | "Š" : "\u{0160}",
180 | "š" : "\u{0161}",
181 | "Ÿ" : "\u{0178}",
182 | "ƒ" : "\u{0192}",
183 | "ˆ" : "\u{02C6}",
184 | "˜" : "\u{02DC}",
185 | "Α" : "\u{0391}",
186 | "Β" : "\u{0392}",
187 | "Γ" : "\u{0393}",
188 | "Δ" : "\u{0394}",
189 | "Ε" : "\u{0395}",
190 | "Ζ" : "\u{0396}",
191 | "Η" : "\u{0397}",
192 | "Θ" : "\u{0398}",
193 | "Ι" : "\u{0399}",
194 | "Κ" : "\u{039A}",
195 | "Λ" : "\u{039B}",
196 | "Μ" : "\u{039C}",
197 | "Ν" : "\u{039D}",
198 | "Ξ" : "\u{039E}",
199 | "Ο" : "\u{039F}",
200 | "Π" : "\u{03A0}",
201 | "Ρ" : "\u{03A1}",
202 | "Σ" : "\u{03A3}",
203 | "Τ" : "\u{03A4}",
204 | "Υ" : "\u{03A5}",
205 | "Φ" : "\u{03A6}",
206 | "Χ" : "\u{03A7}",
207 | "Ψ" : "\u{03A8}",
208 | "Ω" : "\u{03A9}",
209 | "α" : "\u{03B1}",
210 | "β" : "\u{03B2}",
211 | "γ" : "\u{03B3}",
212 | "δ" : "\u{03B4}",
213 | "ε" : "\u{03B5}",
214 | "ζ" : "\u{03B6}",
215 | "η" : "\u{03B7}",
216 | "θ" : "\u{03B8}",
217 | "ι" : "\u{03B9}",
218 | "κ" : "\u{03BA}",
219 | "λ" : "\u{03BB}",
220 | "μ" : "\u{03BC}",
221 | "ν" : "\u{03BD}",
222 | "ξ" : "\u{03BE}",
223 | "ο" : "\u{03BF}",
224 | "π" : "\u{03C0}",
225 | "ρ" : "\u{03C1}",
226 | "ς" : "\u{03C2}",
227 | "σ" : "\u{03C3}",
228 | "τ" : "\u{03C4}",
229 | "υ" : "\u{03C5}",
230 | "φ" : "\u{03C6}",
231 | "χ" : "\u{03C7}",
232 | "ψ" : "\u{03C8}",
233 | "ω" : "\u{03C9}",
234 | "ϑ" : "\u{03D1}",
235 | "ϒ" : "\u{03D2}",
236 | "ϖ" : "\u{03D6}",
237 | " " : "\u{2002}",
238 | " " : "\u{2003}",
239 | " " : "\u{2009}",
240 | "" : "\u{200C}",
241 | "" : "\u{200D}",
242 | "" : "\u{200E}",
243 | "" : "\u{200F}",
244 | "–" : "\u{2013}",
245 | "—" : "\u{2014}",
246 | "‘" : "\u{2018}",
247 | "’" : "\u{2019}",
248 | "‚" : "\u{201A}",
249 | "“" : "\u{201C}",
250 | "”" : "\u{201D}",
251 | "„" : "\u{201E}",
252 | "†" : "\u{2020}",
253 | "‡" : "\u{2021}",
254 | "•" : "\u{2022}",
255 | "…" : "\u{2026}",
256 | "‰" : "\u{2030}",
257 | "′" : "\u{2032}",
258 | "″" : "\u{2033}",
259 | "‹" : "\u{2039}",
260 | "›" : "\u{203A}",
261 | "‾" : "\u{203E}",
262 | "⁄" : "\u{2044}",
263 | "€" : "\u{20AC}",
264 | "ℑ" : "\u{2111}",
265 | "℘" : "\u{2118}",
266 | "ℜ" : "\u{211C}",
267 | "™" : "\u{2122}",
268 | "ℵ" : "\u{2135}",
269 | "←" : "\u{2190}",
270 | "↑" : "\u{2191}",
271 | "→" : "\u{2192}",
272 | "↓" : "\u{2193}",
273 | "↔" : "\u{2194}",
274 | "↵" : "\u{21B5}",
275 | "⇐" : "\u{21D0}",
276 | "⇑" : "\u{21D1}",
277 | "⇒" : "\u{21D2}",
278 | "⇓" : "\u{21D3}",
279 | "⇔" : "\u{21D4}",
280 | "∀" : "\u{2200}",
281 | "∂" : "\u{2202}",
282 | "∃" : "\u{2203}",
283 | "∅" : "\u{2205}",
284 | "∇" : "\u{2207}",
285 | "∈" : "\u{2208}",
286 | "∉" : "\u{2209}",
287 | "∋" : "\u{220B}",
288 | "∏" : "\u{220F}",
289 | "∑" : "\u{2211}",
290 | "−" : "\u{2212}",
291 | "∗" : "\u{2217}",
292 | "√" : "\u{221A}",
293 | "∝" : "\u{221D}",
294 | "∞" : "\u{221E}",
295 | "∠" : "\u{2220}",
296 | "∧" : "\u{2227}",
297 | "∨" : "\u{2228}",
298 | "∩" : "\u{2229}",
299 | "∪" : "\u{222A}",
300 | "∫" : "\u{222B}",
301 | "∴" : "\u{2234}",
302 | "∼" : "\u{223C}",
303 | "≅" : "\u{2245}",
304 | "≈" : "\u{2248}",
305 | "≠" : "\u{2260}",
306 | "≡" : "\u{2261}",
307 | "≤" : "\u{2264}",
308 | "≥" : "\u{2265}",
309 | "⊂" : "\u{2282}",
310 | "⊃" : "\u{2283}",
311 | "⊄" : "\u{2284}",
312 | "⊆" : "\u{2286}",
313 | "⊇" : "\u{2287}",
314 | "⊕" : "\u{2295}",
315 | "⊗" : "\u{2297}",
316 | "⊥" : "\u{22A5}",
317 | "⋅" : "\u{22C5}",
318 | "⌈" : "\u{2308}",
319 | "⌉" : "\u{2309}",
320 | "⌊" : "\u{230A}",
321 | "⌋" : "\u{230B}",
322 | "〈" : "\u{2329}",
323 | "〉" : "\u{232A}",
324 | "◊" : "\u{25CA}",
325 | "♠" : "\u{2660}",
326 | "♣" : "\u{2663}",
327 | "♥" : "\u{2665}",
328 | "♦" : "\u{2666}"
329 | ]
330 |
331 |
332 |
--------------------------------------------------------------------------------
/Sources/Highlighter/Highlighter.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Highlighter.swift
3 | * Copyright 2023, Tony Smith
4 | * Copyright 2016, Juan-Pablo Illanes
5 | *
6 | * Licence: MIT
7 | */
8 |
9 |
10 | import Foundation
11 | import JavaScriptCore
12 |
13 | #if os(OSX)
14 | import AppKit
15 | #endif
16 |
17 |
18 | /**
19 | Wrapper class for generating a highlighted NSAttributedString from a code string.
20 | */
21 | open class Highlighter {
22 |
23 | // MARK:- Public Properties
24 | open var theme: Theme! {
25 | didSet {
26 | themeChanged?(theme)
27 | }
28 | }
29 |
30 | // This block will be called every time the theme changes.
31 | open var themeChanged: ((Theme) -> Void)?
32 |
33 | // When `true`, forces highlighting to finish even if illegal syntax is detected.
34 | open var ignoreIllegals = false
35 |
36 |
37 | // MARK:- Private Properties
38 | private let hljs: JSValue
39 | private let bundle: Bundle
40 | private let htmlStart: String = "<"
41 | private let spanStart: String = "span class=\""
42 | private let spanStartClose: String = "\">"
43 | private let spanEnd: String = "/span>"
44 | private let htmlEscape: NSRegularExpression = try! NSRegularExpression(pattern: "?[a-zA-Z0-9]+?;", options: .caseInsensitive)
45 |
46 |
47 | // MARK:- Constructor
48 |
49 | /**
50 | The default initialiser.
51 |
52 | Returns `nil` on failure to load or evaluate `highlight.min.js`,
53 | or to load the default theme (`Default`)
54 | */
55 | public init?() {
56 |
57 | // Get the library's bundle based on how it's
58 | // being included in the host app
59 | #if SWIFT_PACKAGE
60 | let bundle = Bundle.module
61 | #else
62 | let bundle = Bundle(for: Highlighter.self)
63 | #endif
64 |
65 | // Load the highlight.js code from the bundle or fail
66 | guard let highlightPath: String = bundle.path(forResource: "highlight.min", ofType: "js") else {
67 | return nil
68 | }
69 |
70 | // Check the JavaScript or fail
71 | let context = JSContext.init()!
72 | let highlightJs: String = try! String.init(contentsOfFile: highlightPath)
73 | let _ = context.evaluateScript(highlightJs)
74 | guard let hljs = context.globalObject.objectForKeyedSubscript("hljs") else {
75 | return nil
76 | }
77 |
78 | // Store the results for later
79 | self.hljs = hljs
80 | self.bundle = bundle
81 |
82 | // Check and set applying a theme or fail
83 | // NOTE 'setTheme()' depends on 'self.bundle'
84 | guard setTheme("default") else {
85 | return nil
86 | }
87 | }
88 |
89 |
90 | //MARK: - Primary Functions
91 |
92 | /**
93 | Highlight the supplied code in the specified language.
94 |
95 | - Parameters:
96 | - code: The source code to highlight.
97 | - languageName: The language in which the code is written.
98 | - doFastRender: Should fast rendering be used? Default: `true`.
99 |
100 | - Returns: The highlighted code as an NSAttributedString, or `nil`
101 | */
102 | open func highlight(_ code: String, as languageName: String? = nil, doFastRender: Bool = true) -> NSAttributedString? {
103 |
104 | let returnValue: JSValue
105 |
106 | if let language = languageName {
107 | // Use the specified language
108 | // NOTE Will return 'undefined' (trapped below) if it's a unknown language
109 | let options: [String: Any] = ["language": language, "ignoreIllegals": self.ignoreIllegals]
110 | returnValue = hljs.invokeMethod("highlight",
111 | withArguments: [code, options])
112 | } else {
113 | // Use language auto detection
114 | returnValue = hljs.invokeMethod("highlightAuto",
115 | withArguments: [code])
116 | }
117 |
118 | // Check we got a valid string back - fail if we didn't
119 | let renderedHTMLValue: JSValue? = returnValue.objectForKeyedSubscript("value")
120 | guard var renderedHTMLString: String = renderedHTMLValue!.toString() else {
121 | return nil
122 | }
123 |
124 | // Trap 'undefined' output as this is effectively an error condition
125 | // and should not be returned as a valid result -- it's actually a fail
126 | if renderedHTMLString == "undefined" {
127 | return nil
128 | }
129 |
130 | // Convert the HTML received from Highlight.js to an NSAttributedString or nil
131 | var returnAttrString: NSAttributedString? = nil
132 |
133 | if doFastRender {
134 | // Use fast rendering -- the default
135 | returnAttrString = processHTMLString(renderedHTMLString)!
136 | } else {
137 | // Use NSAttributedString's own not-so-fast rendering
138 | renderedHTMLString = "" + renderedHTMLString + "
"
139 |
140 | let data = renderedHTMLString.data(using: String.Encoding.utf8)!
141 | let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
142 | .documentType: NSAttributedString.DocumentType.html,
143 | .characterEncoding: String.Encoding.utf8.rawValue
144 | ]
145 |
146 | // Execute on main thread
147 | // NOTE Not sure why, when we don't do this elsewhere
148 | safeMainSync
149 | {
150 | returnAttrString = try? NSMutableAttributedString(data:data, options: options, documentAttributes:nil)
151 | }
152 | }
153 |
154 | return returnAttrString
155 | }
156 |
157 |
158 | /**
159 | Set the Highligt.js theme to use for highlighting.
160 |
161 | - Parameters:
162 | - themeName: The Highlight.js theme's name.
163 | - withFont: The name of the font to use. Default: Courier.
164 | - ofSize: The size of the font. Default: 14pt.
165 |
166 | - Returns: Whether the theme was successfully applied (`true`) or not (`false`)
167 | */
168 | @discardableResult
169 | open func setTheme(_ themeName: String, withFont: String? = nil, ofSize: CGFloat? = nil) -> Bool {
170 |
171 | // Make sure we can load the theme's CSS file -- or fail
172 | guard let themePath = self.bundle.path(forResource: themeName, ofType: "css") else {
173 | return false
174 | }
175 |
176 | // Create the required font
177 | // If this fails ('font' == nil), we use the defaults
178 | var font: HRFont? = nil
179 | if let fontName: String = withFont {
180 | var size: CGFloat = 14.0
181 | if ofSize != nil {
182 | size = ofSize!
183 | }
184 |
185 | font = HRFont.init(name: fontName, size: size)
186 | }
187 |
188 | // Get the theme CSS and instantiate a Theme object
189 | let themeString = try! String.init(contentsOfFile: themePath)
190 | self.theme = Theme.init(withTheme: themeString, usingFont: font)
191 | return true
192 | }
193 |
194 |
195 | /**
196 | Get a list of available Highlight.js themes.
197 |
198 | Just lists what CSS files are in the bundle.
199 |
200 | - Returns: The list of themes as an array of strings.
201 | */
202 | open func availableThemes() -> [String] {
203 |
204 | let paths = bundle.paths(forResourcesOfType: "css", inDirectory: nil) as [NSString]
205 | var result = [String]()
206 | for path in paths {
207 | result.append(path.lastPathComponent.replacingOccurrences(of: ".css", with: ""))
208 | }
209 |
210 | return result
211 | }
212 |
213 |
214 | /**
215 | Get a list of languages supported by Highlight.js.
216 |
217 | - Returns: The list of languages as an array of strings.
218 | */
219 | open func supportedLanguages() -> [String] {
220 |
221 | let res: JSValue? = hljs.invokeMethod("listLanguages", withArguments: [])
222 | return res!.toArray() as! [String]
223 | }
224 |
225 |
226 | // MARK:- Fast HTML Rendering Function
227 |
228 | /**
229 | Generate an NSAttributedString from HTML source.
230 |
231 | - Parameters:
232 | - htmlString: The HTML to be converted.
233 |
234 | - Returns: The rendered HTML as an NSAttibutedString, or `nil` if an error occurred.
235 | */
236 | private func processHTMLString(_ htmlString: String) -> NSAttributedString? {
237 |
238 | let scanner: Scanner = Scanner(string: htmlString)
239 | scanner.charactersToBeSkipped = nil
240 | var scannedString: NSString?
241 | let resultString: NSMutableAttributedString = NSMutableAttributedString(string: "")
242 | var propStack: [String] = ["hljs"]
243 |
244 | while !scanner.isAtEnd {
245 | var ended: Bool = false
246 | if scanner.scanUpTo(self.htmlStart,
247 | into: &scannedString) {
248 | ended = scanner.isAtEnd
249 | }
250 |
251 | if scannedString != nil && scannedString!.length > 0 {
252 | let attrScannedString: NSAttributedString = self.theme.applyStyleToString(scannedString! as String,
253 | styleList: propStack)
254 | resultString.append(attrScannedString)
255 |
256 | if ended {
257 | continue
258 | }
259 | }
260 |
261 | scanner.scanLocation += 1
262 |
263 | let string: NSString = scanner.string as NSString
264 | let nextChar: String = string.substring(with: NSMakeRange(scanner.scanLocation, 1))
265 | if nextChar == "s" {
266 | scanner.scanLocation += (self.spanStart as NSString).length
267 | scanner.scanUpTo(self.spanStartClose, into:&scannedString)
268 | scanner.scanLocation += (self.spanStartClose as NSString).length
269 | propStack.append(scannedString! as String)
270 | } else if nextChar == "/" {
271 | scanner.scanLocation += (self.spanEnd as NSString).length
272 | propStack.removeLast()
273 | } else {
274 | let attrScannedString: NSAttributedString = self.theme.applyStyleToString("<", styleList: propStack)
275 | resultString.append(attrScannedString)
276 | scanner.scanLocation += 1
277 | }
278 |
279 | scannedString = nil
280 | }
281 |
282 | let results: [NSTextCheckingResult] = self.htmlEscape.matches(in: resultString.string,
283 | options: [.reportCompletion],
284 | range: NSMakeRange(0, resultString.length))
285 | var localOffset: Int = 0
286 | for result: NSTextCheckingResult in results {
287 | let fixedRange: NSRange = NSMakeRange(result.range.location - localOffset, result.range.length)
288 | let entity: String = (resultString.string as NSString).substring(with: fixedRange)
289 | if let decodedEntity = HTMLUtils.decode(entity) {
290 | resultString.replaceCharacters(in: fixedRange, with: String(decodedEntity))
291 | localOffset += (result.range.length - 1);
292 | }
293 | }
294 |
295 | return resultString
296 | }
297 |
298 |
299 | // MARK:- Utility Functions
300 |
301 | /**
302 | Execute the supplied block on the main thread.
303 | */
304 | private func safeMainSync(_ block: @escaping ()->()) {
305 |
306 | if Thread.isMainThread {
307 | block()
308 | } else {
309 | DispatchQueue.main.sync {
310 | block()
311 | }
312 | }
313 | }
314 |
315 |
316 |
317 | }
318 |
--------------------------------------------------------------------------------
/Sources/Highlighter/Theme.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * Highlighter.swift
3 | * Copyright 2023, Tony Smith
4 | * Copyright 2016, Juan-Pablo Illanes
5 | *
6 | * Licence: MIT
7 | */
8 |
9 |
10 | import Foundation
11 |
12 | #if os(iOS) || os(tvOS) || os(visionOS)
13 | import UIKit
14 | public typealias HRColor = UIColor
15 | public typealias HRFont = UIFont
16 | #else
17 | import AppKit
18 | public typealias HRColor = NSColor
19 | public typealias HRFont = NSFont
20 | #endif
21 |
22 | private typealias HRThemeDict = [String: [AnyHashable: AnyObject]]
23 | private typealias HRThemeStringDict = [String: [String: String]]
24 |
25 |
26 | /**
27 | Class representing HighlightSwift's interal storage of a processed Highlight.js theme.
28 | */
29 | open class Theme {
30 |
31 | // MARK:- Public Properties
32 | internal let theme: String
33 | internal var lightTheme: String!
34 |
35 | open var codeFont: HRFont!
36 | open var boldCodeFont: HRFont!
37 | open var italicCodeFont: HRFont!
38 | open var themeBackgroundColour: HRColor!
39 |
40 |
41 | // MARK:- Private Properties
42 | private var themeDict : HRThemeDict!
43 | private var strippedTheme : HRThemeStringDict!
44 |
45 |
46 | // MARK:- Constructor
47 |
48 | /**
49 | The default initialiser.
50 |
51 | - Parameters:
52 | - withTheme: The name of the Highlight.js theme to use. Default: `Default`.
53 | - usingFont: Optionally, a UIFont or NSFont to apply to the theme. Default: Courier @ 14pt.
54 | */
55 | init(withTheme: String = "default", usingFont: HRFont? = nil) {
56 |
57 | // Record the theme name
58 | self.theme = withTheme
59 |
60 | // Apply the font choice
61 | if let font: HRFont = usingFont {
62 | setCodeFont(font)
63 | } else if let font = HRFont(name: "courier", size: 14.0) {
64 | setCodeFont(font)
65 | } else {
66 | // Just in case Courier has been deleted...
67 | setCodeFont(HRFont.systemFont(ofSize: 14.0))
68 | }
69 |
70 | // Generate and store the theme variants
71 | self.strippedTheme = stripTheme(self.theme)
72 | self.lightTheme = strippedThemeToString(self.strippedTheme)
73 | self.themeDict = strippedThemeToTheme(self.strippedTheme)
74 |
75 | // Determine the theme's background colour as a hex string
76 | var backgroundColourHex: String? = self.strippedTheme[".hljs"]?["background"]
77 | if backgroundColourHex == nil {
78 | backgroundColourHex = self.strippedTheme[".hljs"]?["background-color"]
79 | }
80 |
81 | // Convert the hex to a UIColor or NSColor
82 | if let bgColourHex = backgroundColourHex {
83 | self.themeBackgroundColour = colourFromHexString(bgColourHex)
84 | } else {
85 | // Set a generic (light) background
86 | self.themeBackgroundColour = HRColor.white
87 | }
88 | }
89 |
90 |
91 | // MARK:- Getters and Setters
92 |
93 | /**
94 | Change the theme's font.
95 |
96 | This will automatically populate bold and italic variants of the specified font.
97 |
98 | - Parameters:
99 | - font: The UIFont or NSFont to use.
100 | */
101 | open func setCodeFont(_ font: HRFont) {
102 |
103 | // Store the primary font choice
104 | self.codeFont = font
105 |
106 | // Generate the bold and italic variants
107 | #if os(iOS) || os(tvOS) || os(visionOS)
108 | let boldDescriptor = UIFontDescriptor(fontAttributes: [UIFontDescriptor.AttributeName.family:font.familyName,
109 | UIFontDescriptor.AttributeName.face:"Bold"])
110 | let italicDescriptor = UIFontDescriptor(fontAttributes: [UIFontDescriptor.AttributeName.family:font.familyName,
111 | UIFontDescriptor.AttributeName.face:"Italic"])
112 | let obliqueDescriptor = UIFontDescriptor(fontAttributes: [UIFontDescriptor.AttributeName.family:font.familyName,
113 | UIFontDescriptor.AttributeName.face:"Oblique"])
114 | #else
115 | let boldDescriptor = NSFontDescriptor(fontAttributes: [.family:font.familyName!,
116 | .face:"Bold"])
117 | let italicDescriptor = NSFontDescriptor(fontAttributes: [.family:font.familyName!,
118 | .face:"Italic"])
119 | let obliqueDescriptor = NSFontDescriptor(fontAttributes: [.family:font.familyName!,
120 | .face:"Oblique"])
121 | #endif
122 |
123 | self.boldCodeFont = HRFont(descriptor: boldDescriptor, size: font.pointSize)
124 | self.italicCodeFont = HRFont(descriptor: italicDescriptor, size: font.pointSize)
125 |
126 | if (self.italicCodeFont == nil || self.italicCodeFont.familyName != font.familyName) {
127 | self.italicCodeFont = HRFont(descriptor: obliqueDescriptor, size: font.pointSize)
128 | }
129 |
130 | if (self.italicCodeFont == nil) {
131 | self.italicCodeFont = font
132 | }
133 |
134 | if (self.boldCodeFont == nil) {
135 | self.boldCodeFont = font
136 | }
137 |
138 | if (self.themeDict != nil) {
139 | self.themeDict = strippedThemeToTheme(self.strippedTheme)
140 | }
141 | }
142 |
143 |
144 | // MARK:- Private Functions
145 |
146 | /**
147 | Convert a string to an NSAttributedString styled using the theme.
148 |
149 | Automatically applies the theme's font.
150 |
151 | - Parameters:
152 | - string: The source code string.
153 | - styleList: An array of attribute keys (strings).
154 |
155 | - Returns: The styled text as an NSAttributedString.
156 | */
157 | internal func applyStyleToString(_ string: String, styleList: [String]) -> NSAttributedString {
158 |
159 | let returnString: NSAttributedString
160 |
161 | if styleList.count > 0 {
162 | // Build the attributes from the style list, including the font
163 | var attrs = [AttributedStringKey: Any]()
164 | attrs[.font] = self.codeFont
165 | for style in styleList {
166 | if let themeStyle = self.themeDict[style] as? [AttributedStringKey: Any] {
167 | for (attrName, attrValue) in themeStyle {
168 | attrs.updateValue(attrValue, forKey: attrName)
169 | }
170 | }
171 | }
172 |
173 | returnString = NSAttributedString(string: string, attributes:attrs)
174 | } else {
175 | // No specified attributes? Just set the font
176 | returnString = NSAttributedString(string: string, attributes:[AttributedStringKey.font: codeFont as Any])
177 | }
178 |
179 | return returnString
180 | }
181 |
182 | /**
183 | Convert a Highlight.js theme's CSS to the class' string dictionary.
184 |
185 | - Parameters:
186 | - themeString: The theme's CSS string.
187 |
188 | - Returns: A dictionary of styles and values.
189 | */
190 | private func stripTheme(_ themeString : String) -> HRThemeStringDict {
191 |
192 | let objcString: NSString = (themeString as NSString)
193 | let cssRegex = try! NSRegularExpression(pattern: "(?:(\\.[a-zA-Z0-9\\-_]*(?:[, ]\\.[a-zA-Z0-9\\-_]*)*)\\{([^\\}]*?)\\})",
194 | options:[.caseInsensitive])
195 | let results = cssRegex.matches(in: themeString,
196 | options: [.reportCompletion],
197 | range: NSMakeRange(0, objcString.length))
198 | var resultDict = [String: [String: String]]()
199 |
200 | for result in results {
201 | if result.numberOfRanges == 3 {
202 | var attributes = [String:String]()
203 | let cssPairs = objcString.substring(with: result.range(at: 2)).components(separatedBy: ";")
204 | for pair in cssPairs {
205 | let cssPropComp = pair.components(separatedBy: ":")
206 | if (cssPropComp.count == 2) {
207 | attributes[cssPropComp[0]] = cssPropComp[1]
208 | }
209 | }
210 |
211 | if attributes.count > 0 {
212 | // Check if we're adding attributes to an existing hljs key
213 | if resultDict[objcString.substring(with: result.range(at: 1))] != nil {
214 | // We have the key already so merge in the latest attribute dictionary
215 | let existingAttributes: [String: String] = resultDict[objcString.substring(with: result.range(at: 1))]!
216 | resultDict[objcString.substring(with: result.range(at: 1))] = existingAttributes.merging(attributes, uniquingKeysWith: { (first, _) in first })
217 | } else {
218 | // Set the attributes to a new key
219 | resultDict[objcString.substring(with: result.range(at: 1))] = attributes
220 | }
221 | }
222 | }
223 | }
224 |
225 | var returnDict = [String: [String: String]]()
226 | for (keys, result) in resultDict {
227 | let keyArray = keys.replacingOccurrences(of: " ", with: ",").components(separatedBy: ",")
228 | for key in keyArray {
229 | var props : [String: String]?
230 | props = returnDict[key]
231 | if props == nil {
232 | props = [String:String]()
233 | }
234 |
235 | for (pName, pValue) in result {
236 | props!.updateValue(pValue, forKey: pName)
237 | }
238 |
239 | returnDict[key] = props!
240 | }
241 | }
242 |
243 | return returnDict
244 | }
245 |
246 |
247 | /**
248 | Convert an instance's string dictionary to a CSS string.
249 |
250 | - Parameters:
251 | - themeStringDict: The dictionary of styles and values.
252 |
253 | - Returns: CSS code as a string.
254 | */
255 | private func strippedThemeToString(_ themeStringDict: HRThemeStringDict) -> String {
256 |
257 | var resultString: String = ""
258 | for (key, props) in themeStringDict {
259 | resultString += (key + "{")
260 | for (cssProp, val) in props {
261 | if key != ".hljs" || (cssProp.lowercased() != "background-color" && cssProp.lowercased() != "background") {
262 | resultString += "\(cssProp):\(val);"
263 | }
264 | }
265 |
266 | resultString += "}"
267 | }
268 |
269 | return resultString
270 | }
271 |
272 |
273 | /**
274 | Convert in instance's string dictionary to base dictionary.
275 |
276 | - Parameters:
277 | - themeStringDict: The dictionary of styles and values.
278 |
279 | - Returns: The base dictionary.
280 | */
281 | private func strippedThemeToTheme(_ themeStringDict: HRThemeStringDict) -> HRThemeDict {
282 |
283 | var returnTheme = HRThemeDict()
284 | for (className, props) in themeStringDict {
285 | var keyProps = [AttributedStringKey: AnyObject]()
286 | for (key, prop) in props {
287 | switch key {
288 | case "color":
289 | keyProps[attributeForCSSKey(key)] = colourFromHexString(prop)
290 | case "font-style":
291 | keyProps[attributeForCSSKey(key)] = fontForCSSStyle(prop)
292 | case "font-weight":
293 | keyProps[attributeForCSSKey(key)] = fontForCSSStyle(prop)
294 | case "background-color":
295 | keyProps[attributeForCSSKey(key)] = colourFromHexString(prop)
296 | default:
297 | break
298 | }
299 | }
300 |
301 | if keyProps.count > 0 {
302 | let key: String = className.replacingOccurrences(of: ".", with: "")
303 | returnTheme[key] = keyProps
304 | }
305 | }
306 |
307 | return returnTheme
308 | }
309 |
310 |
311 | /**
312 | Get font information from a CSS string and use it to generate a font object.
313 |
314 | - Parameters:
315 | - fontStyle: The CSS font definition.
316 |
317 | - Returns: A UIFont or NSFont.
318 | */
319 | internal func fontForCSSStyle(_ fontStyle: String) -> HRFont {
320 |
321 | switch fontStyle {
322 | case "bold", "bolder", "600", "700", "800", "900":
323 | return self.boldCodeFont
324 | case "italic", "oblique":
325 | return self.italicCodeFont
326 | default:
327 | return self.codeFont
328 | }
329 | }
330 |
331 |
332 | /**
333 | Emit an AttributedString key based on the a style key from a CSS file.
334 |
335 | - Parameters:
336 | - key: The CSS attribute key.
337 |
338 | - Returns: The NSAttributedString key.
339 | */
340 | internal func attributeForCSSKey(_ key: String) -> AttributedStringKey {
341 |
342 | switch key {
343 | case "color":
344 | return .foregroundColor
345 | case "font-weight":
346 | return .font
347 | case "font-style":
348 | return .font
349 | case "background-color":
350 | return .backgroundColor
351 | default:
352 | return .font
353 | }
354 | }
355 |
356 | /**
357 | Emit a colour object to match a hex string or CSS colour identifiier.
358 |
359 | Identifiers supported:
360 |
361 | * `white`
362 | * `black`
363 | * `red`
364 | * `green`
365 | * `blue`
366 | * `navy`
367 | * `silver`
368 |
369 | Unknown colour identifiers default to grey.
370 |
371 | - Parameters:
372 | - colourValue: The CSS colour specification.
373 |
374 | - Returns: A UIColor or NSColor.
375 | */
376 | internal func colourFromHexString(_ colourValue: String) -> HRColor {
377 |
378 | var colourString: String = colourValue.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
379 |
380 | if (colourString.hasPrefix("#")) {
381 | // The colour is defined by a hex value
382 | colourString = (colourString as NSString).substring(from: 1)
383 | } else {
384 | switch colourString {
385 | case "white":
386 | return HRColor.init(white: 1.0, alpha: 1.0)
387 | case "black":
388 | return HRColor.init(white: 0.0, alpha: 1.0)
389 | case "red":
390 | return HRColor.init(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
391 | case "green":
392 | return HRColor.init(red: 0.0, green: 0.5, blue: 0.0, alpha: 1.0)
393 | case "blue":
394 | return HRColor.init(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0)
395 | case "navy":
396 | return HRColor.init(red: 0.0, green: 0.0, blue: 0.5, alpha: 1.0)
397 | case "silver":
398 | return HRColor.init(red: 0.75, green: 0.75, blue: 0.75, alpha: 1.0)
399 | default:
400 | return HRColor.gray
401 | }
402 | }
403 |
404 | // Colours in hex strings have 3, 6 or 8 (6 + alpha) values
405 | if colourString.count != 8 && colourString.count != 6 && colourString.count != 3 {
406 | return HRColor.gray
407 | }
408 |
409 | var r: UInt64 = 0, g: UInt64 = 0, b: UInt64 = 0, a: UInt64 = 0
410 | var divisor: CGFloat
411 | var alpha: CGFloat = 1.0
412 |
413 | if colourString.count == 6 || colourString.count == 8 {
414 | // Decode a six-character hex string
415 | let rString: String = (colourString as NSString).substring(to: 2)
416 | let gString: String = ((colourString as NSString).substring(from: 2) as NSString).substring(to: 2)
417 | let bString: String = ((colourString as NSString).substring(from: 4) as NSString).substring(to: 2)
418 |
419 | Scanner(string: rString).scanHexInt64(&r)
420 | Scanner(string: gString).scanHexInt64(&g)
421 | Scanner(string: bString).scanHexInt64(&b)
422 |
423 | divisor = 255.0
424 |
425 | if colourString.count == 8 {
426 | // Decode the eight-character hex string's alpha value
427 | let aString: String = ((colourString as NSString).substring(from: 6) as NSString).substring(to: 2)
428 | Scanner(string: aString).scanHexInt64(&a)
429 | alpha = CGFloat(a) / divisor
430 | }
431 | } else {
432 | // Decode a three-character hex string
433 | let rString: String = (colourString as NSString).substring(to: 1)
434 | let gString: String = ((colourString as NSString).substring(from: 1) as NSString).substring(to: 1)
435 | let bString: String = ((colourString as NSString).substring(from: 2) as NSString).substring(to: 1)
436 |
437 | Scanner(string: rString).scanHexInt64(&r)
438 | Scanner(string: gString).scanHexInt64(&g)
439 | Scanner(string: bString).scanHexInt64(&b)
440 |
441 | divisor = 15.0
442 | }
443 |
444 | return HRColor(red: CGFloat(r) / divisor, green: CGFloat(g) / divisor, blue: CGFloat(b) / divisor, alpha: alpha)
445 | }
446 | }
447 |
--------------------------------------------------------------------------------