├── .gitignore ├── LICENSE.md ├── PreviewText.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ ├── PreviewText.xcscheme │ └── Text Previewer.xcscheme ├── PreviewText ├── AppDelegate.swift ├── Base.lproj │ └── MainMenu.xib ├── Constants.swift ├── GenericColorExtensions.swift ├── GenericExtensions.swift ├── Info.plist ├── PMFont.swift ├── PTFontExtensions.swift ├── PreviewText.entitlements └── text-sample.txt ├── README.md ├── Text Previewer ├── Base.lproj │ └── PreviewViewController.xib ├── Common.swift ├── Info.plist ├── PreviewProvider.swift ├── PreviewViewController.swift └── Text_Previewer.entitlements ├── Text Thumbnailer ├── Info.plist ├── Text_Thumbnailer.entitlements └── ThumbnailProvider.swift └── qr-code.png /.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 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | ### Copyright © 2025, Tony Smith (@smittytone) 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /PreviewText.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3B1FA41E28C1242E001DA63C /* QuickLookThumbnailing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B1FA41D28C1242E001DA63C /* QuickLookThumbnailing.framework */; }; 11 | 3B1FA41F28C1242E001DA63C /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B67EBA328BCB3610048F8AE /* Quartz.framework */; }; 12 | 3B1FA42228C1242E001DA63C /* ThumbnailProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B1FA42128C1242E001DA63C /* ThumbnailProvider.swift */; }; 13 | 3B1FA42728C1242E001DA63C /* Text Thumbnailer.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3B1FA41C28C1242E001DA63C /* Text Thumbnailer.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 14 | 3B1FA42B28C124A1001DA63C /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B67EB9428BCB0A20048F8AE /* Constants.swift */; }; 15 | 3B1FA42E28C12DB2001DA63C /* GenericColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B67EB9A28BCB3070048F8AE /* GenericColorExtensions.swift */; }; 16 | 3B59D173294B4FDC0011F048 /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B67EBB828BCB5D70048F8AE /* Common.swift */; }; 17 | 3B59D175294B54780011F048 /* text-sample.txt in Resources */ = {isa = PBXBuildFile; fileRef = 3B59D174294B54780011F048 /* text-sample.txt */; }; 18 | 3B67EB6528BCAEEA0048F8AE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B67EB6428BCAEEA0048F8AE /* AppDelegate.swift */; }; 19 | 3B67EB6A28BCAEEB0048F8AE /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3B67EB6828BCAEEB0048F8AE /* MainMenu.xib */; }; 20 | 3B67EB9228BCB0810048F8AE /* GenericExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B67EB9128BCB0810048F8AE /* GenericExtensions.swift */; }; 21 | 3B67EB9528BCB0A20048F8AE /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B67EB9428BCB0A20048F8AE /* Constants.swift */; }; 22 | 3B67EB9628BCB0A20048F8AE /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B67EB9428BCB0A20048F8AE /* Constants.swift */; }; 23 | 3B67EB9828BCB1DF0048F8AE /* PMFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B67EB9728BCB1DF0048F8AE /* PMFont.swift */; }; 24 | 3B67EB9928BCB1DF0048F8AE /* PMFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B67EB9728BCB1DF0048F8AE /* PMFont.swift */; }; 25 | 3B67EB9B28BCB3070048F8AE /* GenericColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B67EB9A28BCB3070048F8AE /* GenericColorExtensions.swift */; }; 26 | 3B67EB9C28BCB3070048F8AE /* GenericColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B67EB9A28BCB3070048F8AE /* GenericColorExtensions.swift */; }; 27 | 3B67EBA428BCB3610048F8AE /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B67EBA328BCB3610048F8AE /* Quartz.framework */; }; 28 | 3B67EBA728BCB3610048F8AE /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B67EBA628BCB3610048F8AE /* PreviewViewController.swift */; }; 29 | 3B67EBAC28BCB3610048F8AE /* PreviewViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3B67EBAA28BCB3610048F8AE /* PreviewViewController.xib */; }; 30 | 3B67EBB128BCB3610048F8AE /* Text Previewer.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3B67EBA128BCB3610048F8AE /* Text Previewer.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 31 | 3B67EBB928BCB5D70048F8AE /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B67EBB828BCB5D70048F8AE /* Common.swift */; }; 32 | 3B67EBBA28BCB61B0048F8AE /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B67EB9428BCB0A20048F8AE /* Constants.swift */; }; 33 | 3B67EBBC28BCB68B0048F8AE /* GenericColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B67EB9A28BCB3070048F8AE /* GenericColorExtensions.swift */; }; 34 | 3B744D162A8B6963009177B0 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 3B744D152A8B6963009177B0 /* README.md */; }; 35 | 3B744D192A8B6980009177B0 /* new in Resources */ = {isa = PBXBuildFile; fileRef = 3B744D172A8B6980009177B0 /* new */; }; 36 | 3B744D1A2A8B6980009177B0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3B744D182A8B6980009177B0 /* Assets.xcassets */; }; 37 | 3B744D1D2A8B69B2009177B0 /* REPLACE_WITH_YOUR_CODES.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B744D1B2A8B69B2009177B0 /* REPLACE_WITH_YOUR_CODES.swift */; }; 38 | 3B744D1E2A8B69B2009177B0 /* REPLACE_WITH_YOUR_FUNCTIONS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B744D1C2A8B69B2009177B0 /* REPLACE_WITH_YOUR_FUNCTIONS.swift */; }; 39 | 3B744D1F2A8B69C1009177B0 /* REPLACE_WITH_YOUR_CODES.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B744D1B2A8B69B2009177B0 /* REPLACE_WITH_YOUR_CODES.swift */; }; 40 | 3B744D202A8B69C2009177B0 /* REPLACE_WITH_YOUR_CODES.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B744D1B2A8B69B2009177B0 /* REPLACE_WITH_YOUR_CODES.swift */; }; 41 | 3B8DC83128F1C41C004328F7 /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B67EBB828BCB5D70048F8AE /* Common.swift */; }; 42 | 3BAE671928D4D161001532BD /* PTFontExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAE671828D4D161001532BD /* PTFontExtensions.swift */; }; 43 | /* End PBXBuildFile section */ 44 | 45 | /* Begin PBXContainerItemProxy section */ 46 | 3B1FA42528C1242E001DA63C /* PBXContainerItemProxy */ = { 47 | isa = PBXContainerItemProxy; 48 | containerPortal = 3B67EB5928BCAEEA0048F8AE /* Project object */; 49 | proxyType = 1; 50 | remoteGlobalIDString = 3B1FA41B28C1242E001DA63C; 51 | remoteInfo = "JSON Thumbnailer"; 52 | }; 53 | 3B67EB7128BCAEEB0048F8AE /* PBXContainerItemProxy */ = { 54 | isa = PBXContainerItemProxy; 55 | containerPortal = 3B67EB5928BCAEEA0048F8AE /* Project object */; 56 | proxyType = 1; 57 | remoteGlobalIDString = 3B67EB6028BCAEEA0048F8AE; 58 | remoteInfo = PreviewJson; 59 | }; 60 | 3B67EB7B28BCAEEB0048F8AE /* PBXContainerItemProxy */ = { 61 | isa = PBXContainerItemProxy; 62 | containerPortal = 3B67EB5928BCAEEA0048F8AE /* Project object */; 63 | proxyType = 1; 64 | remoteGlobalIDString = 3B67EB6028BCAEEA0048F8AE; 65 | remoteInfo = PreviewJson; 66 | }; 67 | 3B67EBAF28BCB3610048F8AE /* PBXContainerItemProxy */ = { 68 | isa = PBXContainerItemProxy; 69 | containerPortal = 3B67EB5928BCAEEA0048F8AE /* Project object */; 70 | proxyType = 1; 71 | remoteGlobalIDString = 3B67EBA028BCB3610048F8AE; 72 | remoteInfo = "JSON Previewwer"; 73 | }; 74 | /* End PBXContainerItemProxy section */ 75 | 76 | /* Begin PBXCopyFilesBuildPhase section */ 77 | 3B67EBB528BCB3610048F8AE /* Embed Foundation Extensions */ = { 78 | isa = PBXCopyFilesBuildPhase; 79 | buildActionMask = 2147483647; 80 | dstPath = ""; 81 | dstSubfolderSpec = 13; 82 | files = ( 83 | 3B67EBB128BCB3610048F8AE /* Text Previewer.appex in Embed Foundation Extensions */, 84 | 3B1FA42728C1242E001DA63C /* Text Thumbnailer.appex in Embed Foundation Extensions */, 85 | ); 86 | name = "Embed Foundation Extensions"; 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | /* End PBXCopyFilesBuildPhase section */ 90 | 91 | /* Begin PBXFileReference section */ 92 | 3B1E84892D371E480034D2EC /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; }; 93 | 3B1FA41C28C1242E001DA63C /* Text Thumbnailer.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Text Thumbnailer.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 94 | 3B1FA41D28C1242E001DA63C /* QuickLookThumbnailing.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLookThumbnailing.framework; path = System/Library/Frameworks/QuickLookThumbnailing.framework; sourceTree = SDKROOT; }; 95 | 3B1FA42128C1242E001DA63C /* ThumbnailProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailProvider.swift; sourceTree = ""; }; 96 | 3B1FA42328C1242E001DA63C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 97 | 3B1FA42428C1242E001DA63C /* Text_Thumbnailer.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Text_Thumbnailer.entitlements; sourceTree = ""; }; 98 | 3B59D174294B54780011F048 /* text-sample.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "text-sample.txt"; sourceTree = ""; }; 99 | 3B67EB6128BCAEEA0048F8AE /* PreviewText.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PreviewText.app; sourceTree = BUILT_PRODUCTS_DIR; }; 100 | 3B67EB6428BCAEEA0048F8AE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 101 | 3B67EB6928BCAEEB0048F8AE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 102 | 3B67EB6B28BCAEEB0048F8AE /* PreviewText.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PreviewText.entitlements; sourceTree = ""; }; 103 | 3B67EB7028BCAEEB0048F8AE /* PreviewTextTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PreviewTextTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 104 | 3B67EB7A28BCAEEB0048F8AE /* PreviewTextUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PreviewTextUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 105 | 3B67EB9128BCB0810048F8AE /* GenericExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenericExtensions.swift; sourceTree = ""; }; 106 | 3B67EB9428BCB0A20048F8AE /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 107 | 3B67EB9728BCB1DF0048F8AE /* PMFont.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PMFont.swift; sourceTree = ""; }; 108 | 3B67EB9A28BCB3070048F8AE /* GenericColorExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenericColorExtensions.swift; sourceTree = ""; }; 109 | 3B67EBA128BCB3610048F8AE /* Text Previewer.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Text Previewer.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 110 | 3B67EBA328BCB3610048F8AE /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; }; 111 | 3B67EBA628BCB3610048F8AE /* PreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewViewController.swift; sourceTree = ""; }; 112 | 3B67EBAB28BCB3610048F8AE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/PreviewViewController.xib; sourceTree = ""; }; 113 | 3B67EBAD28BCB3610048F8AE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 114 | 3B67EBAE28BCB3610048F8AE /* Text_Previewer.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Text_Previewer.entitlements; sourceTree = ""; }; 115 | 3B67EBB828BCB5D70048F8AE /* Common.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = ""; }; 116 | 3B744D152A8B6963009177B0 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 117 | 3B744D172A8B6980009177B0 /* new */ = {isa = PBXFileReference; lastKnownFileType = folder; name = new; path = "/Users/smitty/Library/Mobile Documents/com~apple~CloudDocs/Documents/Programming/PreviewText/new"; sourceTree = ""; }; 118 | 3B744D182A8B6980009177B0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = "/Users/smitty/Library/Mobile Documents/com~apple~CloudDocs/Documents/Programming/PreviewText/Assets.xcassets"; sourceTree = ""; }; 119 | 3B744D1B2A8B69B2009177B0 /* REPLACE_WITH_YOUR_CODES.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = REPLACE_WITH_YOUR_CODES.swift; path = "/Users/smitty/Library/Mobile Documents/com~apple~CloudDocs/Documents/Programming/PreviewApps/REPLACE_WITH_YOUR_CODES.swift"; sourceTree = ""; }; 120 | 3B744D1C2A8B69B2009177B0 /* REPLACE_WITH_YOUR_FUNCTIONS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = REPLACE_WITH_YOUR_FUNCTIONS.swift; path = "/Users/smitty/Library/Mobile Documents/com~apple~CloudDocs/Documents/Programming/PreviewApps/REPLACE_WITH_YOUR_FUNCTIONS.swift"; sourceTree = ""; }; 121 | 3BAE671828D4D161001532BD /* PTFontExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PTFontExtensions.swift; sourceTree = ""; }; 122 | 3BE355B928BCD07300523296 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 123 | /* End PBXFileReference section */ 124 | 125 | /* Begin PBXFrameworksBuildPhase section */ 126 | 3B1FA41928C1242E001DA63C /* Frameworks */ = { 127 | isa = PBXFrameworksBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | 3B1FA41E28C1242E001DA63C /* QuickLookThumbnailing.framework in Frameworks */, 131 | 3B1FA41F28C1242E001DA63C /* Quartz.framework in Frameworks */, 132 | ); 133 | runOnlyForDeploymentPostprocessing = 0; 134 | }; 135 | 3B67EB5E28BCAEEA0048F8AE /* Frameworks */ = { 136 | isa = PBXFrameworksBuildPhase; 137 | buildActionMask = 2147483647; 138 | files = ( 139 | ); 140 | runOnlyForDeploymentPostprocessing = 0; 141 | }; 142 | 3B67EB6D28BCAEEB0048F8AE /* Frameworks */ = { 143 | isa = PBXFrameworksBuildPhase; 144 | buildActionMask = 2147483647; 145 | files = ( 146 | ); 147 | runOnlyForDeploymentPostprocessing = 0; 148 | }; 149 | 3B67EB7728BCAEEB0048F8AE /* Frameworks */ = { 150 | isa = PBXFrameworksBuildPhase; 151 | buildActionMask = 2147483647; 152 | files = ( 153 | ); 154 | runOnlyForDeploymentPostprocessing = 0; 155 | }; 156 | 3B67EB9E28BCB3610048F8AE /* Frameworks */ = { 157 | isa = PBXFrameworksBuildPhase; 158 | buildActionMask = 2147483647; 159 | files = ( 160 | 3B67EBA428BCB3610048F8AE /* Quartz.framework in Frameworks */, 161 | ); 162 | runOnlyForDeploymentPostprocessing = 0; 163 | }; 164 | /* End PBXFrameworksBuildPhase section */ 165 | 166 | /* Begin PBXGroup section */ 167 | 3B1FA42028C1242E001DA63C /* Text Thumbnailer */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | 3B1FA42128C1242E001DA63C /* ThumbnailProvider.swift */, 171 | 3B1FA42428C1242E001DA63C /* Text_Thumbnailer.entitlements */, 172 | 3B1FA42328C1242E001DA63C /* Info.plist */, 173 | ); 174 | path = "Text Thumbnailer"; 175 | sourceTree = ""; 176 | }; 177 | 3B67EB5828BCAEEA0048F8AE = { 178 | isa = PBXGroup; 179 | children = ( 180 | 3B67EB6328BCAEEA0048F8AE /* PreviewText */, 181 | 3B67EBA528BCB3610048F8AE /* Text Previewer */, 182 | 3B1FA42028C1242E001DA63C /* Text Thumbnailer */, 183 | 3B67EBA228BCB3610048F8AE /* Frameworks */, 184 | 3B744D142A8B6950009177B0 /* Misc */, 185 | 3B67EB6228BCAEEA0048F8AE /* Products */, 186 | ); 187 | sourceTree = ""; 188 | }; 189 | 3B67EB6228BCAEEA0048F8AE /* Products */ = { 190 | isa = PBXGroup; 191 | children = ( 192 | 3B67EB6128BCAEEA0048F8AE /* PreviewText.app */, 193 | 3B67EB7028BCAEEB0048F8AE /* PreviewTextTests.xctest */, 194 | 3B67EB7A28BCAEEB0048F8AE /* PreviewTextUITests.xctest */, 195 | 3B67EBA128BCB3610048F8AE /* Text Previewer.appex */, 196 | 3B1FA41C28C1242E001DA63C /* Text Thumbnailer.appex */, 197 | ); 198 | name = Products; 199 | sourceTree = ""; 200 | }; 201 | 3B67EB6328BCAEEA0048F8AE /* PreviewText */ = { 202 | isa = PBXGroup; 203 | children = ( 204 | 3B67EB9428BCB0A20048F8AE /* Constants.swift */, 205 | 3B67EB6428BCAEEA0048F8AE /* AppDelegate.swift */, 206 | 3BAE671828D4D161001532BD /* PTFontExtensions.swift */, 207 | 3B67EB9128BCB0810048F8AE /* GenericExtensions.swift */, 208 | 3B67EB9A28BCB3070048F8AE /* GenericColorExtensions.swift */, 209 | 3B67EB9728BCB1DF0048F8AE /* PMFont.swift */, 210 | 3B67EB6828BCAEEB0048F8AE /* MainMenu.xib */, 211 | 3BE355B928BCD07300523296 /* Info.plist */, 212 | 3B59D174294B54780011F048 /* text-sample.txt */, 213 | 3B67EB6B28BCAEEB0048F8AE /* PreviewText.entitlements */, 214 | 3B744D132A8B691C009177B0 /* YOU MUST SUPPLY */, 215 | ); 216 | path = PreviewText; 217 | sourceTree = ""; 218 | }; 219 | 3B67EBA228BCB3610048F8AE /* Frameworks */ = { 220 | isa = PBXGroup; 221 | children = ( 222 | 3B67EBA328BCB3610048F8AE /* Quartz.framework */, 223 | 3B1FA41D28C1242E001DA63C /* QuickLookThumbnailing.framework */, 224 | ); 225 | name = Frameworks; 226 | sourceTree = ""; 227 | }; 228 | 3B67EBA528BCB3610048F8AE /* Text Previewer */ = { 229 | isa = PBXGroup; 230 | children = ( 231 | 3B67EBA628BCB3610048F8AE /* PreviewViewController.swift */, 232 | 3B67EBB828BCB5D70048F8AE /* Common.swift */, 233 | 3B67EBAA28BCB3610048F8AE /* PreviewViewController.xib */, 234 | 3B67EBAE28BCB3610048F8AE /* Text_Previewer.entitlements */, 235 | 3B67EBAD28BCB3610048F8AE /* Info.plist */, 236 | ); 237 | path = "Text Previewer"; 238 | sourceTree = ""; 239 | }; 240 | 3B744D132A8B691C009177B0 /* YOU MUST SUPPLY */ = { 241 | isa = PBXGroup; 242 | children = ( 243 | 3B744D1C2A8B69B2009177B0 /* REPLACE_WITH_YOUR_FUNCTIONS.swift */, 244 | 3B744D1B2A8B69B2009177B0 /* REPLACE_WITH_YOUR_CODES.swift */, 245 | 3B744D182A8B6980009177B0 /* Assets.xcassets */, 246 | 3B744D172A8B6980009177B0 /* new */, 247 | ); 248 | name = "YOU MUST SUPPLY"; 249 | sourceTree = ""; 250 | }; 251 | 3B744D142A8B6950009177B0 /* Misc */ = { 252 | isa = PBXGroup; 253 | children = ( 254 | 3B744D152A8B6963009177B0 /* README.md */, 255 | 3B1E84892D371E480034D2EC /* LICENSE.md */, 256 | ); 257 | name = Misc; 258 | sourceTree = ""; 259 | }; 260 | /* End PBXGroup section */ 261 | 262 | /* Begin PBXNativeTarget section */ 263 | 3B1FA41B28C1242E001DA63C /* Text Thumbnailer */ = { 264 | isa = PBXNativeTarget; 265 | buildConfigurationList = 3B1FA42A28C1242E001DA63C /* Build configuration list for PBXNativeTarget "Text Thumbnailer" */; 266 | buildPhases = ( 267 | 3B1FA41828C1242E001DA63C /* Sources */, 268 | 3B1FA41928C1242E001DA63C /* Frameworks */, 269 | 3B1FA41A28C1242E001DA63C /* Resources */, 270 | ); 271 | buildRules = ( 272 | ); 273 | dependencies = ( 274 | ); 275 | name = "Text Thumbnailer"; 276 | productName = "JSON Thumbnailer"; 277 | productReference = 3B1FA41C28C1242E001DA63C /* Text Thumbnailer.appex */; 278 | productType = "com.apple.product-type.app-extension"; 279 | }; 280 | 3B67EB6028BCAEEA0048F8AE /* PreviewText */ = { 281 | isa = PBXNativeTarget; 282 | buildConfigurationList = 3B67EB8428BCAEEB0048F8AE /* Build configuration list for PBXNativeTarget "PreviewText" */; 283 | buildPhases = ( 284 | 3B67EB5D28BCAEEA0048F8AE /* Sources */, 285 | 3B67EB5E28BCAEEA0048F8AE /* Frameworks */, 286 | 3B67EB5F28BCAEEA0048F8AE /* Resources */, 287 | 3B67EBB528BCB3610048F8AE /* Embed Foundation Extensions */, 288 | ); 289 | buildRules = ( 290 | ); 291 | dependencies = ( 292 | 3B67EBB028BCB3610048F8AE /* PBXTargetDependency */, 293 | 3B1FA42628C1242E001DA63C /* PBXTargetDependency */, 294 | ); 295 | name = PreviewText; 296 | productName = PreviewJson; 297 | productReference = 3B67EB6128BCAEEA0048F8AE /* PreviewText.app */; 298 | productType = "com.apple.product-type.application"; 299 | }; 300 | 3B67EB6F28BCAEEB0048F8AE /* PreviewTextTests */ = { 301 | isa = PBXNativeTarget; 302 | buildConfigurationList = 3B67EB8728BCAEEB0048F8AE /* Build configuration list for PBXNativeTarget "PreviewTextTests" */; 303 | buildPhases = ( 304 | 3B67EB6C28BCAEEB0048F8AE /* Sources */, 305 | 3B67EB6D28BCAEEB0048F8AE /* Frameworks */, 306 | 3B67EB6E28BCAEEB0048F8AE /* Resources */, 307 | ); 308 | buildRules = ( 309 | ); 310 | dependencies = ( 311 | 3B67EB7228BCAEEB0048F8AE /* PBXTargetDependency */, 312 | ); 313 | name = PreviewTextTests; 314 | productName = PreviewJsonTests; 315 | productReference = 3B67EB7028BCAEEB0048F8AE /* PreviewTextTests.xctest */; 316 | productType = "com.apple.product-type.bundle.unit-test"; 317 | }; 318 | 3B67EB7928BCAEEB0048F8AE /* PreviewTextUITests */ = { 319 | isa = PBXNativeTarget; 320 | buildConfigurationList = 3B67EB8A28BCAEEB0048F8AE /* Build configuration list for PBXNativeTarget "PreviewTextUITests" */; 321 | buildPhases = ( 322 | 3B67EB7628BCAEEB0048F8AE /* Sources */, 323 | 3B67EB7728BCAEEB0048F8AE /* Frameworks */, 324 | 3B67EB7828BCAEEB0048F8AE /* Resources */, 325 | ); 326 | buildRules = ( 327 | ); 328 | dependencies = ( 329 | 3B67EB7C28BCAEEB0048F8AE /* PBXTargetDependency */, 330 | ); 331 | name = PreviewTextUITests; 332 | productName = PreviewJsonUITests; 333 | productReference = 3B67EB7A28BCAEEB0048F8AE /* PreviewTextUITests.xctest */; 334 | productType = "com.apple.product-type.bundle.ui-testing"; 335 | }; 336 | 3B67EBA028BCB3610048F8AE /* Text Previewer */ = { 337 | isa = PBXNativeTarget; 338 | buildConfigurationList = 3B67EBB228BCB3610048F8AE /* Build configuration list for PBXNativeTarget "Text Previewer" */; 339 | buildPhases = ( 340 | 3B67EB9D28BCB3610048F8AE /* Sources */, 341 | 3B67EB9E28BCB3610048F8AE /* Frameworks */, 342 | 3B67EB9F28BCB3610048F8AE /* Resources */, 343 | ); 344 | buildRules = ( 345 | ); 346 | dependencies = ( 347 | ); 348 | name = "Text Previewer"; 349 | productName = "JSON Previewwer"; 350 | productReference = 3B67EBA128BCB3610048F8AE /* Text Previewer.appex */; 351 | productType = "com.apple.product-type.app-extension"; 352 | }; 353 | /* End PBXNativeTarget section */ 354 | 355 | /* Begin PBXProject section */ 356 | 3B67EB5928BCAEEA0048F8AE /* Project object */ = { 357 | isa = PBXProject; 358 | attributes = { 359 | BuildIndependentTargetsInParallel = 1; 360 | LastSwiftUpdateCheck = 1340; 361 | LastUpgradeCheck = 1600; 362 | TargetAttributes = { 363 | 3B1FA41B28C1242E001DA63C = { 364 | CreatedOnToolsVersion = 13.4.1; 365 | }; 366 | 3B67EB6028BCAEEA0048F8AE = { 367 | CreatedOnToolsVersion = 13.4.1; 368 | }; 369 | 3B67EB6F28BCAEEB0048F8AE = { 370 | CreatedOnToolsVersion = 13.4.1; 371 | TestTargetID = 3B67EB6028BCAEEA0048F8AE; 372 | }; 373 | 3B67EB7928BCAEEB0048F8AE = { 374 | CreatedOnToolsVersion = 13.4.1; 375 | TestTargetID = 3B67EB6028BCAEEA0048F8AE; 376 | }; 377 | 3B67EBA028BCB3610048F8AE = { 378 | CreatedOnToolsVersion = 13.4.1; 379 | }; 380 | }; 381 | }; 382 | buildConfigurationList = 3B67EB5C28BCAEEA0048F8AE /* Build configuration list for PBXProject "PreviewText" */; 383 | compatibilityVersion = "Xcode 13.0"; 384 | developmentRegion = en; 385 | hasScannedForEncodings = 0; 386 | knownRegions = ( 387 | en, 388 | Base, 389 | ); 390 | mainGroup = 3B67EB5828BCAEEA0048F8AE; 391 | productRefGroup = 3B67EB6228BCAEEA0048F8AE /* Products */; 392 | projectDirPath = ""; 393 | projectRoot = ""; 394 | targets = ( 395 | 3B67EB6028BCAEEA0048F8AE /* PreviewText */, 396 | 3B67EBA028BCB3610048F8AE /* Text Previewer */, 397 | 3B1FA41B28C1242E001DA63C /* Text Thumbnailer */, 398 | 3B67EB6F28BCAEEB0048F8AE /* PreviewTextTests */, 399 | 3B67EB7928BCAEEB0048F8AE /* PreviewTextUITests */, 400 | ); 401 | }; 402 | /* End PBXProject section */ 403 | 404 | /* Begin PBXResourcesBuildPhase section */ 405 | 3B1FA41A28C1242E001DA63C /* Resources */ = { 406 | isa = PBXResourcesBuildPhase; 407 | buildActionMask = 2147483647; 408 | files = ( 409 | ); 410 | runOnlyForDeploymentPostprocessing = 0; 411 | }; 412 | 3B67EB5F28BCAEEA0048F8AE /* Resources */ = { 413 | isa = PBXResourcesBuildPhase; 414 | buildActionMask = 2147483647; 415 | files = ( 416 | 3B59D175294B54780011F048 /* text-sample.txt in Resources */, 417 | 3B744D192A8B6980009177B0 /* new in Resources */, 418 | 3B744D162A8B6963009177B0 /* README.md in Resources */, 419 | 3B67EB6A28BCAEEB0048F8AE /* MainMenu.xib in Resources */, 420 | 3B744D1A2A8B6980009177B0 /* Assets.xcassets in Resources */, 421 | ); 422 | runOnlyForDeploymentPostprocessing = 0; 423 | }; 424 | 3B67EB6E28BCAEEB0048F8AE /* Resources */ = { 425 | isa = PBXResourcesBuildPhase; 426 | buildActionMask = 2147483647; 427 | files = ( 428 | ); 429 | runOnlyForDeploymentPostprocessing = 0; 430 | }; 431 | 3B67EB7828BCAEEB0048F8AE /* Resources */ = { 432 | isa = PBXResourcesBuildPhase; 433 | buildActionMask = 2147483647; 434 | files = ( 435 | ); 436 | runOnlyForDeploymentPostprocessing = 0; 437 | }; 438 | 3B67EB9F28BCB3610048F8AE /* Resources */ = { 439 | isa = PBXResourcesBuildPhase; 440 | buildActionMask = 2147483647; 441 | files = ( 442 | 3B67EBAC28BCB3610048F8AE /* PreviewViewController.xib in Resources */, 443 | ); 444 | runOnlyForDeploymentPostprocessing = 0; 445 | }; 446 | /* End PBXResourcesBuildPhase section */ 447 | 448 | /* Begin PBXSourcesBuildPhase section */ 449 | 3B1FA41828C1242E001DA63C /* Sources */ = { 450 | isa = PBXSourcesBuildPhase; 451 | buildActionMask = 2147483647; 452 | files = ( 453 | 3B1FA42E28C12DB2001DA63C /* GenericColorExtensions.swift in Sources */, 454 | 3B744D202A8B69C2009177B0 /* REPLACE_WITH_YOUR_CODES.swift in Sources */, 455 | 3B1FA42B28C124A1001DA63C /* Constants.swift in Sources */, 456 | 3B8DC83128F1C41C004328F7 /* Common.swift in Sources */, 457 | 3B1FA42228C1242E001DA63C /* ThumbnailProvider.swift in Sources */, 458 | ); 459 | runOnlyForDeploymentPostprocessing = 0; 460 | }; 461 | 3B67EB5D28BCAEEA0048F8AE /* Sources */ = { 462 | isa = PBXSourcesBuildPhase; 463 | buildActionMask = 2147483647; 464 | files = ( 465 | 3B59D173294B4FDC0011F048 /* Common.swift in Sources */, 466 | 3B67EB9828BCB1DF0048F8AE /* PMFont.swift in Sources */, 467 | 3B744D1D2A8B69B2009177B0 /* REPLACE_WITH_YOUR_CODES.swift in Sources */, 468 | 3B67EB6528BCAEEA0048F8AE /* AppDelegate.swift in Sources */, 469 | 3B67EB9B28BCB3070048F8AE /* GenericColorExtensions.swift in Sources */, 470 | 3B67EB9228BCB0810048F8AE /* GenericExtensions.swift in Sources */, 471 | 3B744D1E2A8B69B2009177B0 /* REPLACE_WITH_YOUR_FUNCTIONS.swift in Sources */, 472 | 3BAE671928D4D161001532BD /* PTFontExtensions.swift in Sources */, 473 | 3B67EB9528BCB0A20048F8AE /* Constants.swift in Sources */, 474 | ); 475 | runOnlyForDeploymentPostprocessing = 0; 476 | }; 477 | 3B67EB6C28BCAEEB0048F8AE /* Sources */ = { 478 | isa = PBXSourcesBuildPhase; 479 | buildActionMask = 2147483647; 480 | files = ( 481 | 3B67EB9C28BCB3070048F8AE /* GenericColorExtensions.swift in Sources */, 482 | 3B67EB9628BCB0A20048F8AE /* Constants.swift in Sources */, 483 | 3B67EB9928BCB1DF0048F8AE /* PMFont.swift in Sources */, 484 | ); 485 | runOnlyForDeploymentPostprocessing = 0; 486 | }; 487 | 3B67EB7628BCAEEB0048F8AE /* Sources */ = { 488 | isa = PBXSourcesBuildPhase; 489 | buildActionMask = 2147483647; 490 | files = ( 491 | ); 492 | runOnlyForDeploymentPostprocessing = 0; 493 | }; 494 | 3B67EB9D28BCB3610048F8AE /* Sources */ = { 495 | isa = PBXSourcesBuildPhase; 496 | buildActionMask = 2147483647; 497 | files = ( 498 | 3B67EBBC28BCB68B0048F8AE /* GenericColorExtensions.swift in Sources */, 499 | 3B744D1F2A8B69C1009177B0 /* REPLACE_WITH_YOUR_CODES.swift in Sources */, 500 | 3B67EBBA28BCB61B0048F8AE /* Constants.swift in Sources */, 501 | 3B67EBA728BCB3610048F8AE /* PreviewViewController.swift in Sources */, 502 | 3B67EBB928BCB5D70048F8AE /* Common.swift in Sources */, 503 | ); 504 | runOnlyForDeploymentPostprocessing = 0; 505 | }; 506 | /* End PBXSourcesBuildPhase section */ 507 | 508 | /* Begin PBXTargetDependency section */ 509 | 3B1FA42628C1242E001DA63C /* PBXTargetDependency */ = { 510 | isa = PBXTargetDependency; 511 | target = 3B1FA41B28C1242E001DA63C /* Text Thumbnailer */; 512 | targetProxy = 3B1FA42528C1242E001DA63C /* PBXContainerItemProxy */; 513 | }; 514 | 3B67EB7228BCAEEB0048F8AE /* PBXTargetDependency */ = { 515 | isa = PBXTargetDependency; 516 | target = 3B67EB6028BCAEEA0048F8AE /* PreviewText */; 517 | targetProxy = 3B67EB7128BCAEEB0048F8AE /* PBXContainerItemProxy */; 518 | }; 519 | 3B67EB7C28BCAEEB0048F8AE /* PBXTargetDependency */ = { 520 | isa = PBXTargetDependency; 521 | target = 3B67EB6028BCAEEA0048F8AE /* PreviewText */; 522 | targetProxy = 3B67EB7B28BCAEEB0048F8AE /* PBXContainerItemProxy */; 523 | }; 524 | 3B67EBB028BCB3610048F8AE /* PBXTargetDependency */ = { 525 | isa = PBXTargetDependency; 526 | target = 3B67EBA028BCB3610048F8AE /* Text Previewer */; 527 | targetProxy = 3B67EBAF28BCB3610048F8AE /* PBXContainerItemProxy */; 528 | }; 529 | /* End PBXTargetDependency section */ 530 | 531 | /* Begin PBXVariantGroup section */ 532 | 3B67EB6828BCAEEB0048F8AE /* MainMenu.xib */ = { 533 | isa = PBXVariantGroup; 534 | children = ( 535 | 3B67EB6928BCAEEB0048F8AE /* Base */, 536 | ); 537 | name = MainMenu.xib; 538 | sourceTree = ""; 539 | }; 540 | 3B67EBAA28BCB3610048F8AE /* PreviewViewController.xib */ = { 541 | isa = PBXVariantGroup; 542 | children = ( 543 | 3B67EBAB28BCB3610048F8AE /* Base */, 544 | ); 545 | name = PreviewViewController.xib; 546 | sourceTree = ""; 547 | }; 548 | /* End PBXVariantGroup section */ 549 | 550 | /* Begin XCBuildConfiguration section */ 551 | 3B1FA42828C1242E001DA63C /* Debug */ = { 552 | isa = XCBuildConfiguration; 553 | buildSettings = { 554 | CODE_SIGN_ENTITLEMENTS = "Text Thumbnailer/Text_Thumbnailer.entitlements"; 555 | CODE_SIGN_IDENTITY = "-"; 556 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 557 | CODE_SIGN_STYLE = Automatic; 558 | CURRENT_PROJECT_VERSION = 280; 559 | DEAD_CODE_STRIPPING = YES; 560 | DEVELOPMENT_TEAM = Y5J3K52DNA; 561 | ENABLE_HARDENED_RUNTIME = YES; 562 | GENERATE_INFOPLIST_FILE = YES; 563 | INFOPLIST_FILE = "Text Thumbnailer/Info.plist"; 564 | INFOPLIST_KEY_CFBundleDisplayName = "Text Thumbnailer"; 565 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 566 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Tony Smith. All rights reserved."; 567 | LD_RUNPATH_SEARCH_PATHS = ( 568 | "$(inherited)", 569 | "@executable_path/../Frameworks", 570 | "@executable_path/../../../../Frameworks", 571 | ); 572 | MACOSX_DEPLOYMENT_TARGET = 11.0; 573 | MARKETING_VERSION = 1.0.9; 574 | PRODUCT_BUNDLE_IDENTIFIER = com.bps.PreviewText.TextThumbnailer; 575 | PRODUCT_NAME = "$(TARGET_NAME)"; 576 | SKIP_INSTALL = YES; 577 | SWIFT_EMIT_LOC_STRINGS = YES; 578 | SWIFT_VERSION = 5.0; 579 | }; 580 | name = Debug; 581 | }; 582 | 3B1FA42928C1242E001DA63C /* Release */ = { 583 | isa = XCBuildConfiguration; 584 | buildSettings = { 585 | CODE_SIGN_ENTITLEMENTS = "Text Thumbnailer/Text_Thumbnailer.entitlements"; 586 | CODE_SIGN_IDENTITY = "-"; 587 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 588 | CODE_SIGN_STYLE = Automatic; 589 | CURRENT_PROJECT_VERSION = 280; 590 | DEAD_CODE_STRIPPING = YES; 591 | DEVELOPMENT_TEAM = Y5J3K52DNA; 592 | ENABLE_HARDENED_RUNTIME = YES; 593 | GENERATE_INFOPLIST_FILE = YES; 594 | INFOPLIST_FILE = "Text Thumbnailer/Info.plist"; 595 | INFOPLIST_KEY_CFBundleDisplayName = "Text Thumbnailer"; 596 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 597 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Tony Smith. All rights reserved."; 598 | LD_RUNPATH_SEARCH_PATHS = ( 599 | "$(inherited)", 600 | "@executable_path/../Frameworks", 601 | "@executable_path/../../../../Frameworks", 602 | ); 603 | MACOSX_DEPLOYMENT_TARGET = 11.0; 604 | MARKETING_VERSION = 1.0.9; 605 | PRODUCT_BUNDLE_IDENTIFIER = com.bps.PreviewText.TextThumbnailer; 606 | PRODUCT_NAME = "$(TARGET_NAME)"; 607 | SKIP_INSTALL = YES; 608 | SWIFT_EMIT_LOC_STRINGS = YES; 609 | SWIFT_VERSION = 5.0; 610 | }; 611 | name = Release; 612 | }; 613 | 3B67EB8228BCAEEB0048F8AE /* Debug */ = { 614 | isa = XCBuildConfiguration; 615 | buildSettings = { 616 | ALWAYS_SEARCH_USER_PATHS = NO; 617 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 618 | CLANG_ANALYZER_NONNULL = YES; 619 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 620 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 621 | CLANG_ENABLE_MODULES = YES; 622 | CLANG_ENABLE_OBJC_ARC = YES; 623 | CLANG_ENABLE_OBJC_WEAK = YES; 624 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 625 | CLANG_WARN_BOOL_CONVERSION = YES; 626 | CLANG_WARN_COMMA = YES; 627 | CLANG_WARN_CONSTANT_CONVERSION = YES; 628 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 629 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 630 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 631 | CLANG_WARN_EMPTY_BODY = YES; 632 | CLANG_WARN_ENUM_CONVERSION = YES; 633 | CLANG_WARN_INFINITE_RECURSION = YES; 634 | CLANG_WARN_INT_CONVERSION = YES; 635 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 636 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 637 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 638 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 639 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 640 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 641 | CLANG_WARN_STRICT_PROTOTYPES = YES; 642 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 643 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 644 | CLANG_WARN_UNREACHABLE_CODE = YES; 645 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 646 | COPY_PHASE_STRIP = NO; 647 | DEAD_CODE_STRIPPING = YES; 648 | DEBUG_INFORMATION_FORMAT = dwarf; 649 | ENABLE_STRICT_OBJC_MSGSEND = YES; 650 | ENABLE_TESTABILITY = YES; 651 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 652 | GCC_C_LANGUAGE_STANDARD = gnu11; 653 | GCC_DYNAMIC_NO_PIC = NO; 654 | GCC_NO_COMMON_BLOCKS = YES; 655 | GCC_OPTIMIZATION_LEVEL = 0; 656 | GCC_PREPROCESSOR_DEFINITIONS = ( 657 | "DEBUG=1", 658 | "$(inherited)", 659 | ); 660 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 661 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 662 | GCC_WARN_UNDECLARED_SELECTOR = YES; 663 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 664 | GCC_WARN_UNUSED_FUNCTION = YES; 665 | GCC_WARN_UNUSED_VARIABLE = YES; 666 | LD_MAP_FILE_PATH = "$(TARGET_TEMP_DIR)/$(PRODUCT_NAME)-LinkMap-$(CURRENT_VARIANT)-$(CURRENT_ARCH).txt"; 667 | MACOSX_DEPLOYMENT_TARGET = 11.0; 668 | MARKETING_VERSION = 1.0.7; 669 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 670 | MTL_FAST_MATH = YES; 671 | ONLY_ACTIVE_ARCH = YES; 672 | PRODUCT_BUNDLE_IDENTIFIER = com.bps.PreviewText; 673 | PRODUCT_NAME = PreviewText; 674 | SDKROOT = macosx; 675 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 676 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 677 | }; 678 | name = Debug; 679 | }; 680 | 3B67EB8328BCAEEB0048F8AE /* Release */ = { 681 | isa = XCBuildConfiguration; 682 | buildSettings = { 683 | ALWAYS_SEARCH_USER_PATHS = NO; 684 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 685 | CLANG_ANALYZER_NONNULL = YES; 686 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 687 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 688 | CLANG_ENABLE_MODULES = YES; 689 | CLANG_ENABLE_OBJC_ARC = YES; 690 | CLANG_ENABLE_OBJC_WEAK = YES; 691 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 692 | CLANG_WARN_BOOL_CONVERSION = YES; 693 | CLANG_WARN_COMMA = YES; 694 | CLANG_WARN_CONSTANT_CONVERSION = YES; 695 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 696 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 697 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 698 | CLANG_WARN_EMPTY_BODY = YES; 699 | CLANG_WARN_ENUM_CONVERSION = YES; 700 | CLANG_WARN_INFINITE_RECURSION = YES; 701 | CLANG_WARN_INT_CONVERSION = YES; 702 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 703 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 704 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 705 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 706 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 707 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 708 | CLANG_WARN_STRICT_PROTOTYPES = YES; 709 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 710 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 711 | CLANG_WARN_UNREACHABLE_CODE = YES; 712 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 713 | COPY_PHASE_STRIP = NO; 714 | DEAD_CODE_STRIPPING = YES; 715 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 716 | ENABLE_NS_ASSERTIONS = NO; 717 | ENABLE_STRICT_OBJC_MSGSEND = YES; 718 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 719 | GCC_C_LANGUAGE_STANDARD = gnu11; 720 | GCC_NO_COMMON_BLOCKS = YES; 721 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 722 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 723 | GCC_WARN_UNDECLARED_SELECTOR = YES; 724 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 725 | GCC_WARN_UNUSED_FUNCTION = YES; 726 | GCC_WARN_UNUSED_VARIABLE = YES; 727 | LD_MAP_FILE_PATH = "$(TARGET_TEMP_DIR)/$(PRODUCT_NAME)-LinkMap-$(CURRENT_VARIANT)-$(CURRENT_ARCH).txt"; 728 | MACOSX_DEPLOYMENT_TARGET = 11.0; 729 | MARKETING_VERSION = 1.0.7; 730 | MTL_ENABLE_DEBUG_INFO = NO; 731 | MTL_FAST_MATH = YES; 732 | PRODUCT_BUNDLE_IDENTIFIER = com.bps.PreviewText; 733 | PRODUCT_NAME = PreviewText; 734 | SDKROOT = macosx; 735 | SWIFT_COMPILATION_MODE = wholemodule; 736 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 737 | }; 738 | name = Release; 739 | }; 740 | 3B67EB8528BCAEEB0048F8AE /* Debug */ = { 741 | isa = XCBuildConfiguration; 742 | buildSettings = { 743 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 744 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 745 | CODE_SIGN_ENTITLEMENTS = PreviewText/PreviewText.entitlements; 746 | CODE_SIGN_IDENTITY = "-"; 747 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 748 | CODE_SIGN_STYLE = Automatic; 749 | COMBINE_HIDPI_IMAGES = YES; 750 | CURRENT_PROJECT_VERSION = 280; 751 | DEAD_CODE_STRIPPING = YES; 752 | DEVELOPMENT_TEAM = Y5J3K52DNA; 753 | ENABLE_HARDENED_RUNTIME = YES; 754 | GENERATE_INFOPLIST_FILE = YES; 755 | INFOPLIST_FILE = PreviewText/Info.plist; 756 | INFOPLIST_KEY_CFBundleDisplayName = PreviewText; 757 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 758 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Tony Smith. All rights reserved."; 759 | INFOPLIST_KEY_NSMainNibFile = MainMenu; 760 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 761 | LD_RUNPATH_SEARCH_PATHS = ( 762 | "$(inherited)", 763 | "@executable_path/../Frameworks", 764 | ); 765 | MACOSX_DEPLOYMENT_TARGET = 10.15; 766 | MARKETING_VERSION = 1.0.9; 767 | OTHER_SWIFT_FLAGS = "-D TARGET_IS_PT"; 768 | PRODUCT_BUNDLE_IDENTIFIER = com.bps.PreviewText; 769 | PRODUCT_NAME = "$(TARGET_NAME)"; 770 | PROVISIONING_PROFILE_SPECIFIER = ""; 771 | SWIFT_EMIT_LOC_STRINGS = NO; 772 | SWIFT_VERSION = 5.0; 773 | }; 774 | name = Debug; 775 | }; 776 | 3B67EB8628BCAEEB0048F8AE /* Release */ = { 777 | isa = XCBuildConfiguration; 778 | buildSettings = { 779 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 780 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 781 | CODE_SIGN_ENTITLEMENTS = PreviewText/PreviewText.entitlements; 782 | CODE_SIGN_IDENTITY = "-"; 783 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 784 | CODE_SIGN_STYLE = Automatic; 785 | COMBINE_HIDPI_IMAGES = YES; 786 | CURRENT_PROJECT_VERSION = 280; 787 | DEAD_CODE_STRIPPING = YES; 788 | DEVELOPMENT_TEAM = Y5J3K52DNA; 789 | ENABLE_HARDENED_RUNTIME = YES; 790 | GENERATE_INFOPLIST_FILE = YES; 791 | INFOPLIST_FILE = PreviewText/Info.plist; 792 | INFOPLIST_KEY_CFBundleDisplayName = PreviewText; 793 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 794 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Tony Smith. All rights reserved."; 795 | INFOPLIST_KEY_NSMainNibFile = MainMenu; 796 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 797 | LD_RUNPATH_SEARCH_PATHS = ( 798 | "$(inherited)", 799 | "@executable_path/../Frameworks", 800 | ); 801 | MACOSX_DEPLOYMENT_TARGET = 10.15; 802 | MARKETING_VERSION = 1.0.9; 803 | OTHER_SWIFT_FLAGS = "-D TARGET_IS_PT"; 804 | PRODUCT_BUNDLE_IDENTIFIER = com.bps.PreviewText; 805 | PRODUCT_NAME = "$(TARGET_NAME)"; 806 | PROVISIONING_PROFILE_SPECIFIER = ""; 807 | SWIFT_EMIT_LOC_STRINGS = NO; 808 | SWIFT_VERSION = 5.0; 809 | }; 810 | name = Release; 811 | }; 812 | 3B67EB8828BCAEEB0048F8AE /* Debug */ = { 813 | isa = XCBuildConfiguration; 814 | buildSettings = { 815 | BUNDLE_LOADER = "$(TEST_HOST)"; 816 | CODE_SIGN_STYLE = Automatic; 817 | CURRENT_PROJECT_VERSION = 280; 818 | DEAD_CODE_STRIPPING = YES; 819 | DEVELOPMENT_TEAM = Y5J3K52DNA; 820 | GENERATE_INFOPLIST_FILE = YES; 821 | MACOSX_DEPLOYMENT_TARGET = 12.3; 822 | MARKETING_VERSION = 1.0; 823 | PRODUCT_BUNDLE_IDENTIFIER = com.bps.PreviewTextTests; 824 | PRODUCT_NAME = "$(TARGET_NAME)"; 825 | SWIFT_EMIT_LOC_STRINGS = NO; 826 | SWIFT_VERSION = 5.0; 827 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PreviewText.app/Contents/MacOS/PreviewText"; 828 | }; 829 | name = Debug; 830 | }; 831 | 3B67EB8928BCAEEB0048F8AE /* Release */ = { 832 | isa = XCBuildConfiguration; 833 | buildSettings = { 834 | BUNDLE_LOADER = "$(TEST_HOST)"; 835 | CODE_SIGN_STYLE = Automatic; 836 | CURRENT_PROJECT_VERSION = 280; 837 | DEAD_CODE_STRIPPING = YES; 838 | DEVELOPMENT_TEAM = Y5J3K52DNA; 839 | GENERATE_INFOPLIST_FILE = YES; 840 | MACOSX_DEPLOYMENT_TARGET = 12.3; 841 | MARKETING_VERSION = 1.0; 842 | PRODUCT_BUNDLE_IDENTIFIER = com.bps.PreviewTextTests; 843 | PRODUCT_NAME = "$(TARGET_NAME)"; 844 | SWIFT_EMIT_LOC_STRINGS = NO; 845 | SWIFT_VERSION = 5.0; 846 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PreviewText.app/Contents/MacOS/PreviewText"; 847 | }; 848 | name = Release; 849 | }; 850 | 3B67EB8B28BCAEEB0048F8AE /* Debug */ = { 851 | isa = XCBuildConfiguration; 852 | buildSettings = { 853 | CODE_SIGN_STYLE = Automatic; 854 | CURRENT_PROJECT_VERSION = 280; 855 | DEAD_CODE_STRIPPING = YES; 856 | DEVELOPMENT_TEAM = Y5J3K52DNA; 857 | GENERATE_INFOPLIST_FILE = YES; 858 | MARKETING_VERSION = 1.0; 859 | PRODUCT_BUNDLE_IDENTIFIER = com.bps.PreviewTextUITests; 860 | PRODUCT_NAME = "$(TARGET_NAME)"; 861 | SWIFT_EMIT_LOC_STRINGS = NO; 862 | SWIFT_VERSION = 5.0; 863 | TEST_TARGET_NAME = PreviewText; 864 | }; 865 | name = Debug; 866 | }; 867 | 3B67EB8C28BCAEEB0048F8AE /* Release */ = { 868 | isa = XCBuildConfiguration; 869 | buildSettings = { 870 | CODE_SIGN_STYLE = Automatic; 871 | CURRENT_PROJECT_VERSION = 280; 872 | DEAD_CODE_STRIPPING = YES; 873 | DEVELOPMENT_TEAM = Y5J3K52DNA; 874 | GENERATE_INFOPLIST_FILE = YES; 875 | MARKETING_VERSION = 1.0; 876 | PRODUCT_BUNDLE_IDENTIFIER = com.bps.PreviewTextUITests; 877 | PRODUCT_NAME = "$(TARGET_NAME)"; 878 | SWIFT_EMIT_LOC_STRINGS = NO; 879 | SWIFT_VERSION = 5.0; 880 | TEST_TARGET_NAME = PreviewText; 881 | }; 882 | name = Release; 883 | }; 884 | 3B67EBB328BCB3610048F8AE /* Debug */ = { 885 | isa = XCBuildConfiguration; 886 | buildSettings = { 887 | CODE_SIGN_ENTITLEMENTS = "Text Previewer/Text_Previewer.entitlements"; 888 | CODE_SIGN_IDENTITY = "-"; 889 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 890 | CODE_SIGN_STYLE = Automatic; 891 | CURRENT_PROJECT_VERSION = 280; 892 | DEAD_CODE_STRIPPING = YES; 893 | DEVELOPMENT_TEAM = Y5J3K52DNA; 894 | ENABLE_HARDENED_RUNTIME = YES; 895 | GENERATE_INFOPLIST_FILE = YES; 896 | INFOPLIST_FILE = "Text Previewer/Info.plist"; 897 | INFOPLIST_KEY_CFBundleDisplayName = "Text Previewer"; 898 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 899 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Tony Smith. All rights reserved."; 900 | LD_RUNPATH_SEARCH_PATHS = ( 901 | "$(inherited)", 902 | "@executable_path/../Frameworks", 903 | "@executable_path/../../../../Frameworks", 904 | ); 905 | MACOSX_DEPLOYMENT_TARGET = 11.0; 906 | MARKETING_VERSION = 1.0.9; 907 | PRODUCT_BUNDLE_IDENTIFIER = com.bps.PreviewText.TextPreviewer; 908 | PRODUCT_NAME = "$(TARGET_NAME)"; 909 | SKIP_INSTALL = YES; 910 | SWIFT_EMIT_LOC_STRINGS = YES; 911 | SWIFT_VERSION = 5.0; 912 | }; 913 | name = Debug; 914 | }; 915 | 3B67EBB428BCB3610048F8AE /* Release */ = { 916 | isa = XCBuildConfiguration; 917 | buildSettings = { 918 | CODE_SIGN_ENTITLEMENTS = "Text Previewer/Text_Previewer.entitlements"; 919 | CODE_SIGN_IDENTITY = "-"; 920 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 921 | CODE_SIGN_STYLE = Automatic; 922 | CURRENT_PROJECT_VERSION = 280; 923 | DEAD_CODE_STRIPPING = YES; 924 | DEVELOPMENT_TEAM = Y5J3K52DNA; 925 | ENABLE_HARDENED_RUNTIME = YES; 926 | GENERATE_INFOPLIST_FILE = YES; 927 | INFOPLIST_FILE = "Text Previewer/Info.plist"; 928 | INFOPLIST_KEY_CFBundleDisplayName = "Text Previewer"; 929 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 930 | INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Tony Smith. All rights reserved."; 931 | LD_RUNPATH_SEARCH_PATHS = ( 932 | "$(inherited)", 933 | "@executable_path/../Frameworks", 934 | "@executable_path/../../../../Frameworks", 935 | ); 936 | MACOSX_DEPLOYMENT_TARGET = 11.0; 937 | MARKETING_VERSION = 1.0.9; 938 | PRODUCT_BUNDLE_IDENTIFIER = com.bps.PreviewText.TextPreviewer; 939 | PRODUCT_NAME = "$(TARGET_NAME)"; 940 | SKIP_INSTALL = YES; 941 | SWIFT_EMIT_LOC_STRINGS = YES; 942 | SWIFT_VERSION = 5.0; 943 | }; 944 | name = Release; 945 | }; 946 | /* End XCBuildConfiguration section */ 947 | 948 | /* Begin XCConfigurationList section */ 949 | 3B1FA42A28C1242E001DA63C /* Build configuration list for PBXNativeTarget "Text Thumbnailer" */ = { 950 | isa = XCConfigurationList; 951 | buildConfigurations = ( 952 | 3B1FA42828C1242E001DA63C /* Debug */, 953 | 3B1FA42928C1242E001DA63C /* Release */, 954 | ); 955 | defaultConfigurationIsVisible = 0; 956 | defaultConfigurationName = Release; 957 | }; 958 | 3B67EB5C28BCAEEA0048F8AE /* Build configuration list for PBXProject "PreviewText" */ = { 959 | isa = XCConfigurationList; 960 | buildConfigurations = ( 961 | 3B67EB8228BCAEEB0048F8AE /* Debug */, 962 | 3B67EB8328BCAEEB0048F8AE /* Release */, 963 | ); 964 | defaultConfigurationIsVisible = 0; 965 | defaultConfigurationName = Release; 966 | }; 967 | 3B67EB8428BCAEEB0048F8AE /* Build configuration list for PBXNativeTarget "PreviewText" */ = { 968 | isa = XCConfigurationList; 969 | buildConfigurations = ( 970 | 3B67EB8528BCAEEB0048F8AE /* Debug */, 971 | 3B67EB8628BCAEEB0048F8AE /* Release */, 972 | ); 973 | defaultConfigurationIsVisible = 0; 974 | defaultConfigurationName = Release; 975 | }; 976 | 3B67EB8728BCAEEB0048F8AE /* Build configuration list for PBXNativeTarget "PreviewTextTests" */ = { 977 | isa = XCConfigurationList; 978 | buildConfigurations = ( 979 | 3B67EB8828BCAEEB0048F8AE /* Debug */, 980 | 3B67EB8928BCAEEB0048F8AE /* Release */, 981 | ); 982 | defaultConfigurationIsVisible = 0; 983 | defaultConfigurationName = Release; 984 | }; 985 | 3B67EB8A28BCAEEB0048F8AE /* Build configuration list for PBXNativeTarget "PreviewTextUITests" */ = { 986 | isa = XCConfigurationList; 987 | buildConfigurations = ( 988 | 3B67EB8B28BCAEEB0048F8AE /* Debug */, 989 | 3B67EB8C28BCAEEB0048F8AE /* Release */, 990 | ); 991 | defaultConfigurationIsVisible = 0; 992 | defaultConfigurationName = Release; 993 | }; 994 | 3B67EBB228BCB3610048F8AE /* Build configuration list for PBXNativeTarget "Text Previewer" */ = { 995 | isa = XCConfigurationList; 996 | buildConfigurations = ( 997 | 3B67EBB328BCB3610048F8AE /* Debug */, 998 | 3B67EBB428BCB3610048F8AE /* Release */, 999 | ); 1000 | defaultConfigurationIsVisible = 0; 1001 | defaultConfigurationName = Release; 1002 | }; 1003 | /* End XCConfigurationList section */ 1004 | }; 1005 | rootObject = 3B67EB5928BCAEEA0048F8AE /* Project object */; 1006 | } 1007 | -------------------------------------------------------------------------------- /PreviewText.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PreviewText.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PreviewText.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PreviewText.xcodeproj/xcshareddata/xcschemes/PreviewText.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 11 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | 39 | 40 | 41 | 42 | 43 | 48 | 49 | 50 | 51 | 61 | 63 | 69 | 70 | 71 | 72 | 78 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /PreviewText.xcodeproj/xcshareddata/xcschemes/Text Previewer.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 47 | 48 | 60 | 62 | 68 | 69 | 70 | 71 | 79 | 81 | 87 | 88 | 89 | 90 | 92 | 93 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /PreviewText/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * AppDelegate.swift 3 | * PreviewText 4 | * 5 | * Created by Tony Smith on 9/08/2023. 6 | * Copyright © 2025 Tony Smith. All rights reserved. 7 | */ 8 | 9 | 10 | import Cocoa 11 | import CoreServices 12 | import WebKit 13 | 14 | 15 | 16 | @main 17 | final class AppDelegate: NSObject, 18 | NSApplicationDelegate, 19 | URLSessionDelegate, 20 | URLSessionDataDelegate, 21 | WKNavigationDelegate { 22 | 23 | // MARK:- Class UI Properies 24 | // Menu Items 25 | @IBOutlet var helpMenu: NSMenuItem! 26 | @IBOutlet var helpMenuOnlineHelp: NSMenuItem! 27 | @IBOutlet var helpMenuAppStoreRating: NSMenuItem! 28 | //@IBOutlet var helpMenuReportBug: NSMenuItem! 29 | @IBOutlet var helpMenuWhatsNew: NSMenuItem! 30 | @IBOutlet var helpMenuOthersPreviewMarkdown: NSMenuItem! 31 | @IBOutlet var helpMenuOthersPreviewCode: NSMenuItem! 32 | //@IBOutlet var helpMenuOtherspreviewYaml: NSMenuItem! 33 | @IBOutlet var helpMenuOtherspreviewJson: NSMenuItem! 34 | 35 | @IBOutlet var mainMenuSettings: NSMenuItem! 36 | 37 | // Panel Items 38 | @IBOutlet var versionLabel: NSTextField! 39 | // FROM 1.0.5 40 | @IBOutlet var copyrightLabel: NSTextField! 41 | 42 | // Windows 43 | @IBOutlet var window: NSWindow! 44 | 45 | // Report Sheet 46 | @IBOutlet weak var reportWindow: NSWindow! 47 | @IBOutlet weak var feedbackText: NSTextField! 48 | @IBOutlet weak var connectionProgress: NSProgressIndicator! 49 | 50 | // Preferences Sheet 51 | @IBOutlet weak var preferencesWindow: NSWindow! 52 | @IBOutlet weak var fontSizeSlider: NSSlider! 53 | @IBOutlet weak var fontSizeLabel: NSTextField! 54 | @IBOutlet weak var codeFontPopup: NSPopUpButton! 55 | @IBOutlet weak var codeStylePopup: NSPopUpButton! 56 | @IBOutlet weak var inkColourWell: NSColorWell! 57 | @IBOutlet weak var paperColourWell: NSColorWell! 58 | @IBOutlet weak var useLightCheckbox: NSButton! 59 | @IBOutlet weak var foregroundLabel: NSTextField! 60 | @IBOutlet weak var backgroundLabel: NSTextField! 61 | @IBOutlet weak var lineSpacingPopup: NSPopUpButton! 62 | @IBOutlet weak var noteLabel: NSTextField! 63 | @IBOutlet weak var previewView: NSTextView! 64 | @IBOutlet weak var previewScrollView: NSScrollView! 65 | // FROM 1.0.5 66 | @IBOutlet weak var minimumSizePopup: NSPopUpButton! 67 | 68 | // What's New Sheet 69 | @IBOutlet weak var whatsNewWindow: NSWindow! 70 | @IBOutlet weak var whatsNewWebView: WKWebView! 71 | 72 | 73 | // MARK:- Private Properies 74 | internal var whatsNewNav: WKNavigation? = nil 75 | private var feedbackTask: URLSessionTask? = nil 76 | private var boolStyle: Int = BUFFOON_CONSTANTS.BOOL_STYLE.FULL 77 | private var bodyFontSize: CGFloat = CGFloat(BUFFOON_CONSTANTS.BASE_PREVIEW_FONT_SIZE) 78 | private var bodyFontName: String = BUFFOON_CONSTANTS.BODY_FONT_NAME 79 | private var inkColourHex: String = BUFFOON_CONSTANTS.INK_COLOUR_HEX 80 | private var paperColourHex: String = BUFFOON_CONSTANTS.PAPER_COLOUR_HEX 81 | private var lineSpacing: CGFloat = BUFFOON_CONSTANTS.BASE_LINE_SPACING 82 | private var doShowLightBackground: Bool = false 83 | internal var isMontereyPlus: Bool = false 84 | private var isLightMode: Bool = true 85 | //private var havePrefsChanged: Bool = false 86 | internal var bodyFonts: [PMFont] = [] 87 | // FROM 1.0.5 88 | private var minimumThumbSize: Int = BUFFOON_CONSTANTS.MIN_THUMB_SIZE 89 | 90 | /* 91 | Replace the following string with your own team ID. This is used to 92 | identify the app suite and so share preferences set by the main app with 93 | the previewer and thumbnailer extensions. 94 | */ 95 | private var appSuiteName: String = MNU_SECRETS.PID + BUFFOON_CONSTANTS.SUITE_NAME 96 | 97 | 98 | // MARK: - Class Lifecycle Functions 99 | 100 | func applicationDidFinishLaunching(_ notification: Notification) { 101 | 102 | // Asynchronously get the list of code fonts 103 | DispatchQueue.init(label: "com.bps.previewtext.async-queue").async { 104 | self.asyncGetFonts() 105 | } 106 | 107 | // Set application group-level defaults 108 | registerPreferences() 109 | recordSystemState() 110 | 111 | // Add the app's version number to the UI 112 | let version: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String 113 | let build: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as! String 114 | versionLabel.stringValue = "Version \(version) (\(build))" 115 | 116 | // Disable the Help menu Spotlight features 117 | let dummyHelpMenu: NSMenu = NSMenu.init(title: "Dummy") 118 | let theApp = NSApplication.shared 119 | theApp.helpMenu = dummyHelpMenu 120 | 121 | // Watch for macOS UI mode changes 122 | DistributedNotificationCenter.default.addObserver(self, 123 | selector: #selector(interfaceModeChanged), 124 | name: NSNotification.Name(rawValue: "AppleInterfaceThemeChangedNotification"), 125 | object: nil) 126 | 127 | // FRON 1.0.5 128 | // Auto-set the date 129 | let year = Calendar(identifier: .gregorian).dateComponents([.year], from: Date()).year 130 | self.copyrightLabel.stringValue = "Copyright © \(year!) Tony Smith. All rights reserved." 131 | 132 | // Centre the main window and display 133 | self.window.center() 134 | self.window.makeKeyAndOrderFront(self) 135 | 136 | // Show the 'What's New' panel if we need to 137 | // NOTE Has to take place at the end of the function 138 | doShowWhatsNew(self) 139 | } 140 | 141 | 142 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 143 | 144 | // When the main window closed, shut down the app 145 | return true 146 | } 147 | 148 | 149 | // MARK:- Action Functions 150 | 151 | /** 152 | Called from **File > Close** and the various Quit controls. 153 | 154 | - Parameters: 155 | - sender: The source of the action. 156 | */ 157 | @IBAction private func doClose(_ sender: Any) { 158 | 159 | // Reset the QL thumbnail cache... just in case it helps 160 | _ = runProcess(app: "/usr/bin/qlmanage", with: ["-r", "cache"]) 161 | 162 | // Check for open panels 163 | if self.preferencesWindow.isVisible { 164 | if checkPrefs() { 165 | let alert: NSAlert = showAlert("You have unsaved settings", 166 | "Do you wish to cancel and save them, or quit the app anyway?", 167 | false) 168 | alert.addButton(withTitle: "Quit") 169 | alert.addButton(withTitle: "Cancel") 170 | alert.beginSheetModal(for: self.preferencesWindow) { (response: NSApplication.ModalResponse) in 171 | if response == NSApplication.ModalResponse.alertFirstButtonReturn { 172 | // The user clicked 'Quit' 173 | self.preferencesWindow.close() 174 | self.window.close() 175 | } 176 | } 177 | 178 | return 179 | } 180 | 181 | self.preferencesWindow.close() 182 | } 183 | 184 | if self.whatsNewWindow.isVisible { 185 | self.whatsNewWindow.close() 186 | } 187 | 188 | if self.reportWindow.isVisible { 189 | if self.feedbackText.stringValue.count > 0 { 190 | let alert: NSAlert = showAlert("You have unsent feedback", 191 | "Do you wish to cancel and send it, or quit the app anyway?", 192 | false) 193 | alert.addButton(withTitle: "Quit") 194 | alert.addButton(withTitle: "Cancel") 195 | alert.beginSheetModal(for: self.reportWindow) { (response: NSApplication.ModalResponse) in 196 | if response == NSApplication.ModalResponse.alertFirstButtonReturn { 197 | // The user clicked 'Quit' 198 | self.reportWindow.close() 199 | self.window.close() 200 | } 201 | } 202 | 203 | return 204 | } 205 | 206 | self.reportWindow.close() 207 | } 208 | 209 | // Close the window... which will trigger an app closure 210 | self.window.close() 211 | } 212 | 213 | 214 | /** 215 | Called from various **Help** items to open various websites. 216 | 217 | - Parameters: 218 | - sender: The source of the action. 219 | */ 220 | @IBAction @objc private func doShowSites(sender: Any) { 221 | 222 | // Open the websites for contributors, help and suc 223 | let item: NSMenuItem = sender as! NSMenuItem 224 | var path: String = BUFFOON_CONSTANTS.URL_MAIN 225 | 226 | // Depending on the menu selected, set the load path 227 | if item == self.helpMenuAppStoreRating { 228 | path = BUFFOON_CONSTANTS.APP_STORE + "?action=write-review" 229 | } else if item == self.helpMenuOnlineHelp { 230 | path += "#how-to-use-previewtext" 231 | } else if item == self.helpMenuOthersPreviewMarkdown { 232 | path = BUFFOON_CONSTANTS.APP_URLS.PM 233 | } else if item == self.helpMenuOthersPreviewCode { 234 | path = BUFFOON_CONSTANTS.APP_URLS.PC 235 | //} else if item == self.helpMenuOtherspreviewYaml { 236 | // path = BUFFOON_CONSTANTS.APP_URLS.PY 237 | } else if item == self.helpMenuOtherspreviewJson { 238 | path = BUFFOON_CONSTANTS.APP_URLS.PJ 239 | } 240 | 241 | // Open the selected website 242 | NSWorkspace.shared.open(URL.init(string:path)!) 243 | } 244 | 245 | 246 | /** 247 | Open the System Preferences app at the Extensions pane. 248 | 249 | - Parameters: 250 | - sender: The source of the action. 251 | */ 252 | @IBAction private func doOpenSysPrefs(sender: Any) { 253 | 254 | // Open the System Preferences app at the Extensions pane 255 | NSWorkspace.shared.open(URL(fileURLWithPath: "/System/Library/PreferencePanes/Extensions.prefPane")) 256 | } 257 | 258 | 259 | // MARK: - Report Functions 260 | /* 261 | /** 262 | Display a window in which the user can submit feedback, or report a bug. 263 | 264 | - Parameters: 265 | - sender: The source of the action. 266 | */ 267 | @IBAction @objc private func doShowReportWindow(sender: Any?) { 268 | 269 | // Reset the UI 270 | hidePanelGenerators() 271 | self.connectionProgress.stopAnimation(self) 272 | self.feedbackText.stringValue = "" 273 | 274 | // Present the window 275 | self.window.beginSheet(self.reportWindow, 276 | completionHandler: nil) 277 | } 278 | 279 | 280 | /** 281 | User has clicked the Report window's **Cancel** button, so just close the sheet. 282 | 283 | - Parameters: 284 | - sender: The source of the action. 285 | */ 286 | @IBAction @objc private func doCancelReportWindow(sender: Any) { 287 | 288 | // User has clicked the Report window's 'Cancel' button, 289 | // so just close the sheet 290 | 291 | self.connectionProgress.stopAnimation(self) 292 | self.window.endSheet(self.reportWindow) 293 | showPanelGenerators() 294 | } 295 | 296 | 297 | /** 298 | User has clicked the Report window's **Send** button. 299 | 300 | Get the message (if there is one) from the text field and submit it. 301 | 302 | - Parameters: 303 | - sender: The source of the action. 304 | */ 305 | @IBAction @objc private func doSendFeedback(sender: Any) { 306 | 307 | // User has clicked the Report window's 'Send' button, 308 | // so get the message (if there is one) from the text field and submit it 309 | 310 | let feedback: String = self.feedbackText.stringValue 311 | 312 | if feedback.count > 0 { 313 | // Start the connection indicator if it's not already visible 314 | self.connectionProgress.startAnimation(self) 315 | 316 | /* 317 | Add your own `func sendFeedback(_ feedback: String) -> URLSessionTask?` function 318 | */ 319 | 320 | self.feedbackTask = sendFeedback(feedback) 321 | 322 | if self.feedbackTask != nil { 323 | // We have a valid URL Session Task, so start it to send 324 | self.feedbackTask!.resume() 325 | return 326 | } else { 327 | // Report the error 328 | sendFeedbackError() 329 | } 330 | } 331 | 332 | // No feedback, so close the sheet 333 | self.window.endSheet(self.reportWindow) 334 | showPanelGenerators() 335 | 336 | // NOTE sheet closes asynchronously unless there was no feedback to send, 337 | // or an error occured with setting up the feedback session 338 | } 339 | */ 340 | 341 | // MARK: - Preferences Functions 342 | 343 | /** 344 | Initialise and display the **Preferences** sheet. 345 | 346 | - Parameters: 347 | - sender: The source of the action. 348 | */ 349 | @IBAction private func doShowPreferences(sender: Any) { 350 | 351 | // Disable menus we don't want active while the panel is open 352 | hidePanelGenerators() 353 | 354 | // Reset the changes flag 355 | //self.havePrefsChanged = false 356 | 357 | // Prep the preview view 358 | self.previewView.isSelectable = false 359 | self.previewScrollView.wantsLayer = true 360 | self.previewScrollView.layer?.borderWidth = 2.0 361 | self.previewScrollView.layer?.cornerRadius = 8.0 362 | 363 | // Check for the OS mode 364 | self.isLightMode = isMacInLightMode() 365 | 366 | #if DEBUG 367 | self.foregroundLabel.stringValue = self.isLightMode ? "LIGHT" : "DARK" 368 | self.backgroundLabel.stringValue = self.useLightCheckbox.state == .on ? "ON" : "OFF" 369 | #endif 370 | 371 | // The suite name is the app group name, set in each the entitlements file of 372 | // the host app and of each extension 373 | if let defaults = UserDefaults(suiteName: self.appSuiteName) { 374 | self.bodyFontSize = CGFloat(defaults.float(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_FONT_SIZE)) 375 | self.doShowLightBackground = defaults.bool(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_USE_LIGHT) 376 | self.bodyFontName = defaults.string(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_FONT_NAME) ?? BUFFOON_CONSTANTS.BODY_FONT_NAME 377 | self.inkColourHex = defaults.string(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_INK_COLOUR) ?? BUFFOON_CONSTANTS.INK_COLOUR_HEX 378 | self.paperColourHex = defaults.string(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_PAPER_COLOUR) ?? BUFFOON_CONSTANTS.PAPER_COLOUR_HEX 379 | self.lineSpacing = CGFloat(defaults.float(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_LINE_SPACING)) 380 | // FROM 1.0.5 381 | self.minimumThumbSize = defaults.integer(forKey: BUFFOON_CONSTANTS.PREFS_IDS.THUMB_MIN_SIZE) 382 | } 383 | 384 | // Get the menu item index from the stored value 385 | // NOTE The index is that of the list of available fonts (see 'Common.swift') so 386 | // we need to convert this to an equivalent menu index because the menu also 387 | // contains a separator and two title items 388 | let index: Int = BUFFOON_CONSTANTS.FONT_SIZE_OPTIONS.lastIndex(of: self.bodyFontSize) ?? 3 389 | self.fontSizeSlider.floatValue = Float(index) 390 | self.fontSizeLabel.stringValue = "\(Int(BUFFOON_CONSTANTS.FONT_SIZE_OPTIONS[index]))pt" 391 | 392 | // Set the checkboxes 393 | self.useLightCheckbox.state = self.doShowLightBackground ? .on : .off 394 | self.useLightCheckbox.isEnabled = !self.isLightMode 395 | 396 | // Set the colour panel's initial view 397 | NSColorPanel.setPickerMode(.RGB) 398 | if self.isLightMode || self.doShowLightBackground { 399 | // Light mode, so ink = foreground, paper = background 400 | self.inkColourWell.color = NSColor.hexToColour(self.inkColourHex) 401 | self.paperColourWell.color = NSColor.hexToColour(self.paperColourHex) 402 | } else { 403 | // Dark mode, so ink = background, paper = foreground 404 | self.inkColourWell.color = NSColor.hexToColour(self.paperColourHex) 405 | self.paperColourWell.color = NSColor.hexToColour(self.inkColourHex) 406 | } 407 | 408 | // Set the colour shift warning's state 409 | self.noteLabel.alphaValue = self.isLightMode ? 0.25 : 1.0 410 | 411 | // Set the font name popup 412 | // List the current system's monospace fonts 413 | self.codeFontPopup.removeAllItems() 414 | for i: Int in 0.. Bool { 616 | 617 | var haveChanged: Bool = false 618 | 619 | // Check for a use light background change 620 | let state: Bool = self.useLightCheckbox.state == .on 621 | haveChanged = (self.doShowLightBackground != state) 622 | 623 | // Check for and record an indent change 624 | if !haveChanged { 625 | let lineIndex: Int = self.lineSpacingPopup.indexOfSelectedItem 626 | var lineSpacing: CGFloat = 1.0 627 | switch(lineIndex) { 628 | case 1: 629 | lineSpacing = 1.15 630 | case 2: 631 | lineSpacing = 1.5 632 | case 3: 633 | lineSpacing = 2.0 634 | default: 635 | lineSpacing = 1.0 636 | } 637 | 638 | haveChanged = (round(self.lineSpacing * 100) / 100.0 != lineSpacing) 639 | } 640 | 641 | // Check for and record a font and style change 642 | if let fontName: String = getPostScriptName() { 643 | if !haveChanged { 644 | haveChanged = (fontName != self.bodyFontName) 645 | } 646 | } 647 | 648 | // Check for and record a font size change 649 | if !haveChanged { 650 | haveChanged = (self.bodyFontSize != BUFFOON_CONSTANTS.FONT_SIZE_OPTIONS[Int(self.fontSizeSlider.floatValue)]) 651 | } 652 | 653 | // Check for thumbnail minimum size change 654 | if !haveChanged { 655 | haveChanged = (self.minimumThumbSize != BUFFOON_CONSTANTS.THUMB_SIZES[self.minimumSizePopup.indexOfSelectedItem]) 656 | } 657 | 658 | // Check for ink/paper colour changes 659 | // NOTE `state` is `true` if the user wants a light preview in dark mode 660 | if !haveChanged && (self.isLightMode || (!self.isLightMode && state)) { 661 | // In Light Mode, or in Dark Mode and the user wants a light preview 662 | var newColour: String = self.inkColourWell.color.hexString 663 | if newColour != self.inkColourHex { 664 | haveChanged = true 665 | } 666 | 667 | newColour = self.paperColourWell.color.hexString 668 | if newColour != self.paperColourHex { 669 | haveChanged = true 670 | } 671 | } else { 672 | // In Dark Mode, and the user wants a dark preview 673 | var newColour: String = self.inkColourWell.color.hexString 674 | if newColour != self.paperColourHex { 675 | haveChanged = true 676 | } 677 | 678 | newColour = self.paperColourWell.color.hexString 679 | if newColour != self.inkColourHex { 680 | haveChanged = true 681 | } 682 | } 683 | 684 | return haveChanged 685 | } 686 | 687 | 688 | /** 689 | When the font size slider is moved and released, this function updates the font size readout. 690 | 691 | - Parameters: 692 | - sender: The source of the action. 693 | */ 694 | @IBAction private func doMoveSlider(sender: Any) { 695 | 696 | let index: Int = Int(self.fontSizeSlider.floatValue) 697 | self.fontSizeLabel.stringValue = "\(Int(BUFFOON_CONSTANTS.FONT_SIZE_OPTIONS[index]))pt" 698 | //self.havePrefsChanged = true 699 | 700 | // Update the preview 701 | doRenderPreview() 702 | } 703 | 704 | 705 | /** 706 | Called when the user selects a font from the list. 707 | 708 | - Parameters: 709 | - sender: The source of the action. 710 | */ 711 | @IBAction private func doUpdateFonts(sender: Any) { 712 | 713 | //self.havePrefsChanged = true 714 | 715 | // Update the font's styles list 716 | setStylePopup() 717 | 718 | // Update the preview 719 | doRenderPreview() 720 | } 721 | 722 | 723 | /** 724 | Called when the user selects a style from the list. 725 | 726 | - Parameters: 727 | - sender: The source of the action. 728 | */ 729 | @IBAction private func doUpdateStyle(sender: Any) { 730 | 731 | //self.havePrefsChanged = true 732 | 733 | // Update the preview 734 | doRenderPreview() 735 | } 736 | 737 | 738 | /** 739 | Called when the user selects a thumbnail size from the list. 740 | FROM 1.0.5 741 | 742 | - Parameters: 743 | - sender: The source of the action. 744 | */ 745 | @IBAction private func doUpdateMinSize(sender: Any) { 746 | 747 | /* 748 | if self.minimumThumbSize != BUFFOON_CONSTANTS.THUMB_SIZES[self.minimumSizePopup.indexOfSelectedItem] { 749 | self.minimumThumbSize = BUFFOON_CONSTANTS.THUMB_SIZES[self.minimumSizePopup.indexOfSelectedItem] 750 | self.havePrefsChanged = true 751 | } 752 | */ 753 | } 754 | 755 | 756 | /** 757 | Respond to a click on the **use light background** checkbox. 758 | 759 | This is only possible in Dark Mode (control disabled in Light Mode). 760 | 761 | - Parameters: 762 | - sender: The source of the action. 763 | */ 764 | @IBAction @objc func doSwitchColours(_ sender: Any) { 765 | 766 | // Swap the colours 767 | let tempColour: NSColor = self.inkColourWell.color 768 | self.inkColourWell.color = self.paperColourWell.color 769 | self.paperColourWell.color = tempColour 770 | 771 | // Update the preview 772 | doRenderPreview() 773 | 774 | // Set the warning note's state (greyed out when it's not relevant) 775 | self.noteLabel.alphaValue = self.useLightCheckbox.state == .on ? 0.25 : 1.0 776 | } 777 | 778 | 779 | /** 780 | Called when either NSColorWell's value is changed. 781 | 782 | - Parameters: 783 | - sender: The source of the action. 784 | */ 785 | @IBAction @objc func doChangeColours(_ sender: Any) { 786 | 787 | //self.havePrefsChanged = true 788 | 789 | // Update the preview 790 | doRenderPreview() 791 | } 792 | 793 | 794 | /** 795 | Render a preview sample based on the current NSColorWell colours 796 | and mode settings. 797 | */ 798 | private func doRenderPreview() { 799 | 800 | // Load in the code sample we'll preview the themes with 801 | guard let loadedCode = loadBundleFile(BUFFOON_CONSTANTS.FILE_CODE_SAMPLE) else { return } 802 | 803 | let common: Common = Common.init(false) 804 | common.paperColour = self.paperColourWell.color.hexString 805 | common.inkColour = self.inkColourWell.color.hexString 806 | common.fontSize = BUFFOON_CONSTANTS.PREVIEW_SIZE_OPTIONS[Int(self.fontSizeSlider.floatValue)] 807 | 808 | var lineSpacing: CGFloat = 1.0 809 | switch(self.lineSpacingPopup.indexOfSelectedItem) { 810 | case 1: 811 | lineSpacing = 1.15 812 | case 2: 813 | lineSpacing = 1.5 814 | case 3: 815 | lineSpacing = 2.0 816 | default: 817 | lineSpacing = 1.0 818 | } 819 | 820 | 821 | common.lineSpacing = lineSpacing 822 | common.setProperties(false, getPostScriptName() ?? BUFFOON_CONSTANTS.BODY_FONT_NAME) 823 | 824 | // Render the sample text and drop it into the view 825 | let pas: NSAttributedString = common.getAttributedString(loadedCode) 826 | self.previewView.backgroundColor = NSColor.hexToColour(common.paperColour) 827 | 828 | if let renderTextStorage: NSTextStorage = self.previewView.textStorage { 829 | renderTextStorage.beginEditing() 830 | renderTextStorage.setAttributedString(pas) 831 | renderTextStorage.endEditing() 832 | self.previewView.display() 833 | } 834 | } 835 | 836 | 837 | /** 838 | Load a known text file from the app bundle. 839 | 840 | - Parameters: 841 | - file: The name of the text file without its extension. 842 | 843 | - Returns: The contents of the loaded file 844 | */ 845 | private func loadBundleFile(_ fileName: String, _ type: String = "txt") -> String? { 846 | 847 | // Load the required resource and return its contents 848 | guard let filePath: String = Bundle.main.path(forResource: fileName, ofType: type) 849 | else { 850 | // TODO Post error 851 | return nil 852 | } 853 | 854 | do { 855 | let fileContents: String = try String.init(contentsOf: URL.init(fileURLWithPath: filePath)) 856 | return fileContents 857 | } catch { 858 | // TODO Post error 859 | } 860 | 861 | return nil 862 | } 863 | 864 | 865 | /** 866 | Generic IBAction for any Prefs control to register it has been used. 867 | 868 | - Parameters: 869 | - sender: The source of the action. 870 | */ 871 | @IBAction private func checkboxClicked(sender: Any) { 872 | 873 | //self.havePrefsChanged = true 874 | 875 | let tempColour: NSColor = self.inkColourWell.color 876 | self.inkColourWell.color = self.paperColourWell.color 877 | self.paperColourWell.color = tempColour 878 | 879 | // FROM 1.0.1 -- Render preview on changes 880 | self.doRenderPreview() 881 | 882 | #if DEBUG 883 | self.foregroundLabel.stringValue = self.isLightMode ? "LIGHT" : "DARK" 884 | self.backgroundLabel.stringValue = self.useLightCheckbox.state == .on ? "ON" : "OFF" 885 | #endif 886 | } 887 | 888 | 889 | // MARK: - What's New Sheet Functions 890 | 891 | /** 892 | Show the **What's New** sheet. 893 | 894 | If we're on a new, non-patch version, of the user has explicitly 895 | asked to see it with a menu click See if we're coming from a menu click 896 | (`sender != self`) or directly in code from *appDidFinishLoading()* 897 | (`sender == self`) 898 | 899 | - Parameters: 900 | - sender: The source of the action. 901 | */ 902 | @IBAction private func doShowWhatsNew(_ sender: Any) { 903 | 904 | // See if we're coming from a menu click (sender != self) or 905 | // directly in code from 'appDidFinishLoading()' (sender == self) 906 | var doShowSheet: Bool = type(of: self) != type(of: sender) 907 | 908 | if !doShowSheet { 909 | // We are coming from the 'appDidFinishLoading()' so check 910 | // if we need to show the sheet by the checking the prefs 911 | if let defaults = UserDefaults(suiteName: self.appSuiteName) { 912 | // Get the version-specific preference key 913 | let key: String = BUFFOON_CONSTANTS.PREFS_IDS.MAIN_WHATS_NEW + getVersion() 914 | doShowSheet = defaults.bool(forKey: key) 915 | } 916 | } 917 | 918 | // Configure and show the sheet 919 | if doShowSheet { 920 | // Hide manus we don't want used 921 | hidePanelGenerators() 922 | 923 | // First, get the folder path 924 | let htmlFolderPath = Bundle.main.resourcePath! + "/new" 925 | 926 | // Set up the WKWebBiew: no elasticity, horizontal scroller 927 | self.whatsNewWebView.enclosingScrollView?.hasHorizontalScroller = false 928 | self.whatsNewWebView.enclosingScrollView?.horizontalScrollElasticity = .none 929 | self.whatsNewWebView.enclosingScrollView?.verticalScrollElasticity = .none 930 | 931 | // Just in case, make sure we can load the file 932 | if FileManager.default.fileExists(atPath: htmlFolderPath) { 933 | let htmlFileURL = URL.init(fileURLWithPath: htmlFolderPath + "/new.html") 934 | let htmlFolderURL = URL.init(fileURLWithPath: htmlFolderPath) 935 | self.whatsNewNav = self.whatsNewWebView.loadFileURL(htmlFileURL, allowingReadAccessTo: htmlFolderURL) 936 | } 937 | } 938 | } 939 | 940 | 941 | /** 942 | Close the **What's New** sheet. 943 | 944 | Make sure we clear the preference flag for this minor version, so that 945 | the sheet is not displayed next time the app is run (unless the version changes) 946 | 947 | - Parameters: 948 | - sender: The source of the action. 949 | */ 950 | @IBAction private func doCloseWhatsNew(_ sender: Any) { 951 | 952 | // Close the sheet 953 | self.window.endSheet(self.whatsNewWindow) 954 | 955 | // Scroll the web view back to the top 956 | self.whatsNewWebView.evaluateJavaScript("window.scrollTo(0,0)", completionHandler: nil) 957 | 958 | // Set this version's preference 959 | if let defaults = UserDefaults(suiteName: self.appSuiteName) { 960 | let key: String = BUFFOON_CONSTANTS.PREFS_IDS.MAIN_WHATS_NEW + getVersion() 961 | defaults.setValue(false, forKey: key) 962 | 963 | #if DEBUG 964 | print("\(key) reset back to true") 965 | defaults.setValue(true, forKey: key) 966 | #endif 967 | 968 | defaults.synchronize() 969 | } 970 | 971 | // Restore menus 972 | showPanelGenerators() 973 | } 974 | 975 | 976 | // MARK: - Misc Functions 977 | 978 | /** 979 | Called by the app at launch to register its initial defaults. 980 | */ 981 | private func registerPreferences() { 982 | 983 | // Check if each preference value exists -- set if it doesn't 984 | if let defaults = UserDefaults(suiteName: self.appSuiteName) { 985 | 986 | // Preview body font size, stored as a CGFloat 987 | // Default: 16.0 988 | let bodyFontSizeDefault: Any? = defaults.object(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_FONT_SIZE) 989 | if bodyFontSizeDefault == nil { 990 | defaults.setValue(CGFloat(BUFFOON_CONSTANTS.BASE_PREVIEW_FONT_SIZE), 991 | forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_FONT_SIZE) 992 | } 993 | 994 | // Thumbnail view base font size, stored as a CGFloat, not currently used 995 | // Default: 28.0 996 | let thumbFontSizeDefault: Any? = defaults.object(forKey: BUFFOON_CONSTANTS.PREFS_IDS.THUMB_FONT_SIZE) 997 | if thumbFontSizeDefault == nil { 998 | defaults.setValue(CGFloat(BUFFOON_CONSTANTS.BASE_THUMB_FONT_SIZE), 999 | forKey: BUFFOON_CONSTANTS.PREFS_IDS.THUMB_FONT_SIZE) 1000 | } 1001 | 1002 | // Colour of JSON keys in the preview, stored as in integer array index 1003 | // Default: #CA0D0E 1004 | var colourDefault: Any? = defaults.object(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_INK_COLOUR) 1005 | if colourDefault == nil { 1006 | defaults.setValue(BUFFOON_CONSTANTS.INK_COLOUR_HEX, 1007 | forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_INK_COLOUR) 1008 | } 1009 | 1010 | // Colour of JSON markers in the preview, stored as in integer array index 1011 | // Default: #0096FF 1012 | colourDefault = defaults.object(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_PAPER_COLOUR) 1013 | if colourDefault == nil { 1014 | defaults.setValue(BUFFOON_CONSTANTS.PAPER_COLOUR_HEX, 1015 | forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_PAPER_COLOUR) 1016 | } 1017 | 1018 | // Use light background even in dark mode, stored as a bool 1019 | // Default: false 1020 | let useLightDefault: Any? = defaults.object(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_USE_LIGHT) 1021 | if useLightDefault == nil { 1022 | defaults.setValue(false, 1023 | forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_USE_LIGHT) 1024 | } 1025 | 1026 | // Show the What's New sheet 1027 | // Default: true 1028 | // This is a version-specific preference suffixed with, eg, '-2-3'. Once created 1029 | // this will persist, but with each new major and/or minor version, we make a 1030 | // new preference that will be read by 'doShowWhatsNew()' to see if the sheet 1031 | // should be shown this run 1032 | let key: String = BUFFOON_CONSTANTS.PREFS_IDS.MAIN_WHATS_NEW + getVersion() 1033 | let showNewDefault: Any? = defaults.object(forKey: key) 1034 | if showNewDefault == nil { 1035 | defaults.setValue(true, forKey: key) 1036 | } 1037 | 1038 | // Store the preview line spacing value 1039 | let lineSpacingDefault: Any? = defaults.object(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_LINE_SPACING) 1040 | if lineSpacingDefault == nil { 1041 | defaults.setValue(BUFFOON_CONSTANTS.BASE_LINE_SPACING, 1042 | forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_LINE_SPACING) 1043 | } 1044 | 1045 | // FROM 1.0.5 1046 | // Store the miniumum thumbnail render size 1047 | let minThumbSizeDefault: Any? = defaults.object(forKey: BUFFOON_CONSTANTS.PREFS_IDS.THUMB_MIN_SIZE) 1048 | if minThumbSizeDefault == nil { 1049 | defaults.setValue(BUFFOON_CONSTANTS.MIN_THUMB_SIZE, 1050 | forKey: BUFFOON_CONSTANTS.PREFS_IDS.THUMB_MIN_SIZE) 1051 | } 1052 | 1053 | // Sync any additions 1054 | defaults.synchronize() 1055 | } 1056 | 1057 | } 1058 | 1059 | 1060 | /** 1061 | Handler for macOS UI mode change notifications 1062 | */ 1063 | @objc private func interfaceModeChanged() { 1064 | 1065 | // FROM 1.0.6 1066 | // macOS 14 appears to switch values from earlier versions. 1067 | // See note 2 below 1068 | if #available(macOS 14, *) { 1069 | self.isLightMode = isMacInLightMode() 1070 | } else { 1071 | self.isLightMode = !isMacInLightMode() 1072 | } 1073 | 1074 | if self.preferencesWindow.isVisible { 1075 | #if DEBUG 1076 | self.foregroundLabel.stringValue = self.isLightMode ? "LIGHT" : "DARK" 1077 | self.backgroundLabel.stringValue = self.useLightCheckbox.state == .on ? "ON" : "OFF" 1078 | #endif 1079 | 1080 | // Prefs window is up, so switch the use light background checkbox 1081 | // on or off according to whether the current mode is light 1082 | // NOTE 1 For light mode, this checkbox is irrelevant, so the 1083 | // checkbox should be disabled 1084 | // NOTE 2 Appearance it this point seems to reflect the mode 1085 | // we're coming FROM, not what it has changed TO 1086 | self.useLightCheckbox.isEnabled = !self.isLightMode 1087 | 1088 | if self.useLightCheckbox.state == .off { 1089 | // Swap the NSColorWell values around as we're 1090 | // changing mode -- the checkbox freezes the mode 1091 | let tempColour: NSColor = self.inkColourWell.color 1092 | self.inkColourWell.color = self.paperColourWell.color 1093 | self.paperColourWell.color = tempColour 1094 | } 1095 | 1096 | // Update the preview 1097 | doRenderPreview() 1098 | 1099 | // Set the warning note's state (greyed out when it's not relevant) 1100 | self.noteLabel.alphaValue = self.isLightMode ? 0.25 : 1.0 1101 | } 1102 | } 1103 | 1104 | } 1105 | -------------------------------------------------------------------------------- /PreviewText/Constants.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Constants.swift 3 | * PreviewText 4 | * 5 | * Created by Tony Smith on 12/08/2020. 6 | * Copyright © 2025 Tony Smith. All rights reserved. 7 | */ 8 | 9 | 10 | import Foundation 11 | 12 | 13 | // Combine the app's various constants into a struct 14 | struct BUFFOON_CONSTANTS { 15 | 16 | struct ERRORS { 17 | 18 | struct CODES { 19 | static let NONE = 0 20 | static let FILE_INACCESSIBLE = 400 21 | static let FILE_WONT_OPEN = 401 22 | static let BAD_MD_STRING = 402 23 | static let BAD_TS_STRING = 403 24 | // FROM 1.0.2 25 | static let BAD_GO_CONFIG = 404 26 | } 27 | 28 | struct MESSAGES { 29 | static let NO_ERROR = "No error" 30 | static let FILE_INACCESSIBLE = "Can't access file" 31 | static let FILE_WONT_OPEN = "Can't open file" 32 | static let BAD_MD_STRING = "Can't get JSON data" 33 | static let BAD_TS_STRING = "Can't access NSTextView's TextStorage" 34 | // FROM 1.0.2 35 | static let BAD_GO_CONFIG = "This file is not a Go config file" 36 | } 37 | } 38 | 39 | struct THUMBNAIL_SIZE { 40 | 41 | static let ORIGIN_X = 0 42 | static let ORIGIN_Y = 0 43 | static let WIDTH = 768 44 | static let HEIGHT = 1024 45 | static let ASPECT = 0.75 46 | static let TAG_HEIGHT = 204.8 47 | static let FONT_SIZE = 130.0 48 | } 49 | 50 | struct ITEM_TYPE { 51 | 52 | static let KEY = 0 53 | static let VALUE = 1 54 | static let MARK_START = 2 55 | static let MARK_END = 3 56 | } 57 | 58 | struct BOOL_STYLE { 59 | 60 | static let FULL = 0 61 | static let OUTLINE = 1 62 | static let TEXT = 2 63 | } 64 | 65 | static let BASE_PREVIEW_FONT_SIZE: CGFloat = 16.0 66 | static let BASE_THUMB_FONT_SIZE: CGFloat = 22.0 67 | static let THUMBNAIL_LINE_COUNT = 32 68 | 69 | static let FONT_SIZE_OPTIONS: [CGFloat] = [10.0, 12.0, 14.0, 16.0, 18.0, 24.0, 28.0] 70 | static let PREVIEW_SIZE_OPTIONS: [CGFloat] = [9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 22.0] 71 | 72 | static let BASE_LINE_SPACING: CGFloat = 1.0 73 | 74 | static let URL_MAIN = "https://smittytone.net/previewtext/index.html" 75 | static let APP_STORE = "https://apps.apple.com/us/app/previewtext/id1660037028" 76 | static let SUITE_NAME = ".suite.preview-previewtext" 77 | static let APP_CODE_PREVIEWER = "com.bps.previewtext.Text_Previewer" 78 | 79 | static let BODY_FONT_NAME = "Menlo-Regular" 80 | static let INK_COLOUR_HEX = "000000FF" 81 | static let PAPER_COLOUR_HEX = "FFFFFFFF" 82 | 83 | static let RENDER_DEBUG = false 84 | 85 | static let FILE_CODE_SAMPLE = "text-sample" 86 | 87 | struct APP_URLS { 88 | 89 | static let PM = "https://apps.apple.com/us/app/previewmarkdown/id1492280469?ls=1" 90 | static let PC = "https://apps.apple.com/us/app/previewcode/id1571797683?ls=1" 91 | static let PY = "https://apps.apple.com/us/app/previewyaml/id1564574724?ls=1" 92 | static let PJ = "https://apps.apple.com/us/app/previewjson/id6443584377?ls=1" 93 | static let PT = "https://apps.apple.com/us/app/previewtext/id1660037028?ls=1" 94 | } 95 | 96 | // FROM 1.0.5 97 | static let MIN_THUMB_SIZE = 48 98 | static let BASE_THUMB_LINE_SPACING = 1.15 99 | static let THUMB_SIZES: [Int] = [256, 128, 96, 64, 48, 32, 16] 100 | 101 | struct PREFS_IDS { 102 | 103 | static let MAIN_WHATS_NEW = "com-bps-previewtext-do-show-whats-new-" 104 | static let PREVIEW_FONT_SIZE = "com-bps-previewtext-base-font-size" 105 | static let PREVIEW_FONT_NAME = "com-bps-previewtext-base-font-name" 106 | static let PREVIEW_LINE_SPACING = "com-bps-previewtext-line-spacing" 107 | static let PREVIEW_PAPER_COLOUR = "com-bps-previewtext-paper-colour-hex" 108 | static let PREVIEW_INK_COLOUR = "com-bps-previewtext-ink-colour-hex" 109 | static let PREVIEW_USE_LIGHT = "com-bps-previewtext-do-use-light" 110 | static let THUMB_MIN_SIZE = "com-bps-previewtext-min-thumb-size" 111 | static let THUMB_FONT_SIZE = "com-bps-previewtext-thumb-font-size" 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /PreviewText/GenericColorExtensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * GenericColorExtension.swift 3 | * PreviewApps 4 | * 5 | * Created by Tony Smith on 18/06/2021. 6 | * Copyright © 2025 Tony Smith. All rights reserved. 7 | */ 8 | 9 | 10 | import Foundation 11 | import Cocoa 12 | 13 | 14 | extension NSColor { 15 | 16 | /** 17 | Convert a colour's internal representation into an RGB+A hex string. 18 | */ 19 | var hexString: String { 20 | 21 | guard let rgbColour = usingColorSpace(.sRGB) else { 22 | return BUFFOON_CONSTANTS.INK_COLOUR_HEX 23 | } 24 | 25 | let red: Int = Int(round(rgbColour.redComponent * 0xFF)) 26 | let green: Int = Int(round(rgbColour.greenComponent * 0xFF)) 27 | let blue: Int = Int(round(rgbColour.blueComponent * 0xFF)) 28 | let alpha: Int = Int(round(rgbColour.alphaComponent * 0xFF)) 29 | 30 | let hexString: NSString = NSString(format: "%02X%02X%02X%02X", red, green, blue, alpha) 31 | return hexString as String 32 | } 33 | 34 | 35 | /** 36 | Generate a new NSColor from an RGB+A hex string.. 37 | 38 | - Parameters: 39 | - hex: The RGB+A hex string, eg.`AABBCCFF`. 40 | 41 | - Returns: An NSColor instance. 42 | */ 43 | static func hexToColour(_ hex: String) -> NSColor { 44 | 45 | if hex.count != 8 { 46 | return NSColor.red 47 | } 48 | 49 | func hexToFloat(_ hs: String) -> CGFloat { 50 | return CGFloat(UInt8(hs, radix: 16) ?? 0) 51 | } 52 | 53 | let hexns: NSString = hex as NSString 54 | let red: CGFloat = hexToFloat(hexns.substring(with: NSRange.init(location: 0, length: 2))) / 255 55 | let green: CGFloat = hexToFloat(hexns.substring(with: NSRange.init(location: 2, length: 2))) / 255 56 | let blue: CGFloat = hexToFloat(hexns.substring(with: NSRange.init(location: 4, length: 2))) / 255 57 | let alpha: CGFloat = hexToFloat(hexns.substring(with: NSRange.init(location: 6, length: 2))) / 255 58 | return NSColor.init(srgbRed: red, green: green, blue: blue, alpha: alpha) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /PreviewText/GenericExtensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * GenericExtensions.swift 3 | * PreviewApps 4 | * 5 | * These functions can be used by all PreviewApps 6 | * 7 | * Created by Tony Smith on 18/06/2021. 8 | * Copyright © 2025 Tony Smith. All rights reserved. 9 | */ 10 | 11 | 12 | import Foundation 13 | import Cocoa 14 | import WebKit 15 | import UniformTypeIdentifiers 16 | 17 | 18 | extension AppDelegate { 19 | 20 | // MARK: - Process Handling Functions 21 | 22 | /** 23 | Generic macOS process creation and run function. 24 | 25 | Make sure we clear the preference flag for this minor version, so that 26 | the sheet is not displayed next time the app is run (unless the version changes) 27 | 28 | - Parameters: 29 | - app: The location of the app. 30 | - with: Array of arguments to pass to the app. 31 | 32 | - Returns: `true` if the operation was successful, otherwise `false`. 33 | */ 34 | internal func runProcess(app path: String, with args: [String]) -> Bool { 35 | 36 | let task: Process = Process() 37 | task.executableURL = URL.init(fileURLWithPath: path) 38 | task.arguments = args 39 | 40 | // Pipe out the output to avoid putting it in the log 41 | let outputPipe = Pipe() 42 | task.standardOutput = outputPipe 43 | task.standardError = outputPipe 44 | 45 | do { 46 | try task.run() 47 | } catch { 48 | return false 49 | } 50 | 51 | // Block until the task has completed (short tasks ONLY) 52 | task.waitUntilExit() 53 | 54 | if !task.isRunning { 55 | if (task.terminationStatus != 0) { 56 | // Command failed -- collect the output if there is any 57 | let outputHandle = outputPipe.fileHandleForReading 58 | var outString: String = "" 59 | if let line = String(data: outputHandle.availableData, encoding: String.Encoding.utf8) { 60 | outString = line 61 | } 62 | 63 | if outString.count > 0 { 64 | print("\(outString)") 65 | } else { 66 | print("Error", "Exit code \(task.terminationStatus)") 67 | } 68 | return false 69 | } 70 | } 71 | 72 | return true 73 | } 74 | 75 | 76 | // MARK: - Misc Functions 77 | 78 | /** 79 | Present an error message specific to sending feedback. 80 | 81 | This is called from multiple locations: if the initial request can't be created, 82 | there was a send failure, or a server error. 83 | */ 84 | internal func sendFeedbackError() { 85 | 86 | let alert: NSAlert = showAlert("Feedback Could Not Be Sent", 87 | "Unfortunately, your comments could not be send at this time. Please try again later.") 88 | alert.beginSheetModal(for: self.reportWindow) { (resp) in 89 | self.window.endSheet(self.reportWindow) 90 | self.showPanelGenerators() 91 | } 92 | } 93 | 94 | 95 | /** 96 | Generic alert generator. 97 | 98 | - Parameters: 99 | - head: The alert's title. 100 | - message: The alert's message. 101 | - addOkButton: Should we add an OK button? 102 | 103 | - Returns: The NSAlert. 104 | */ 105 | internal func showAlert(_ head: String, _ message: String, _ addOkButton: Bool = true) -> NSAlert { 106 | 107 | let alert: NSAlert = NSAlert() 108 | alert.messageText = head 109 | alert.informativeText = message 110 | if addOkButton { alert.addButton(withTitle: "OK") } 111 | return alert 112 | } 113 | 114 | 115 | /** 116 | Build a basic 'major.manor' version string for prefs usage. 117 | 118 | - Returns: The version string. 119 | */ 120 | internal func getVersion() -> String { 121 | 122 | let version: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String 123 | let parts: [String] = (version as NSString).components(separatedBy: ".") 124 | return parts[0] + "-" + parts[1] 125 | } 126 | 127 | 128 | /** 129 | Build a date string string for feedback usage. 130 | 131 | - Returns: The date string. 132 | */ 133 | internal func getDateForFeedback() -> String { 134 | 135 | let date: Date = Date() 136 | let dateFormatter: DateFormatter = DateFormatter() 137 | dateFormatter.locale = Locale(identifier: "en_US_POSIX") 138 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" 139 | dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) 140 | return dateFormatter.string(from: date) 141 | } 142 | 143 | 144 | /** 145 | Build a user-agent string string for feedback usage. 146 | 147 | - Returns: The user-agent string. 148 | */ 149 | internal func getUserAgentForFeedback() -> String { 150 | 151 | // Refactor code out into separate function for clarity 152 | 153 | let sysVer: OperatingSystemVersion = ProcessInfo.processInfo.operatingSystemVersion 154 | let bundle: Bundle = Bundle.main 155 | let app: String = bundle.object(forInfoDictionaryKey: "CFBundleExecutable") as! String 156 | let version: String = bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String 157 | let build: String = bundle.object(forInfoDictionaryKey: "CFBundleVersion") as! String 158 | return "\(app)/\(version)-\(build) (macOS/\(sysVer.majorVersion).\(sysVer.minorVersion).\(sysVer.patchVersion))" 159 | } 160 | 161 | 162 | /** 163 | Read back the host system's registered UTI for the specified file. 164 | 165 | This is not PII. It used solely for debugging purposes 166 | 167 | - Parameters: 168 | - filename: The file we'll use to get the UTI. 169 | 170 | - Returns: The file's UTI. 171 | */ 172 | internal func getLocalFileUTI(_ filename: String) -> String { 173 | 174 | var localUTI: String = "NONE" 175 | let samplePath = Bundle.main.resourcePath! + "/" + filename 176 | 177 | if FileManager.default.fileExists(atPath: samplePath) { 178 | // Create a URL reference to the sample file 179 | let sampleURL = URL.init(fileURLWithPath: samplePath) 180 | 181 | do { 182 | // Read back the UTI from the URL 183 | // Use Big Sur's UTType API 184 | if #available(macOS 11, *) { 185 | if let uti: UTType = try sampleURL.resourceValues(forKeys: [.contentTypeKey]).contentType { 186 | localUTI = uti.identifier 187 | } 188 | } else { 189 | // NOTE '.typeIdentifier' yields an optional 190 | if let uti: String = try sampleURL.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier { 191 | localUTI = uti 192 | } 193 | } 194 | } catch { 195 | // NOP 196 | } 197 | } 198 | 199 | return localUTI 200 | } 201 | 202 | 203 | /** 204 | Disable all panel-opening menu items. 205 | */ 206 | internal func hidePanelGenerators() { 207 | 208 | //self.helpMenuReportBug.isEnabled = false 209 | self.helpMenuWhatsNew.isEnabled = false 210 | self.mainMenuSettings.isEnabled = false 211 | } 212 | 213 | 214 | /** 215 | Enable all panel-opening menu items. 216 | */ 217 | internal func showPanelGenerators() { 218 | 219 | //self.helpMenuReportBug.isEnabled = true 220 | self.helpMenuWhatsNew.isEnabled = true 221 | self.mainMenuSettings.isEnabled = true 222 | } 223 | 224 | 225 | /** 226 | Get system and state information and record it for use during run. 227 | */ 228 | internal func recordSystemState() { 229 | 230 | // First ensure we are running on Mojave or above - Dark Mode is not supported by earlier versons 231 | let sysVer: OperatingSystemVersion = ProcessInfo.processInfo.operatingSystemVersion 232 | self.isMontereyPlus = (sysVer.majorVersion >= 12) 233 | } 234 | 235 | 236 | /** 237 | Determine whether the host Mac is in light mode. 238 | 239 | - Returns: `true` if the Mac is in light mode, otherwise `false`. 240 | */ 241 | internal func isMacInLightMode() -> Bool { 242 | 243 | let appearNameString: String = NSApp.effectiveAppearance.name.rawValue 244 | return (appearNameString == "NSAppearanceNameAqua") 245 | } 246 | 247 | 248 | // MARK: - URLSession Delegate Functions 249 | 250 | func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) { 251 | 252 | // Some sort of connection error - report it 253 | self.connectionProgress.stopAnimation(self) 254 | sendFeedbackError() 255 | } 256 | 257 | 258 | func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { 259 | 260 | // The operation to send the comment completed 261 | self.connectionProgress.stopAnimation(self) 262 | if let _ = error { 263 | // An error took place - report it 264 | sendFeedbackError() 265 | } else { 266 | // The comment was submitted successfully 267 | let alert: NSAlert = showAlert("Thanks For Your Feedback!", 268 | "Your comments have been received and we’ll take a look at them shortly.") 269 | alert.beginSheetModal(for: self.reportWindow) { (resp) in 270 | // Close the feedback window when the modal alert returns 271 | let _: Timer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { timer in 272 | self.window.endSheet(self.reportWindow) 273 | self.showPanelGenerators() 274 | } 275 | } 276 | } 277 | } 278 | 279 | 280 | // MARK: - WKWebNavigation Delegate Functions 281 | 282 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { 283 | 284 | // Asynchronously show the sheet once the HTML has loaded 285 | // (triggered by delegate method) 286 | 287 | if let nav = self.whatsNewNav { 288 | if nav == navigation { 289 | // FROM 1.0.5 -- add timer to prevent 'white flash' 290 | Timer.scheduledTimer(withTimeInterval: 0.05, repeats: false) { timer in 291 | timer.invalidate() 292 | self.window.beginSheet(self.whatsNewWindow, completionHandler: nil) 293 | } 294 | } 295 | } 296 | } 297 | } 298 | 299 | 300 | extension NSApplication { 301 | 302 | func isMacInLightMode() -> Bool { 303 | 304 | return (self.effectiveAppearance.name.rawValue == "NSAppearanceNameAqua") 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /PreviewText/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIconFile 6 | 7 | ITSAppUsesNonExemptEncryption 8 | 9 | LSMinimumSystemVersion 10 | $(MACOSX_DEPLOYMENT_TARGET) 11 | UTExportedTypeDeclarations 12 | 13 | 14 | UTTypeConformsTo 15 | 16 | public.text 17 | 18 | UTTypeDescription 19 | Subtitle file 20 | UTTypeIcons 21 | 22 | UTTypeIconText 23 | Subtitle 24 | 25 | UTTypeIdentifier 26 | com.bps.sub 27 | UTTypeTagSpecification 28 | 29 | public.filename-extension 30 | 31 | sub 32 | 33 | public.mime-type 34 | 35 | text/plain 36 | 37 | 38 | 39 | 40 | UTTypeConformsTo 41 | 42 | public.text 43 | 44 | UTTypeDescription 45 | Info 46 | UTTypeIcons 47 | 48 | UTTypeIconText 49 | Info 50 | 51 | UTTypeIdentifier 52 | com.bps.nfo 53 | UTTypeTagSpecification 54 | 55 | public.filename-extension 56 | 57 | nfo 58 | info 59 | inf 60 | 61 | public.mime-type 62 | 63 | text/x-nfo 64 | 65 | 66 | 67 | 68 | UTTypeConformsTo 69 | 70 | public.text 71 | 72 | UTTypeDescription 73 | First 74 | UTTypeIcons 75 | 76 | UTTypeIconText 77 | Info 78 | 79 | UTTypeIdentifier 80 | com.bps.1st 81 | UTTypeTagSpecification 82 | 83 | public.filename-extension 84 | 85 | 1st 86 | 87 | public.mime-type 88 | 89 | text/plain 90 | 91 | 92 | 93 | 94 | UTTypeConformsTo 95 | 96 | public.text 97 | 98 | UTTypeDescription 99 | ReStructuredText 100 | UTTypeIcons 101 | 102 | UTTypeIconText 103 | RSText 104 | 105 | UTTypeIdentifier 106 | com.bps.rst 107 | UTTypeTagSpecification 108 | 109 | public.filename-extension 110 | 111 | rst 112 | 113 | public.mime-type 114 | 115 | text/x-rst 116 | 117 | 118 | 119 | 120 | UTTypeConformsTo 121 | 122 | public.text 123 | 124 | UTTypeDescription 125 | Me 126 | UTTypeIcons 127 | 128 | UTTypeIconText 129 | Me 130 | 131 | UTTypeIdentifier 132 | com.bps.me 133 | UTTypeTagSpecification 134 | 135 | public.filename-extension 136 | 137 | me 138 | 139 | public.mime-type 140 | 141 | text/plain 142 | 143 | 144 | 145 | 146 | UTTypeConformsTo 147 | 148 | public.text 149 | 150 | UTTypeDescription 151 | Go Config 152 | UTTypeIcons 153 | 154 | UTTypeIconText 155 | Go Conf 156 | 157 | UTTypeIdentifier 158 | com.bps.goconfig 159 | UTTypeTagSpecification 160 | 161 | public.filename-extension 162 | 163 | work 164 | sum 165 | mod 166 | 167 | 168 | 169 | 170 | UTTypeConformsTo 171 | 172 | public.text 173 | 174 | UTTypeDescription 175 | I/O 176 | UTTypeIcons 177 | 178 | UTTypeIconText 179 | Input/Output 180 | 181 | UTTypeIdentifier 182 | com.bps.io 183 | UTTypeTagSpecification 184 | 185 | public.filename-extension 186 | 187 | in 188 | out 189 | 190 | public.mime-type 191 | 192 | text/plain 193 | 194 | 195 | 196 | 197 | UTTypeConformsTo 198 | 199 | public.text 200 | 201 | UTTypeDescription 202 | Toml 203 | UTTypeIcons 204 | 205 | UTTypeIconText 206 | TOML 207 | 208 | UTTypeIdentifier 209 | com.bps.toml 210 | UTTypeReferenceURL 211 | https://toml.io/en/ 212 | UTTypeTagSpecification 213 | 214 | public.filename-extension 215 | 216 | toml 217 | 218 | public.mime-type 219 | 220 | application/toml 221 | 222 | 223 | 224 | 225 | UTTypeConformsTo 226 | 227 | public.plain-text 228 | 229 | UTTypeDescription 230 | Config 231 | UTTypeIcons 232 | 233 | UTTypeIdentifier 234 | com.bps.config 235 | UTTypeTagSpecification 236 | 237 | public.filename-extension 238 | 239 | config 240 | 241 | 242 | 243 | 244 | UTTypeConformsTo 245 | 246 | public.plain-text 247 | 248 | UTTypeDescription 249 | Subtitle 250 | UTTypeIcons 251 | 252 | UTTypeIconText 253 | Subtitle 254 | 255 | UTTypeIdentifier 256 | com.bps.srt 257 | UTTypeTagSpecification 258 | 259 | public.filename-extension 260 | 261 | srt 262 | 263 | public.mime-type 264 | 265 | text/plain 266 | 267 | 268 | 269 | 270 | UTTypeConformsTo 271 | 272 | public.plain-text 273 | 274 | UTTypeDescription 275 | Public Key 276 | UTTypeIcons 277 | 278 | UTTypeIconText 279 | Public Key 280 | 281 | UTTypeIdentifier 282 | com.bps.pubkey 283 | UTTypeTagSpecification 284 | 285 | public.filename-extension 286 | 287 | pub 288 | 289 | public.mime-type 290 | 291 | text/plain 292 | 293 | 294 | 295 | 296 | UTImportedTypeDeclarations 297 | 298 | 299 | UTTypeDescription 300 | PlainText 301 | UTTypeIcons 302 | 303 | UTTypeIdentifier 304 | public.plain-text 305 | UTTypeTagSpecification 306 | 307 | public.filename-extension 308 | 309 | txt 310 | text 311 | 312 | public.mime-type 313 | 314 | text/plain 315 | 316 | 317 | 318 | 319 | UTTypeDescription 320 | PlainDoc 321 | UTTypeIcons 322 | 323 | UTTypeIdentifier 324 | public.document 325 | UTTypeTagSpecification 326 | 327 | public.mime-type 328 | 329 | 330 | 331 | 332 | UTTypeDescription 333 | PlainUTF8Text 334 | UTTypeIcons 335 | 336 | UTTypeIdentifier 337 | public.utf8-plain-text 338 | UTTypeTagSpecification 339 | 340 | public.filename-extension 341 | 342 | txt 343 | 344 | public.mime-type 345 | 346 | text/plain;charset=UTF-8 347 | 348 | 349 | 350 | 351 | UTTypeConformsTo 352 | 353 | public.plain-text 354 | 355 | UTTypeDescription 356 | SubRip 357 | UTTypeIcons 358 | 359 | UTTypeIconText 360 | Subtitle 361 | 362 | UTTypeIdentifier 363 | cz.wz.zuggy.subrip 364 | UTTypeTagSpecification 365 | 366 | public.filename-extension 367 | 368 | srt 369 | 370 | public.mime-type 371 | 372 | text/plain 373 | 374 | 375 | 376 | 377 | 378 | 379 | -------------------------------------------------------------------------------- /PreviewText/PMFont.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * PMFont.swift 3 | * PreviewApps 4 | * 5 | * Created by Tony Smith on 02/07/2021. 6 | * Copyright © 2025 Tony Smith. All rights reserved. 7 | */ 8 | 9 | 10 | import Foundation 11 | 12 | /** 13 | Internal font record structure. 14 | */ 15 | 16 | struct PMFont { 17 | 18 | var postScriptName: String = "" 19 | var displayName: String = "" 20 | var styleName: String = "" 21 | var traits: UInt = 0 22 | var styles: [PMFont]? = nil 23 | } 24 | -------------------------------------------------------------------------------- /PreviewText/PTFontExtensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * GenericExtensions.swift 3 | * PreviewMarkdown/PreviewText 4 | * 5 | * These functions can be used by all PreviewApps 6 | * 7 | * Created by Tony Smith on 18/06/2021. 8 | * Copyright © 2025 Tony Smith. All rights reserved. 9 | */ 10 | 11 | 12 | import Foundation 13 | import Cocoa 14 | import WebKit 15 | import UniformTypeIdentifiers 16 | 17 | 18 | extension AppDelegate { 19 | 20 | // MARK: - Font Management 21 | 22 | /** 23 | Build a list of available fonts. 24 | 25 | Should be called asynchronously. Two sets created: monospace fonts and regular fonts. 26 | Requires 'bodyFonts' and 'codeFonts' to be set as instance properties. 27 | Comment out either of these, as required. 28 | 29 | The final font lists each comprise pairs of strings: the font's PostScript name 30 | then its display name. 31 | */ 32 | internal func asyncGetFonts() { 33 | 34 | var bf: [PMFont] = [] 35 | 36 | let mono: UInt = NSFontTraitMask.fixedPitchFontMask.rawValue 37 | let bold: UInt = NSFontTraitMask.boldFontMask.rawValue 38 | let ital: UInt = NSFontTraitMask.italicFontMask.rawValue 39 | let symb: UInt = NSFontTraitMask.nonStandardCharacterSetFontMask.rawValue 40 | 41 | let fm: NSFontManager = NSFontManager.shared 42 | 43 | let families: [String] = fm.availableFontFamilies 44 | for family in families { 45 | // Remove known unwanted fonts 46 | if family.hasPrefix(".") || family.hasPrefix("Apple Braille") || family == "Apple Color Emoji" { 47 | continue 48 | } 49 | 50 | // For each family, examine its fonts for suitable ones 51 | if let fonts: [[Any]] = fm.availableMembers(ofFontFamily: family) { 52 | // This will hold a font family: individual fonts will be added to 53 | // the 'styles' array 54 | var familyRecord: PMFont = PMFont.init() 55 | familyRecord.displayName = family 56 | 57 | for font: [Any] in fonts { 58 | let psname: String = font[0] as! String 59 | let traits: UInt = font[3] as! UInt 60 | var doUseFont: Bool = false 61 | 62 | if mono & traits != 0 { 63 | doUseFont = true 64 | } else if traits & bold == 0 && traits & ital == 0 && traits & symb == 0 { 65 | doUseFont = true 66 | } 67 | 68 | if doUseFont { 69 | // The font is good to use, so add it to the list 70 | var fontRecord: PMFont = PMFont.init() 71 | fontRecord.postScriptName = psname 72 | fontRecord.styleName = font[1] as! String 73 | fontRecord.traits = traits 74 | 75 | if familyRecord.styles == nil { 76 | familyRecord.styles = [] 77 | } 78 | 79 | familyRecord.styles!.append(fontRecord) 80 | } 81 | } 82 | 83 | if familyRecord.styles != nil && familyRecord.styles!.count > 0 { 84 | bf.append(familyRecord) 85 | } 86 | } 87 | } 88 | 89 | DispatchQueue.main.async { 90 | self.bodyFonts = bf 91 | } 92 | } 93 | 94 | 95 | /** 96 | Build and enable the font style popup. 97 | 98 | - Parameters: 99 | - isBody: Whether we're handling body text font styles (`true`) or code font styles (`false`). Default: `true`. 100 | - styleName: The name of the selected style. Default: `nil`. 101 | */ 102 | internal func setStylePopup(_ styleName: String? = nil) { 103 | 104 | let selectedFamily: String = self.codeFontPopup.titleOfSelectedItem! 105 | let familyList: [PMFont] = self.bodyFonts 106 | let targetPopup: NSPopUpButton = self.codeStylePopup 107 | targetPopup.removeAllItems() 108 | 109 | for family: PMFont in familyList { 110 | if selectedFamily == family.displayName { 111 | if let styles: [PMFont] = family.styles { 112 | targetPopup.isEnabled = true 113 | for style: PMFont in styles { 114 | targetPopup.addItem(withTitle: style.styleName) 115 | } 116 | 117 | if styleName != nil { 118 | targetPopup.selectItem(withTitle: styleName!) 119 | } 120 | } 121 | } 122 | } 123 | } 124 | 125 | 126 | /** 127 | Select the font popup using the stored PostScript name 128 | of the user's chosen font. 129 | 130 | - Parameters: 131 | - postScriptName: The PostScript name of the font. 132 | - isBody: Whether we're handling body text font styles (`true`) or code font styles (`false`). 133 | */ 134 | internal func selectFontByPostScriptName(_ postScriptName: String) { 135 | 136 | let familyList: [PMFont] = self.bodyFonts 137 | let targetPopup: NSPopUpButton = self.codeFontPopup 138 | 139 | for family: PMFont in familyList { 140 | if let styles: [PMFont] = family.styles { 141 | for style: PMFont in styles { 142 | if style.postScriptName == postScriptName { 143 | targetPopup.selectItem(withTitle: family.displayName) 144 | setStylePopup(style.styleName) 145 | } 146 | } 147 | } 148 | } 149 | } 150 | 151 | 152 | /** 153 | Get the PostScript name from the selected family and style. 154 | 155 | - Parameters: 156 | - isBody: Whether we're handling body text font styles (`true`) or code font styles (`false`). 157 | 158 | - Returns: The PostScript name as a string, or nil. 159 | */ 160 | internal func getPostScriptName() -> String? { 161 | 162 | let familyList: [PMFont] = self.bodyFonts 163 | let fontPopup: NSPopUpButton = self.codeFontPopup 164 | let stylePopup: NSPopUpButton = self.codeStylePopup 165 | 166 | if let selectedFont: String = fontPopup.titleOfSelectedItem { 167 | let selectedStyle: Int = stylePopup.indexOfSelectedItem 168 | 169 | for family: PMFont in familyList { 170 | if family.displayName == selectedFont { 171 | if let styles: [PMFont] = family.styles { 172 | let font: PMFont = styles[selectedStyle] 173 | return font.postScriptName 174 | } 175 | } 176 | } 177 | } 178 | 179 | return nil 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /PreviewText/PreviewText.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.application-groups 8 | 9 | $(TeamIdentifierPrefix)suite.preview-previewtext 10 | 11 | com.apple.security.automation.apple-events 12 | 13 | com.apple.security.files.downloads.read-only 14 | 15 | com.apple.security.files.user-selected.read-only 16 | 17 | com.apple.security.network.client 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /PreviewText/text-sample.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. In lacinia aliquet velit. Suspendisse pulvinar erat vel velit viverra dignissim. Aliquam dapibus sem aliquet iaculis consequat. Morbi nisl quam, mattis nec enim quis, ultrices bibendum sem. Nullam semper felis ac quam pharetra interdum. Nulla facilisi. Quisque aliquet tempor blandit. Sed quis suscipit justo, semper facilisis lacus. 2 | Phasellus fermentum, leo nec tristique efficitur, tortor est gravida est, non fringilla turpis metus in eros. Praesent ac est vitae ex posuere dapibus. Vivamus vel quam leo. Phasellus facilisis justo mi. Aenean et sem eget sem pulvinar pulvinar. Fusce libero ligula, venenatis ac feugiat vel, tempor vel erat. Curabitur dignissim elit at euismod laoreet. Sed in tempus lectus. Vestibulum ac commodo nisl, ac laoreet elit. Sed eu nulla sed felis semper dapibus. Duis egestas risus nisi, non porttitor est efficitur non. Duis pulvinar elementum tellus vel interdum. Cras leo justo, euismod condimentum felis sed, consectetur cursus eros. Maecenas porttitor hendrerit mauris, eu dictum risus pharetra in. Aliquam sagittis purus et imperdiet consequat. Aenean mattis finibus cursus. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PreviewText 1.0.9 2 | 3 | QuickLook preview and icon thumbnailing app extensions for macOS Catalina and beyond. 4 | 5 | *PreviewText* provides previews and thumbnails for textual files that have no file extension, or use non-standard extensions, including `.toml`, `.nfo`, `.1st`, `.srt` and `.sub`. 6 | 7 | *PreviewText* is [available free of charge from the Mac App Store](https://apps.apple.com/us/app/previewtext/id1660037028). 8 | 9 | ![PreviewText App Store QR code](qr-code.png) 10 | 11 | ## Installation and Usage ## 12 | 13 | Just run the host app once to register the extensions — you can quit the app as soon as it has launched. We recommend logging out of your Mac and back in again at this point. Now you can preview textual documents using QuickLook (select an icon and hit Space), and Finder’s preview pane and **Info** panels. 14 | 15 | You can disable and re-enable the *Text Previewer* and *Text Thumbnailer* extensions at any time in **System Preferences > Extensions > Quick Look**. 16 | 17 | ### Adjusting the Preview ### 18 | 19 | You can change some of the key elements of the preview by using the **Preferences** panel. 20 | 21 | Changing these settings will affect previews immediately, but may not affect thumbnail until you open a folder that has not been previously opened in the current login session. 22 | 23 | ## Source Code 24 | 25 | This repository contains the primary source code for *PreviewText*. Certain graphical assets, code components and data files are not included. To build *PreviewText* from scratch, you will need to add these files yourself or remove them from your fork. 26 | 27 | The files `REPLACE_WITH_YOUR_FUNCTIONS` and `REPLACE_WITH_YOUR_CODES` must be replaced with your own files. The former will contain your `sendFeedback(_ feedback: String) -> URLSessionTask?` function. The latter your Developer Team ID, used as the App Suite identifier prefix. 28 | 29 | You will need to generate your own `Assets.xcassets` file containing the app icon, `app_logo.png` and theme screenshots. 30 | 31 | You will need to create your own `new` directory containing your own `new.html` file. 32 | 33 | ## Contributions ## 34 | 35 | Contributions are welcome, but pull requestss can only be accepted when they target the `develop` branch. PRs targetting `main` will be rejected. 36 | 37 | Contributions will only be accepted if they code they contain is licensed under the terms of [the MIT Licence](#LICENSE.md) 38 | 39 | ## Release Notes ## 40 | 41 | - 1.0.9 *April 2025* 42 | - Preparation work ahead of deprecation. 43 | - 1.0.8 *16 January 2025* 44 | - Support .pub public key files. 45 | - Improved thumbnail rendering. 46 | - 1.0.7 *1 December 2024* 47 | - Remove support for `.conf` files — conflict with [PreviewCode](https://smittytone.net/previewcode/index.html) for these files. 48 | - Correct build target to macOS 10.15 Catalina. 49 | - 1.0.6 *3 September 2024* 50 | - Support `.conf` and `.config` files. 51 | - Revert NSTextViews to TextKit 1 (previously bumped to 2 by Xcode). 52 | - Correct **Preferences** panel preview behaviour in Dark Mode. 53 | - Improve preference change handling. 54 | - Fix preview colour changes across modes and mode changes. 55 | - 1.0.5 *29 April 2024* 56 | - Support `.toml` files. 57 | - Allow the user to set a minimum size below which thumbnails will not be rendered because they’ll be too small to be of value. Default: 48 pixels. 58 | - Revise Thumbnailer code with a view to reducing massive memory usage seen by some users, and as-yet-unexplained Thumbnailer crashes. 59 | - Fix the 'white flash' seen on loading the What's New sheet. 60 | - 1.0.4 *14 August 2023* 61 | - Non-shipping release: repo/code reorganisation. 62 | - 1.0.3 *12 May 2023* 63 | - Remove `.asc` — conflict with [PreviewCode](https://smittytone.net/previewcode/index.html) for Asciidoc files. 64 | - 1.0.2 *22 March 2023* 65 | - Make previewed text selectable. 66 | - Support `go.mod`, `go.sum` and `go.work` files. 67 | - Support `.in` and `.out` files. 68 | - 1.0.1 *21 January 2023* 69 | - Fix issue with line-spacing (thanks @aaronkollasch). 70 | - Correctly preview line-spacing changes. 71 | - 1.0.0 *23 December 2022* 72 | - Initial public release. 73 | 74 | ## Copyright and Credits ## 75 | 76 | Primary app code and UI design © 2025, Tony Smith. 77 | -------------------------------------------------------------------------------- /Text Previewer/Base.lproj/PreviewViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /Text Previewer/Common.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Common.swift 3 | * PreviewText 4 | * Code common to Text Previewer and Text Thumbnailer 5 | * 6 | * Created by Tony Smith on 05/10/2023. 7 | * Copyright © 2025 Tony Smith. All rights reserved. 8 | */ 9 | 10 | 11 | import Foundation 12 | import Cocoa 13 | import AppKit 14 | import UniformTypeIdentifiers 15 | 16 | 17 | final class Common: NSObject { 18 | 19 | // MARK: - Public Properties 20 | 21 | var isLightMode: Bool = true 22 | var inkColour: String = BUFFOON_CONSTANTS.INK_COLOUR_HEX 23 | var paperColour: String = BUFFOON_CONSTANTS.PAPER_COLOUR_HEX 24 | var fontSize: CGFloat = BUFFOON_CONSTANTS.BASE_PREVIEW_FONT_SIZE 25 | var lineSpacing: CGFloat = BUFFOON_CONSTANTS.BASE_LINE_SPACING 26 | // FROM 1.0.5 27 | var minimumTumbnailSize: Int = BUFFOON_CONSTANTS.MIN_THUMB_SIZE 28 | 29 | /* 30 | Replace the following string with your own team ID. This is used to 31 | identify the app suite and so share preferences set by the main app with 32 | the previewer and thumbnailer extensions. 33 | */ 34 | private var appSuiteName: String = MNU_SECRETS.PID + BUFFOON_CONSTANTS.SUITE_NAME 35 | 36 | 37 | // MARK: - Private Properties 38 | 39 | private var isThumbnail: Bool = false 40 | private var alwaysLightMode: Bool = false 41 | 42 | // String artifacts... 43 | private var textAtts: [NSAttributedString.Key: Any] = [:] 44 | private var hr: NSAttributedString = NSAttributedString.init(string: "") 45 | private var newLine: NSAttributedString = NSAttributedString.init(string: "") 46 | // FROM 1.0.8 47 | private var debugAtts: [NSAttributedString.Key: Any] = [:] 48 | 49 | 50 | // MARK:- Lifecycle Functions 51 | 52 | init(_ isThumbnail: Bool = false) { 53 | 54 | super.init() 55 | 56 | // Set up instance properties 57 | self.isThumbnail = isThumbnail 58 | setProperties() 59 | } 60 | 61 | 62 | func setProperties(_ usePrefs: Bool = true, _ fontName: String = BUFFOON_CONSTANTS.BODY_FONT_NAME) { 63 | 64 | var baseFontName: String = fontName 65 | var baseInkColour: String = BUFFOON_CONSTANTS.INK_COLOUR_HEX 66 | var basePaperColour: String = BUFFOON_CONSTANTS.PAPER_COLOUR_HEX 67 | 68 | // The suite name is the app group name, set in each extension's entitlements, and the host app's 69 | if usePrefs { 70 | if let prefs = UserDefaults(suiteName: self.appSuiteName) { 71 | // First check the Mac's mode 72 | self.alwaysLightMode = prefs.bool(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_USE_LIGHT) 73 | 74 | // This should be true if we're rendering a thumbnail, the user wants 75 | // a dark-on-light preview even in Dark Mode, or the Mac is in Light Mode 76 | self.isLightMode = (isThumbnail || self.alwaysLightMode || isMacInLightMode()) 77 | 78 | // Set current ink and paper colours 79 | baseInkColour = prefs.string(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_INK_COLOUR) ?? BUFFOON_CONSTANTS.INK_COLOUR_HEX 80 | basePaperColour = prefs.string(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_PAPER_COLOUR) ?? BUFFOON_CONSTANTS.PAPER_COLOUR_HEX 81 | 82 | // Set the used colours according to the mode 83 | if isLightMode { 84 | self.inkColour = baseInkColour 85 | self.paperColour = basePaperColour 86 | } else { 87 | self.inkColour = basePaperColour 88 | self.paperColour = baseInkColour 89 | } 90 | 91 | // Get font sizes 92 | self.fontSize = isThumbnail 93 | ? BUFFOON_CONSTANTS.BASE_THUMB_FONT_SIZE 94 | : CGFloat(prefs.float(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_FONT_SIZE)) 95 | baseFontName = prefs.string(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_FONT_NAME) ?? BUFFOON_CONSTANTS.BODY_FONT_NAME 96 | 97 | // Set line spacing 98 | self.lineSpacing = isThumbnail 99 | ? BUFFOON_CONSTANTS.BASE_THUMB_LINE_SPACING 100 | : CGFloat(prefs.float(forKey: BUFFOON_CONSTANTS.PREFS_IDS.PREVIEW_LINE_SPACING)) 101 | 102 | // FROM 1.0.5 103 | // Set minimum icon size 104 | self.minimumTumbnailSize = prefs.integer(forKey: BUFFOON_CONSTANTS.PREFS_IDS.THUMB_MIN_SIZE) 105 | } 106 | 107 | // Just in case the above block reads in zero values 108 | // NOTE The other values CAN be zero 109 | if self.fontSize < CGFloat(BUFFOON_CONSTANTS.FONT_SIZE_OPTIONS[0]) || 110 | self.fontSize > CGFloat(BUFFOON_CONSTANTS.FONT_SIZE_OPTIONS[BUFFOON_CONSTANTS.FONT_SIZE_OPTIONS.count - 1]) { 111 | self.fontSize = CGFloat(isThumbnail ? BUFFOON_CONSTANTS.BASE_THUMB_FONT_SIZE : BUFFOON_CONSTANTS.BASE_PREVIEW_FONT_SIZE) 112 | } 113 | } 114 | 115 | // Create the font we'll use 116 | var font: NSFont 117 | if let chosenFont: NSFont = NSFont.init(name: baseFontName, size: self.fontSize) { 118 | font = chosenFont 119 | } else { 120 | font = NSFont.systemFont(ofSize: self.fontSize) 121 | } 122 | 123 | // Set up the attributed string components we may use during rendering 124 | let textParaStyle: NSMutableParagraphStyle = NSMutableParagraphStyle.init() 125 | textParaStyle.lineSpacing = (self.lineSpacing - 1) * self.fontSize 126 | textParaStyle.paragraphSpacing = 0.0 127 | 128 | self.textAtts = [ 129 | .foregroundColor: NSColor.hexToColour(self.inkColour), 130 | .font: font, 131 | .paragraphStyle: textParaStyle 132 | ] 133 | 134 | self.debugAtts = [ 135 | .foregroundColor: NSColor.systemRed, 136 | .font: font, 137 | .paragraphStyle: textParaStyle 138 | ] 139 | 140 | // Horizontal line 141 | // NOTE This formulation requires TextKit 1 142 | self.hr = NSAttributedString(string: "\n\u{00A0}\u{0009}\u{00A0}\n\n", 143 | attributes: [.strikethroughStyle: NSUnderlineStyle.thick.rawValue, 144 | .strikethroughColor: NSColor.hexToColour(self.inkColour)] 145 | ) 146 | 147 | // New line symbol 148 | self.newLine = NSAttributedString.init(string: "\n", 149 | attributes: textAtts) 150 | } 151 | 152 | 153 | // MARK:- The Primary Function 154 | 155 | /** 156 | Render the input JSON as an NSAttributedString. 157 | 158 | - Parameters: 159 | - textString: The path to the JSON code. 160 | 161 | - Returns: The rendered source as an NSAttributedString. 162 | */ 163 | func getAttributedString(_ textString: String) -> NSAttributedString { 164 | 165 | // Set up the base string 166 | #if DEBUG 167 | let renderedString: NSMutableAttributedString = NSMutableAttributedString.init(string: textString, 168 | attributes: self.textAtts) 169 | var encodingRange: NSRange = (textString as NSString).range(of: "PTDEBUG\n") 170 | if encodingRange.location != NSNotFound { 171 | encodingRange = NSMakeRange(0, encodingRange.location + 7) 172 | renderedString.setAttributes(self.debugAtts, range: encodingRange) 173 | } 174 | #else 175 | let renderedString: NSMutableAttributedString = NSMutableAttributedString.init(string: textString, 176 | attributes: self.textAtts) 177 | #endif 178 | return renderedString as NSAttributedString 179 | } 180 | 181 | 182 | /** 183 | Determine whether the host Mac is in light mode. 184 | 185 | - Returns: `true` if the Mac is in light mode, otherwise `false`. 186 | */ 187 | private func isMacInLightMode() -> Bool { 188 | 189 | let appearNameString: String = NSApp.effectiveAppearance.name.rawValue 190 | return (appearNameString == "NSAppearanceNameAqua") 191 | } 192 | 193 | 194 | /** 195 | Handler for macOS UI mode change notifications 196 | */ 197 | @objc private func interfaceModeChanged() { 198 | 199 | // Do nothing if we're thumbnailing... 200 | if !self.isThumbnail { 201 | // ...otherwise reset the properties 202 | setProperties() 203 | } 204 | } 205 | 206 | 207 | /** 208 | Get the supplied source file's UTI. 209 | 210 | We'll use it to determine the file's programming language. 211 | 212 | - Parameters: 213 | - sourceFilePath: The path to the source code file. 214 | 215 | - Returns: The source code's UTI. 216 | */ 217 | func getSourceFileUTI(_ sourceFilePath: String) -> String { 218 | 219 | // Create a URL reference to the sample file 220 | var sourceFileUTI: String = "" 221 | let sourceFileURL = URL.init(fileURLWithPath: sourceFilePath) 222 | 223 | do { 224 | // Read back the UTI from the URL 225 | // Use Big Sur's UTType API 226 | if #available(macOS 11, *) { 227 | if let uti: UTType = try sourceFileURL.resourceValues(forKeys: [.contentTypeKey]).contentType { 228 | sourceFileUTI = uti.identifier 229 | } 230 | } else { 231 | // NOTE '.typeIdentifier' yields an optional 232 | if let uti: String = try sourceFileURL.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier { 233 | sourceFileUTI = uti 234 | } 235 | } 236 | } catch { 237 | // NOP 238 | } 239 | 240 | return sourceFileUTI 241 | } 242 | } 243 | 244 | 245 | /** 246 | Get the encoding of the string formed from data. 247 | 248 | - Returns: The string's encoding or nil. 249 | */ 250 | 251 | extension Data { 252 | 253 | var stringEncoding: String.Encoding? { 254 | 255 | guard case let rawValue = NSString.stringEncoding(for: self, 256 | encodingOptions: nil, 257 | convertedString: nil, 258 | usedLossyConversion: nil), rawValue != 0 else { return nil } 259 | return .init(rawValue: rawValue) 260 | } 261 | } 262 | 263 | -------------------------------------------------------------------------------- /Text Previewer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionAttributes 8 | 9 | QLIsDataBasedPreview 10 | 11 | QLSupportedContentTypes 12 | 13 | public.data 14 | public.content 15 | public.document 16 | public.plain-text 17 | com.apple.log 18 | com.bps.srt 19 | com.bps.sub 20 | com.bps.1st 21 | com.bps.rst 22 | com.bps.nfo 23 | com.bps.me 24 | com.bps.goconfig 25 | com.bps.io 26 | com.bps.toml 27 | com.bps.config 28 | cz.wz.zuggy.subrip 29 | com.bps.pubkey 30 | 31 | QLSupportsSearchableItems 32 | 33 | 34 | NSExtensionMainNibFile 35 | PreviewViewController 36 | NSExtensionPointIdentifier 37 | com.apple.quicklook.preview 38 | NSExtensionPrincipalClass 39 | $(PRODUCT_MODULE_NAME).PreviewViewController 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Text Previewer/PreviewProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewProvider.swift 3 | // JSON Previewer 4 | // 5 | // Created by Tony Smith on 29/08/2022. 6 | // 7 | 8 | import Cocoa 9 | import Quartz 10 | 11 | class PreviewProvider: QLPreviewProvider, QLPreviewingController { 12 | 13 | /* 14 | Use a QLPreviewProvider to provide data-based previews. 15 | 16 | To set up your extension as a data-based preview extension: 17 | 18 | - Modify the extension's Info.plist by setting 19 | QLIsDataBasedPreview 20 | 21 | 22 | - Add the supported content types to QLSupportedContentTypes array in the extension's Info.plist. 23 | 24 | - Change the NSExtensionPrincipalClass to this class. 25 | e.g. 26 | NSExtensionPrincipalClass 27 | $(PRODUCT_MODULE_NAME).PreviewProvider 28 | 29 | - Implement providePreview(for:) 30 | */ 31 | 32 | func providePreview(for request: QLFilePreviewRequest) async throws -> QLPreviewReply { 33 | 34 | //You can create a QLPreviewReply in several ways, depending on the format of the data you want to return. 35 | //To return Data of a supported content type: 36 | 37 | let contentType = UTType.plainText // replace with your data type 38 | 39 | let reply = QLPreviewReply.init(dataOfContentType: contentType, contentSize: CGSize.init(width: 800, height: 800)) { (replyToUpdate : QLPreviewReply) in 40 | 41 | let data = Data("Hello world".utf8) 42 | 43 | //setting the stringEncoding for text and html data is optional and defaults to String.Encoding.utf8 44 | replyToUpdate.stringEncoding = .utf8 45 | 46 | //initialize your data here 47 | 48 | return data 49 | } 50 | 51 | return reply 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Text Previewer/PreviewViewController.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * PreviewViewController.swift 3 | * PreviewText 4 | * 5 | * Created by Tony Smith on 29/08/2023. 6 | * Copyright © 2025 Tony Smith. All rights reserved. 7 | */ 8 | 9 | 10 | import Cocoa 11 | import Quartz 12 | 13 | 14 | class PreviewViewController: NSViewController, 15 | QLPreviewingController { 16 | 17 | 18 | // MARK:- Class UI Properties 19 | 20 | @IBOutlet weak var renderTextView: NSTextView! 21 | @IBOutlet weak var renderTextScrollView: NSScrollView! 22 | @IBOutlet weak var previewErrorLabel: NSTextField! 23 | 24 | 25 | override var nibName: NSNib.Name? { 26 | 27 | return NSNib.Name("PreviewViewController") 28 | } 29 | 30 | 31 | override func loadView() { 32 | 33 | super.loadView() 34 | } 35 | 36 | 37 | func preparePreviewOfFile(at url: URL, completionHandler handler: @escaping (Error?) -> Void) { 38 | 39 | /* 40 | * Main entry point for the macOS preview system 41 | */ 42 | 43 | // Prepare the view 44 | self.previewErrorLabel.stringValue = "" 45 | self.previewErrorLabel.isHidden = true 46 | self.renderTextScrollView.isHidden = false 47 | 48 | // Get an error message ready for use 49 | var reportError: NSError? = nil 50 | 51 | // Set the base values 52 | let common: Common = Common.init() 53 | 54 | // Load the source file using a co-ordinator as we don't know what thread this function 55 | // will be executed in when it's called by macOS' QuickLook code 56 | if FileManager.default.isReadableFile(atPath: url.path) { 57 | // Only proceed if the file is accessible from here 58 | do { 59 | // Get the file contents as a string 60 | let data: Data = try Data.init(contentsOf: url, options: [.uncached]) 61 | let encoding: String.Encoding = data.stringEncoding ?? .utf8 62 | 63 | // FROM 1.0.2 64 | // Get the UTI to check Go config files 65 | let sourceFileUTI: String = common.getSourceFileUTI(url.path).lowercased() 66 | if sourceFileUTI == "com.bps.goconfig" { 67 | if !url.lastPathComponent.starts(with: "go.") { 68 | reportError = setError(BUFFOON_CONSTANTS.ERRORS.CODES.BAD_GO_CONFIG) 69 | showError(reportError!.userInfo[NSLocalizedDescriptionKey] as! String) 70 | handler(reportError) 71 | return 72 | } 73 | } 74 | 75 | if let textString = String.init(data: data, encoding: encoding) { 76 | // Get the key string first 77 | // FROM 1.0.6: display the encoding in the DEBUG version 78 | #if DEBUG 79 | let textAttString: NSAttributedString = common.getAttributedString("\(encoding) [\(encoding.rawValue)] PTDEBUG\n" + textString) 80 | #else 81 | let textAttString: NSAttributedString = common.getAttributedString(textString) 82 | #endif 83 | 84 | // Knock back the light background to make the scroll bars visible in dark mode 85 | // NOTE If !doShowLightBackground, 86 | // in light mode, the scrollers show up dark-on-light, in dark mode light-on-dark 87 | // If doShowLightBackground, 88 | // in light mode, the scrollers show up light-on-light, in dark mode light-on-dark 89 | // NOTE Changing the scrollview scroller knob style has no effect 90 | self.renderTextView.backgroundColor = NSColor.hexToColour(common.paperColour) 91 | self.renderTextScrollView.scrollerKnobStyle = .light //common.isLightMode ? .dark : .light 92 | 93 | if let renderTextStorage: NSTextStorage = self.renderTextView.textStorage { 94 | /* 95 | * NSTextStorage subclasses that return true from the fixesAttributesLazily 96 | * method should avoid directly calling fixAttributes(in:) or else bracket 97 | * such calls with beginEditing() and endEditing() messages. 98 | */ 99 | renderTextStorage.beginEditing() 100 | renderTextStorage.setAttributedString(textAttString) 101 | renderTextStorage.endEditing() 102 | 103 | // Add the subview to the instance's own view and draw 104 | self.view.display() 105 | 106 | // Call the QLPreviewingController indicating no error 107 | // (argument is nil) 108 | handler(nil) 109 | return 110 | } 111 | 112 | // We can't access the preview NSTextView's NSTextStorage 113 | reportError = setError(BUFFOON_CONSTANTS.ERRORS.CODES.BAD_TS_STRING) 114 | } else { 115 | // We couldn't convert to data to a valid encoding 116 | let errDesc: String = "\(BUFFOON_CONSTANTS.ERRORS.MESSAGES.BAD_TS_STRING) \(encoding)" 117 | reportError = NSError(domain: BUFFOON_CONSTANTS.APP_CODE_PREVIEWER, 118 | code: BUFFOON_CONSTANTS.ERRORS.CODES.BAD_MD_STRING, 119 | userInfo: [NSLocalizedDescriptionKey: errDesc]) 120 | } 121 | } catch { 122 | // We couldn't read the file so set an appropriate error to report back 123 | reportError = setError(BUFFOON_CONSTANTS.ERRORS.CODES.FILE_WONT_OPEN) 124 | } 125 | } else { 126 | // We couldn't access the file so set an appropriate error to report back 127 | reportError = setError(BUFFOON_CONSTANTS.ERRORS.CODES.FILE_INACCESSIBLE) 128 | } 129 | 130 | // Display the error locally in the window 131 | showError(reportError!.userInfo[NSLocalizedDescriptionKey] as! String) 132 | 133 | // Call the QLPreviewingController indicating an error 134 | // (argumnet is not nil) 135 | handler(reportError) 136 | } 137 | 138 | 139 | // MARK:- Utility Functions 140 | 141 | /** 142 | Place an error message in its various outlets. 143 | 144 | - parameters: 145 | - errString: The error message. 146 | */ 147 | func showError(_ errString: String) { 148 | 149 | NSLog("BUFFOON \(errString)") 150 | self.renderTextScrollView.isHidden = true 151 | self.previewErrorLabel.stringValue = errString 152 | self.previewErrorLabel.isHidden = false 153 | self.view.display() 154 | } 155 | 156 | 157 | /** 158 | Generate an NSError for an internal error, specified by its code. 159 | 160 | Codes are listed in `Constants.swift` 161 | 162 | - Parameters: 163 | - code: The internal error code. 164 | 165 | - Returns: The described error as an NSError. 166 | */ 167 | func setError(_ code: Int) -> NSError { 168 | 169 | var errDesc: String 170 | 171 | switch(code) { 172 | case BUFFOON_CONSTANTS.ERRORS.CODES.FILE_INACCESSIBLE: 173 | errDesc = BUFFOON_CONSTANTS.ERRORS.MESSAGES.FILE_INACCESSIBLE 174 | case BUFFOON_CONSTANTS.ERRORS.CODES.FILE_WONT_OPEN: 175 | errDesc = BUFFOON_CONSTANTS.ERRORS.MESSAGES.FILE_WONT_OPEN 176 | case BUFFOON_CONSTANTS.ERRORS.CODES.BAD_TS_STRING: 177 | errDesc = BUFFOON_CONSTANTS.ERRORS.MESSAGES.BAD_TS_STRING 178 | case BUFFOON_CONSTANTS.ERRORS.CODES.BAD_MD_STRING: 179 | errDesc = BUFFOON_CONSTANTS.ERRORS.MESSAGES.BAD_MD_STRING 180 | case BUFFOON_CONSTANTS.ERRORS.CODES.BAD_GO_CONFIG: 181 | errDesc = BUFFOON_CONSTANTS.ERRORS.MESSAGES.BAD_GO_CONFIG 182 | default: 183 | errDesc = "UNKNOWN ERROR" 184 | } 185 | 186 | return NSError(domain: BUFFOON_CONSTANTS.APP_CODE_PREVIEWER, 187 | code: code, 188 | userInfo: [NSLocalizedDescriptionKey: errDesc]) 189 | } 190 | 191 | } 192 | -------------------------------------------------------------------------------- /Text Previewer/Text_Previewer.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.application-groups 8 | 9 | $(TeamIdentifierPrefix)suite.preview-previewtext 10 | 11 | com.apple.security.files.user-selected.read-only 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Text Thumbnailer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionAttributes 8 | 9 | QLSupportedContentTypes 10 | 11 | public.data 12 | public.content 13 | public.document 14 | public.plain-text 15 | com.apple.log 16 | com.bps.srt 17 | com.bps.sub 18 | com.bps.nfo 19 | com.bps.1st 20 | com.bps.rst 21 | com.bps.me 22 | com.bps.goconfig 23 | com.bps.io 24 | com.bps.toml 25 | com.bps.config 26 | cz.wz.zuggy.subrip 27 | com.bps.pubkey 28 | 29 | QLThumbnailMinimumDimension 30 | 0 31 | 32 | NSExtensionPointIdentifier 33 | com.apple.quicklook.thumbnail 34 | NSExtensionPrincipalClass 35 | $(PRODUCT_MODULE_NAME).ThumbnailProvider 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Text Thumbnailer/Text_Thumbnailer.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.application-groups 8 | 9 | $(TeamIdentifierPrefix)suite.preview-previewtext 10 | 11 | com.apple.security.files.user-selected.read-only 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Text Thumbnailer/ThumbnailProvider.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * ThumbnailProvider.swift 3 | * PreviewText 4 | * 5 | * Created by Tony Smith on 01/09/2023. 6 | * Copyright © 2025 Tony Smith. All rights reserved. 7 | */ 8 | 9 | 10 | import Foundation 11 | import AppKit 12 | import QuickLookThumbnailing 13 | 14 | 15 | class ThumbnailProvider: QLThumbnailProvider { 16 | 17 | // MARK:- Private Properties 18 | 19 | private enum ThumbnailerError: Error { 20 | case badFileLoad(String) 21 | case badFileUnreadable(String) 22 | case badFileUnsupportedEncoding(String) 23 | case badFileUnsupportedFile(String) 24 | case badRequestedIconSize(String) 25 | case badGfxBitmap 26 | case badGfxDraw 27 | } 28 | 29 | 30 | // MARK:- QLThumbnailProvider Required Functions 31 | 32 | override func provideThumbnail(for request: QLFileThumbnailRequest, _ handler: @escaping (QLThumbnailReply?, Error?) -> Void) { 33 | 34 | /* 35 | * This is the main entry point for macOS' thumbnailing system 36 | */ 37 | 38 | // FROM 1.0.5 39 | // Don't bother rendering if the required icon size is 40 | // too small to see. Set by the user, but 63 pixels or less by default 41 | let common: Common = Common.init(true) 42 | if Double(common.minimumTumbnailSize) > request.maximumSize.height { 43 | handler(nil, ThumbnailerError.badRequestedIconSize("ICON SIZE BELOW MINIMUM")) 44 | return 45 | } 46 | 47 | // Load the source file using a co-ordinator as we don't know what thread this function 48 | // will be executed in when it's called by macOS' QuickLook code 49 | if FileManager.default.isReadableFile(atPath: request.fileURL.path) { 50 | // Only proceed if the file is accessible from here 51 | do { 52 | // Get the file contents as a string, making sure it's not cached 53 | // as we're not going to read it again any time soon 54 | let data: Data = try Data.init(contentsOf: request.fileURL, options: [.uncached]) 55 | 56 | // Get the string's encoding, or fail back to .utf8 57 | let encoding: String.Encoding = data.stringEncoding ?? .utf8 58 | 59 | // Check the string's encoding generates a valid string 60 | // NOTE This may not be necessary and so may be removed 61 | guard let textFileString: String = String.init(data: data, encoding: encoding) else { 62 | handler(nil, ThumbnailerError.badFileLoad(request.fileURL.path)) 63 | return 64 | } 65 | 66 | // FROM 1.0.2 67 | // Get the UTI to check Go config files 68 | let sourceFileUTI: String = common.getSourceFileUTI(request.fileURL.path).lowercased() 69 | if sourceFileUTI == "com.bps.goconfig" { 70 | if !request.fileURL.lastPathComponent.starts(with: "go.") { 71 | handler(nil, ThumbnailerError.badFileUnsupportedFile("Not a Go config file")) 72 | return 73 | } 74 | } 75 | 76 | // Only render the lines likely to appear in the thumbnail: split into THUMBNAIL_LINE_COUNT substrings, assuming one per line. 77 | // This may be excessive if paragraphs span multiple lines, but we have to work to the minumum line-per-para count 78 | let paragraphs: [Substring] = textFileString.split(separator: "\n", maxSplits: BUFFOON_CONSTANTS.THUMBNAIL_LINE_COUNT, omittingEmptySubsequences: false) 79 | var displayLineCount: Int = 0 80 | var cutoff: Substring.Index = textFileString.endIndex 81 | for i in 0.. 1 { 88 | displayLineCount += (approxParagraphLineCount + 1) 89 | } else { 90 | displayLineCount += 1 91 | } 92 | 93 | if displayLineCount >= BUFFOON_CONSTANTS.THUMBNAIL_LINE_COUNT { 94 | cutoff = paragraphs[i].endIndex 95 | break 96 | } 97 | } 98 | 99 | // Set the primary drawing frame and a base font size 100 | let textFrame: CGRect = NSMakeRect(CGFloat(BUFFOON_CONSTANTS.THUMBNAIL_SIZE.ORIGIN_X), 101 | CGFloat(BUFFOON_CONSTANTS.THUMBNAIL_SIZE.ORIGIN_Y), 102 | CGFloat(BUFFOON_CONSTANTS.THUMBNAIL_SIZE.WIDTH), 103 | CGFloat(BUFFOON_CONSTANTS.THUMBNAIL_SIZE.HEIGHT)) 104 | 105 | // Instantiate an NSTextField to display the NSAttributedString render of the text 106 | let textTextField: NSTextField = NSTextField.init(frame: textFrame) 107 | let displayString = String(textFileString[textFileString.startIndex.. Bool in 132 | // `scaleFrame` and `cgImage` are immutable 133 | context.draw(image, in: scaleFrame, byTiling: false) 134 | return true 135 | }, nil) 136 | 137 | return 138 | } 139 | 140 | handler(nil, ThumbnailerError.badGfxDraw) 141 | return 142 | } catch { 143 | // NOP: fall through to error 144 | } 145 | } 146 | 147 | // We didn't draw anything because of 'can't find file' error 148 | handler(nil, ThumbnailerError.badFileUnreadable(request.fileURL.path)) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /qr-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smittytone/PreviewText/84e85477a43e6dc877431e851e6de73628395056/qr-code.png --------------------------------------------------------------------------------