├── .appcast.xml ├── .gitignore ├── README.md ├── assets ├── HelloSketch.framework │ ├── Headers │ ├── HelloSketch │ ├── HelloSketch.js │ ├── Modules │ ├── Resources │ └── Versions │ │ ├── A │ │ ├── Headers │ │ │ └── HelloSketch.h │ │ ├── HelloSketch │ │ ├── Modules │ │ │ └── module.modulemap │ │ └── Resources │ │ │ └── Info.plist │ │ └── Current └── icon.png ├── bettertypetool.sketchplugin └── Contents │ ├── Resources │ ├── HelloSketch.framework │ │ ├── Headers │ │ │ └── HelloSketch.h │ │ ├── HelloSketch │ │ ├── HelloSketch.js │ │ ├── Modules │ │ │ └── module.modulemap │ │ ├── Resources │ │ │ └── Info.plist │ │ └── Versions │ │ │ ├── A │ │ │ ├── Headers │ │ │ │ └── HelloSketch.h │ │ │ ├── HelloSketch │ │ │ ├── Modules │ │ │ │ └── module.modulemap │ │ │ └── Resources │ │ │ │ └── Info.plist │ │ │ └── Current │ │ │ ├── Headers │ │ │ └── HelloSketch.h │ │ │ ├── HelloSketch │ │ │ ├── Modules │ │ │ └── module.modulemap │ │ │ └── Resources │ │ │ └── Info.plist │ └── icon.png │ └── Sketch │ ├── manifest.json │ ├── my-command.js │ └── my-command.js.map ├── images ├── Icon.sketch ├── Images.sketch ├── banner.png └── banner.svg ├── package-lock.json ├── package.json └── src ├── manifest.json └── my-command.js /.appcast.xml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build artifacts 2 | plugin.sketchplugin 3 | 4 | # npm 5 | node_modules 6 | .npm 7 | npm-debug.log 8 | 9 | # mac 10 | .DS_Store 11 | 12 | # WebStorm 13 | .idea 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | BetterTypeTool - A Sketchplugin to make it easy to apply common OpenType Features 3 | 4 | 5 | ![Showing How the plugin works](https://media.giphy.com/media/1qWljmoJUq12MOny5L/giphy.gif) 6 | 7 | ## Supported OpenType Features 8 | - Number Position 9 | - Default Position 10 | - Superscript 11 | - Subscript 12 | - Ordinals 13 | - Scientific Notation 14 | - Number Spacing 15 | - Proportional 16 | - Monospaced/Tabular 17 | - Number Case 18 | - Lining Figures 19 | - Old-style Figures 20 | - Small Caps 21 | - Lower Case 22 | - Upper Case 23 | 24 | ## Inital Release Features 25 | - [X] When setting up the UI, set the state of the buttons properly the first time (i.e. check if there is a text object selected or not and then set the state rather than setting the default state of the UI then running `updateUI()`) 26 | - [X] Dark mode support 27 | - [X] Properly style small caps buttons 28 | - [X] Basic Support for applying properties to multiple selected Text Layers 29 | - [X] Better communicate when fonts have unspported properties 30 | - [X] Basic substring support 31 | 32 | ## Later Goals 33 | - [ ] Support Fractional Forms 34 | - [ ] Better Support Multiple layer selection 35 | - [ ] Resolve applying small caps to layers with different fonts 36 | - [ ] Respect Pixelfitting settings 37 | - [ ] More OpenType Features! (Ligatures, Stylistic Sets, etc) 38 | - [ ] Text Style support (I hope Sketch will consider tweaking things on their end rather me hacking it together) 39 | - [X] Better substring support 40 | - [ ] Keyboard shortcuts! 41 | 42 | ## Building The Plugin 43 | 44 | _This plugin was created using `skpm`. For a detailed explanation on how things work, checkout the [skpm Readme](https://github.com/skpm/skpm/blob/master/README.md)._ 45 | 46 | Install the dependencies 47 | 48 | ```bash 49 | npm install 50 | ``` 51 | 52 | Once the installation is done, you can run some commands inside the project folder: 53 | 54 | ```bash 55 | npm run build 56 | ``` 57 | 58 | To watch for changes: 59 | 60 | ```bash 61 | npm run watch 62 | ``` 63 | 64 | Additionally, if you wish to run the plugin every time it is built: 65 | 66 | ```bash 67 | npm run start 68 | ``` 69 | 70 | ## Custom Configuration 71 | 72 | ### Babel 73 | 74 | To customize Babel, you have two options: 75 | 76 | * You may create a [`.babelrc`](https://babeljs.io/docs/usage/babelrc) file in your project's root directory. Any settings you define here will overwrite matching config-keys within skpm preset. For example, if you pass a "presets" object, it will replace & reset all Babel presets that skpm defaults to. 77 | 78 | * If you'd like to modify or add to the existing Babel config, you must use a `webpack.skpm.config.js` file. Visit the [Webpack](#webpack) section for more info. 79 | 80 | ### Webpack 81 | 82 | To customize webpack create `webpack.skpm.config.js` file which exports function that will change webpack's config. 83 | 84 | ```js 85 | /** 86 | * Function that mutates original webpack config. 87 | * Supports asynchronous changes when promise is returned. 88 | * 89 | * @param {object} config - original webpack config. 90 | * @param {boolean} isPluginCommand - whether the config is for a plugin command or a resource 91 | **/ 92 | module.exports = function(config, isPluginCommand) { 93 | /** you can change config here **/ 94 | } 95 | ``` 96 | 97 | ## Debugging 98 | 99 | To view the output of your `console.log`, you have a few different options: 100 | 101 | * Use the [`sketch-dev-tools`](https://github.com/skpm/sketch-dev-tools) 102 | * Open `Console.app` and look for the sketch logs 103 | * Look at the `~/Library/Logs/com.bohemiancoding.sketch3/Plugin Output.log` file 104 | 105 | Skpm provides a convenient way to do the latter: 106 | 107 | ```bash 108 | skpm log 109 | ``` 110 | 111 | The `-f` option causes `skpm log` to not stop when the end of logs is reached, but rather to wait for additional data to be appended to the input 112 | 113 | ## Publishing your plugin 114 | 115 | ```bash 116 | skpm publish 117 | ``` 118 | 119 | (where `bump` can be `patch`, `minor` or `major`) 120 | 121 | `skpm publish` will create a new release on your GitHub repository and create an appcast file in order for Sketch users to be notified of the update. 122 | 123 | You will need to specify a `repository` in the `package.json`: 124 | 125 | ```diff 126 | ... 127 | + "repository" : { 128 | + "type": "git", 129 | + "url": "git+https://github.com/ORG/NAME.git" 130 | + } 131 | ... 132 | ``` 133 | -------------------------------------------------------------------------------- /assets/HelloSketch.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /assets/HelloSketch.framework/HelloSketch: -------------------------------------------------------------------------------- 1 | Versions/Current/HelloSketch -------------------------------------------------------------------------------- /assets/HelloSketch.framework/HelloSketch.js: -------------------------------------------------------------------------------- 1 | var HelloSketch_FrameworkPath = HelloSketch_FrameworkPath || COScript.currentCOScript().env().scriptURL.path().stringByDeletingLastPathComponent(); 2 | var HelloSketch_Log = HelloSketch_Log || log; 3 | (function() { 4 | var mocha = Mocha.sharedRuntime(); 5 | var frameworkName = "HelloSketch"; 6 | var directory = HelloSketch_FrameworkPath; 7 | if (mocha.valueForKey(frameworkName)) { 8 | HelloSketch_Log("😎 loadFramework: `" + frameworkName + "` already loaded."); 9 | return true; 10 | } else if ([mocha loadFrameworkWithName:frameworkName inDirectory:directory]) { 11 | HelloSketch_Log("✅ loadFramework: `" + frameworkName + "` success!"); 12 | mocha.setValue_forKey_(true, frameworkName); 13 | return true; 14 | } else { 15 | HelloSketch_Log("❌ loadFramework: `" + frameworkName + "` failed!: " + directory + ". Please define HelloSketch_FrameworkPath if you're trying to @import in a custom plugin"); 16 | return false; 17 | } 18 | })(); 19 | -------------------------------------------------------------------------------- /assets/HelloSketch.framework/Modules: -------------------------------------------------------------------------------- 1 | Versions/Current/Modules -------------------------------------------------------------------------------- /assets/HelloSketch.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /assets/HelloSketch.framework/Versions/A/Headers/HelloSketch.h: -------------------------------------------------------------------------------- 1 | // 2 | // HelloSketch.h 3 | // HelloSketch 4 | // 5 | // Created by Kevin Gutowski on 1/3/19. 6 | // Copyright © 2019 Kevin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for HelloSketch. 12 | FOUNDATION_EXPORT double HelloSketchVersionNumber; 13 | 14 | //! Project version string for HelloSketch. 15 | FOUNDATION_EXPORT const unsigned char HelloSketchVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /assets/HelloSketch.framework/Versions/A/HelloSketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KevinGutowski/betterTypePanel/51024af7f07877e50a2cd4ebf8d51061351371e1/assets/HelloSketch.framework/Versions/A/HelloSketch -------------------------------------------------------------------------------- /assets/HelloSketch.framework/Versions/A/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module HelloSketch { 2 | umbrella header "HelloSketch.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /assets/HelloSketch.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 18E226 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | HelloSketch 11 | CFBundleIdentifier 12 | com.kevingutowski.HelloSketch 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | HelloSketch 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSupportedPlatforms 22 | 23 | MacOSX 24 | 25 | CFBundleVersion 26 | 1 27 | DTCompiler 28 | com.apple.compilers.llvm.clang.1_0 29 | DTPlatformBuild 30 | 10E1001 31 | DTPlatformVersion 32 | GM 33 | DTSDKBuild 34 | 18E219 35 | DTSDKName 36 | macosx10.14 37 | DTXcode 38 | 1020 39 | DTXcodeBuild 40 | 10E1001 41 | NSHumanReadableCopyright 42 | Copyright © 2019 Kevin. All rights reserved. 43 | 44 | 45 | -------------------------------------------------------------------------------- /assets/HelloSketch.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KevinGutowski/betterTypePanel/51024af7f07877e50a2cd4ebf8d51061351371e1/assets/icon.png -------------------------------------------------------------------------------- /bettertypetool.sketchplugin/Contents/Resources/HelloSketch.framework/Headers/HelloSketch.h: -------------------------------------------------------------------------------- 1 | // 2 | // HelloSketch.h 3 | // HelloSketch 4 | // 5 | // Created by Kevin Gutowski on 1/3/19. 6 | // Copyright © 2019 Kevin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for HelloSketch. 12 | FOUNDATION_EXPORT double HelloSketchVersionNumber; 13 | 14 | //! Project version string for HelloSketch. 15 | FOUNDATION_EXPORT const unsigned char HelloSketchVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /bettertypetool.sketchplugin/Contents/Resources/HelloSketch.framework/HelloSketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KevinGutowski/betterTypePanel/51024af7f07877e50a2cd4ebf8d51061351371e1/bettertypetool.sketchplugin/Contents/Resources/HelloSketch.framework/HelloSketch -------------------------------------------------------------------------------- /bettertypetool.sketchplugin/Contents/Resources/HelloSketch.framework/HelloSketch.js: -------------------------------------------------------------------------------- 1 | var HelloSketch_FrameworkPath = HelloSketch_FrameworkPath || COScript.currentCOScript().env().scriptURL.path().stringByDeletingLastPathComponent(); 2 | var HelloSketch_Log = HelloSketch_Log || log; 3 | (function() { 4 | var mocha = Mocha.sharedRuntime(); 5 | var frameworkName = "HelloSketch"; 6 | var directory = HelloSketch_FrameworkPath; 7 | if (mocha.valueForKey(frameworkName)) { 8 | HelloSketch_Log("😎 loadFramework: `" + frameworkName + "` already loaded."); 9 | return true; 10 | } else if ([mocha loadFrameworkWithName:frameworkName inDirectory:directory]) { 11 | HelloSketch_Log("✅ loadFramework: `" + frameworkName + "` success!"); 12 | mocha.setValue_forKey_(true, frameworkName); 13 | return true; 14 | } else { 15 | HelloSketch_Log("❌ loadFramework: `" + frameworkName + "` failed!: " + directory + ". Please define HelloSketch_FrameworkPath if you're trying to @import in a custom plugin"); 16 | return false; 17 | } 18 | })(); 19 | -------------------------------------------------------------------------------- /bettertypetool.sketchplugin/Contents/Resources/HelloSketch.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module HelloSketch { 2 | umbrella header "HelloSketch.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /bettertypetool.sketchplugin/Contents/Resources/HelloSketch.framework/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 18E226 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | HelloSketch 11 | CFBundleIdentifier 12 | com.kevingutowski.HelloSketch 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | HelloSketch 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSupportedPlatforms 22 | 23 | MacOSX 24 | 25 | CFBundleVersion 26 | 1 27 | DTCompiler 28 | com.apple.compilers.llvm.clang.1_0 29 | DTPlatformBuild 30 | 10E1001 31 | DTPlatformVersion 32 | GM 33 | DTSDKBuild 34 | 18E219 35 | DTSDKName 36 | macosx10.14 37 | DTXcode 38 | 1020 39 | DTXcodeBuild 40 | 10E1001 41 | NSHumanReadableCopyright 42 | Copyright © 2019 Kevin. All rights reserved. 43 | 44 | 45 | -------------------------------------------------------------------------------- /bettertypetool.sketchplugin/Contents/Resources/HelloSketch.framework/Versions/A/Headers/HelloSketch.h: -------------------------------------------------------------------------------- 1 | // 2 | // HelloSketch.h 3 | // HelloSketch 4 | // 5 | // Created by Kevin Gutowski on 1/3/19. 6 | // Copyright © 2019 Kevin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for HelloSketch. 12 | FOUNDATION_EXPORT double HelloSketchVersionNumber; 13 | 14 | //! Project version string for HelloSketch. 15 | FOUNDATION_EXPORT const unsigned char HelloSketchVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /bettertypetool.sketchplugin/Contents/Resources/HelloSketch.framework/Versions/A/HelloSketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KevinGutowski/betterTypePanel/51024af7f07877e50a2cd4ebf8d51061351371e1/bettertypetool.sketchplugin/Contents/Resources/HelloSketch.framework/Versions/A/HelloSketch -------------------------------------------------------------------------------- /bettertypetool.sketchplugin/Contents/Resources/HelloSketch.framework/Versions/A/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module HelloSketch { 2 | umbrella header "HelloSketch.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /bettertypetool.sketchplugin/Contents/Resources/HelloSketch.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 18E226 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | HelloSketch 11 | CFBundleIdentifier 12 | com.kevingutowski.HelloSketch 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | HelloSketch 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSupportedPlatforms 22 | 23 | MacOSX 24 | 25 | CFBundleVersion 26 | 1 27 | DTCompiler 28 | com.apple.compilers.llvm.clang.1_0 29 | DTPlatformBuild 30 | 10E1001 31 | DTPlatformVersion 32 | GM 33 | DTSDKBuild 34 | 18E219 35 | DTSDKName 36 | macosx10.14 37 | DTXcode 38 | 1020 39 | DTXcodeBuild 40 | 10E1001 41 | NSHumanReadableCopyright 42 | Copyright © 2019 Kevin. All rights reserved. 43 | 44 | 45 | -------------------------------------------------------------------------------- /bettertypetool.sketchplugin/Contents/Resources/HelloSketch.framework/Versions/Current/Headers/HelloSketch.h: -------------------------------------------------------------------------------- 1 | // 2 | // HelloSketch.h 3 | // HelloSketch 4 | // 5 | // Created by Kevin Gutowski on 1/3/19. 6 | // Copyright © 2019 Kevin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for HelloSketch. 12 | FOUNDATION_EXPORT double HelloSketchVersionNumber; 13 | 14 | //! Project version string for HelloSketch. 15 | FOUNDATION_EXPORT const unsigned char HelloSketchVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /bettertypetool.sketchplugin/Contents/Resources/HelloSketch.framework/Versions/Current/HelloSketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KevinGutowski/betterTypePanel/51024af7f07877e50a2cd4ebf8d51061351371e1/bettertypetool.sketchplugin/Contents/Resources/HelloSketch.framework/Versions/Current/HelloSketch -------------------------------------------------------------------------------- /bettertypetool.sketchplugin/Contents/Resources/HelloSketch.framework/Versions/Current/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module HelloSketch { 2 | umbrella header "HelloSketch.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /bettertypetool.sketchplugin/Contents/Resources/HelloSketch.framework/Versions/Current/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 18E226 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | HelloSketch 11 | CFBundleIdentifier 12 | com.kevingutowski.HelloSketch 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | HelloSketch 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSupportedPlatforms 22 | 23 | MacOSX 24 | 25 | CFBundleVersion 26 | 1 27 | DTCompiler 28 | com.apple.compilers.llvm.clang.1_0 29 | DTPlatformBuild 30 | 10E1001 31 | DTPlatformVersion 32 | GM 33 | DTSDKBuild 34 | 18E219 35 | DTSDKName 36 | macosx10.14 37 | DTXcode 38 | 1020 39 | DTXcodeBuild 40 | 10E1001 41 | NSHumanReadableCopyright 42 | Copyright © 2019 Kevin. All rights reserved. 43 | 44 | 45 | -------------------------------------------------------------------------------- /bettertypetool.sketchplugin/Contents/Resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KevinGutowski/betterTypePanel/51024af7f07877e50a2cd4ebf8d51061351371e1/bettertypetool.sketchplugin/Contents/Resources/icon.png -------------------------------------------------------------------------------- /bettertypetool.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "compatibleVersion": 3, 3 | "bundleVersion": 1, 4 | "icon": "icon.png", 5 | "commands": [ 6 | { 7 | "name": "Toggle BetterTypePanel", 8 | "identifier": "my-command-identifier", 9 | "script": "my-command.js", 10 | "shortcut": "control cmd t" 11 | }, 12 | { 13 | "name": "selectionChanged", 14 | "identifier": "mySelectionChanged", 15 | "script": "my-command.js", 16 | "handlers": { 17 | "actions": { 18 | "SelectionChanged.begin": "selectionChanged", 19 | "TextChanged.finish": "textChanged" 20 | } 21 | } 22 | } 23 | ], 24 | "menu": { 25 | "title": "betterTypePanel", 26 | "items": [ 27 | "my-command-identifier" 28 | ], 29 | "isRoot": true 30 | }, 31 | "version": "0.3.3", 32 | "description": "A sketch plugin to help manage common OpenType properties", 33 | "name": "betterTypePanel", 34 | "identifier": "betterTypePanel", 35 | "disableCocoaScriptPreprocessor": true, 36 | "appcast": "https://raw.githubusercontent.com/KevinGutowski/betterTypePanel/master/.appcast.xml", 37 | "author": "Kevin Gutowski", 38 | "authorEmail": "kskiviolin@gmail.com" 39 | } -------------------------------------------------------------------------------- /bettertypetool.sketchplugin/Contents/Sketch/my-command.js: -------------------------------------------------------------------------------- 1 | var globalThis=this;function __skpm_run(t,e){globalThis.context=e;var i=function(t){var e={};function i(a){if(e[a])return e[a].exports;var n=e[a]={i:a,l:!1,exports:{}};return t[a].call(n.exports,n,n.exports,i),n.l=!0,n.exports}return i.m=t,i.c=e,i.d=function(t,e,a){i.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:a})},i.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var a=Object.create(null);if(i.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)i.d(a,n,function(e){return t[e]}.bind(null,n));return a},i.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return i.d(e,"a",e),e},i.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},i.p="",i(i.s=1)}([function(t,e){t.exports=require("sketch")},function(t,i,a){"use strict";a.r(i),a.d(i,"shutdown",function(){return g}),a.d(i,"selectionChanged",function(){return h}),a.d(i,"textChanged",function(){return C});var n=a(0),r=a.n(n);COScript.currentCOScript().setShouldKeepAround_(!0);var o="com.betterTypePanel",s="com.betterTypePanel.popupButton.verticalPosition",l="com.betterTypePanel.radioButton.proportional",S="com.betterTypePanel.radioButton.monospaced",u="com.betterTypePanel.button.lowerCase",c="com.betterTypePanel.button.upperCase",d="com.betterTypePanel.radioButton.liningFigures",f="com.betterTypePanel.radioButton.oldStyle",m="com.betterTypePanel.popupButton.sfSymbolSize",p="com.betterTypePanel.row.sfSymbolSize",N="com.betterTypePanel.vibrancy",b=312,y=210;function g(){try{T(),HSMain.alloc().init().stopObservingTextViewSelectionChanges()}catch(t){console.error(t)}}function h(t){framework("CoreText"),NSThread.mainThread().threadDictionary()[o]&&w()}function C(){if(framework("CoreText"),NSThread.mainThread().threadDictionary()[o]){w(!0)}}function T(){var t,i,a=e.scriptPath||COScript.currentCOScript().env().scriptURL.path(),n=a.stringByDeletingLastPathComponent().stringByDeletingLastPathComponent()+"/Resources",r=r||log;t=Mocha.sharedRuntime(),i=n,t.valueForKey("HelloSketch")||(t.loadFrameworkWithName_inDirectory("HelloSketch",i)?t.setValue_forKey_(!0,"HelloSketch"):r("❌ betterTypePanel loadFramework: `HelloSketch` failed!: "+i+". Please define HelloSketch_FrameworkPath if you're trying to @import in a custom plugin - scriptPath: "+a))}function k(t,e,i){t.setTranslatesAutoresizingMaskIntoConstraints(!1),v(NSLayoutAttributeTop,t,e,i[0]),v(NSLayoutAttributeTrailing,t,e,i[1]),v(NSLayoutAttributeBottom,t,e,i[2]),v(NSLayoutAttributeLeading,t,e,i[3])}function v(t,e,i,a){i.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant(e,t,NSLayoutRelationEqual,i,t,1,a))}function O(t){var e=t.text,i=t.frame,a=t.alignment,n=t.fontSize,r=void 0===n?13:n,o=NSTextField.alloc().initWithFrame(i);return o.setStringValue(e),o.setAlignment(a),o.setFont(NSFont.systemFontOfSize(r)),o.setBezeled(!1),o.setDrawsBackground(!1),o.setEditable(!1),o.setSelectable(!1),o}function A(t){var e=r.a.getSelectedDocument();e.selectedLayers.layers.filter(function(t){return"Text"==t.type}).forEach(function(e){var i=e.sketchObject.font(),a=i.pointSize(),n=(i.fontDescriptor().fontAttributes()[NSFontFeatureSettingsAttribute],i.fontDescriptor().fontDescriptorByAddingAttributes(t)),r=NSFont.fontWithDescriptor_size(n,a);if(e.sketchObject.setFont(r),1==e.sketchObject.isEditingText()){var o=e.sketchObject.editingDelegate().textView(),s=o.textStorage();x(e).forEach(function(e){var i=e.font,a=e.range,n=i.pointSize(),r=i.fontDescriptor().fontDescriptorByAddingAttributes(t),o=NSFont.fontWithDescriptor_size(r,n),l=NSDictionary.dictionaryWithObject_forKey(o,NSFontAttributeName);s.addAttributes_range(l,a)}),o.didChangeText()}}),e.sketchObject.inspectorController().reload()}function _(t,e){var i=NSFont.boldSystemFontOfSize(13),a=L(t,e),n=(i.fontDescriptor().fontAttributes()[NSFontFeatureSettingsAttribute],i.fontDescriptor().fontDescriptorByAddingAttributes(a));return NSFont.fontWithDescriptor_size(n,13)}function L(t,e){return{[NSFontFeatureSettingsAttribute]:[{[NSFontFeatureTypeIdentifierKey]:t,[NSFontFeatureSelectorIdentifierKey]:e}]}}function w(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0],e=r.a.getSelectedDocument().selectedLayers.layers,i=NSThread.mainThread().threadDictionary(),a=i[N];if(null==e)return W(i),void(a.isHidden()||(a.layer().setBackgroundFilters([]),a.setHidden(!0)));var n=e.filter(function(t){return"Text"==t.type});if(0==n.length)return W(i),void(a.isHidden()||(a.layer().setBackgroundFilters([]),a.setHidden(!0)));var b=[];n.forEach(function(e){x(e,t).forEach(function(t){var e,i,a,n,r,s,l,S,u;e=t.font,i=e.familyName(),a=!1,n=NSThread.mainThread().threadDictionary(),r=n[p],s=n[o],l=s.frame().origin.x,S=s.frame().origin.y,s.frame().size.height,u=s.frame().size.height,["SF Pro Text","SF Pro Rounded","SF Pro Display","SF Compact Text","SF Compact Rounded","SF Compact Display"].forEach(function(t){if(i==t)return a=!0,void(235!=u&&(s.setFrame_display_animate(NSMakeRect(l,S-25,312,235),!0,!0),r.setHidden(!1)))}),a||210!=u&&(r.setHidden(!0),s.setFrame_display_animate(NSMakeRect(l,S+25,312,210),!0,!0));var c=function(t){var e={verticalPosition:"default",numberSpacing:"proportional",numberCase:"lining",smallCapsLowerCase:!1,smallCapsUpperCase:!1,sfSymbolSize:"medium"};(function(t){framework("CoreText");var e=[];try{T();var i=HSMain.alloc().init(),a=CTFontCreateWithName(t.fontName(),t.pointSize(),null),n=CTFontCopyFeatures(a),r=i.bridgeArray(n);e=function(t){var e=[];if(t){var i=[];t.forEach(function(t){i.push(Number(t.CTFeatureTypeIdentifier))}),i.includes(10)||e.push("verticalPosition"),i.includes(6)||e.push("numberSpacing"),i.includes(21)||e.push("numberCase"),i.includes(37)||e.push("smallCapsLowerCase"),i.includes(38)||e.push("smallCapsUpperCase")}else e.push("verticalPosition","numberSpacing","numberCase","smallCapsLowerCase","smallCapsUpperCase");return e}(r)}catch(s){console.error(s)}var o=t.familyName().toLowerCase().trim();["sf pro text","sf pro rounded","sf pro display","sf compact text","sf compact rounded","sf compact display"].includes(o)||e.push("sfSymbolSize");return e})(t).forEach(function(t){switch(t){case"verticalPosition":e.verticalPosition="disabled";break;case"numberSpacing":e.numberSpacing="disabled";break;case"numberCase":e.numberCase="disabled";break;case"smallCapsLowerCase":e.smallCapsLowerCase="disabled";break;case"smallCapsUpperCase":e.smallCapsUpperCase="disabled";break;case"sfSymbolSize":e.sfSymbolSize="disabled"}});var i=t.fontDescriptor().fontAttributes()[NSFontFeatureSettingsAttribute];i&&i.forEach(function(t){var i=Number(t[NSFontFeatureTypeIdentifierKey]),a=Number(t[NSFontFeatureSelectorIdentifierKey]);switch(i){case 10:switch(a){case 0:e.verticalPosition="default";break;case 1:e.verticalPosition="superscript";break;case 2:e.verticalPosition="subscript";break;case 3:e.verticalPosition="ordinals";break;case 4:e.verticalPosition="scientific inferiors"}break;case 6:switch(a){case 0:e.numberSpacing="monospaced";break;case 1:e.numberSpacing="proportional"}break;case 21:switch(a){case 0:e.numberCase="oldStyle";break;case 1:e.numberCase="lining"}break;case 37:switch(a){case 0:e.smallCapsLowerCase=!1;break;case 1:e.smallCapsLowerCase=!0}break;case 38:switch(a){case 0:e.smallCapsUpperCase=!1;break;case 1:e.smallCapsUpperCase=!0}break;case 35:switch(a){case 30:e.sfSymbolSize="small";break;case 31:break;case 32:e.sfSymbolSize="large"}}});return e}(t.font);b.push(c)})});var y={smallCapsLowerCase:[],smallCapsUpperCase:[],sfSymbolSize:[],numberCase:[],verticalPosition:[],numberSpacing:[]};for(var g in b.forEach(function(t){Object.keys(t).forEach(function(e){y[e].push(t[e])})}),y)y[g]=y[g].filter(h);function h(t,e,i){return i.indexOf(t)===e}var C,k=!0;for(var v in y){if(y[v].length>1){k=!1;break}if("disabled"!==y[v][0]){k=!1;break}}for(var O in k&&a.isHidden()?(a.setHidden(!1),a.layer().setBackgroundFilters([(C=CIFilter.filterWithName("CIGaussianBlur"),C.setDefaults(),C.setValue_forKey(2,"inputRadius"),C.setName("blur"),C)])):k||a.isHidden()?k&&a.isHidden():(a.layer().setBackgroundFilters([]),a.setHidden(!0)),y)if("verticalPosition"==O)!function(){var t=i[s];t.setEnabled(!0),M(),y[O].length>1?(t.selectItemWithTitle("Multiple"),y[O].forEach(function(e){"default"==e?t.itemWithTitle("Default Position").setState(NSControlStateValueMixed):"superscript"==e?t.itemWithTitle("Superscript").setState(NSControlStateValueMixed):"subscript"==e?t.itemWithTitle("Subscript").setState(NSControlStateValueMixed):"ordinals"==e?t.itemWithTitle("Ordinals").setState(NSControlStateValueMixed):"scientific inferiors"==e&&t.itemWithTitle("Scientific Notation").setState(NSControlStateValueMixed)})):"default"==y[O][0]?(t.selectItemWithTitle("Default Position"),t.itemWithTitle("Default Position").setState(NSControlStateValueOn)):"superscript"==y[O][0]?(t.selectItemWithTitle("Superscript"),t.itemWithTitle("Superscript").setState(NSControlStateValueOn)):"subscript"==y[O][0]?(t.selectItemWithTitle("Subscript"),t.itemWithTitle("Subscript").setState(NSControlStateValueOn)):"ordinals"==y[O][0]?(t.selectItemWithTitle("Ordinals"),t.itemWithTitle("Ordinals").setState(NSControlStateValueOn)):"scientific inferiors"==y[O][0]?(t.selectItemWithTitle("Scientific Notation"),t.itemWithTitle("Scientific Notation").setState(NSControlStateValueOn)):"disabled"==y[O][0]&&t.setEnabled(!1)}();else if("numberSpacing"==O){var A=i[l],_=i[S];A.setEnabled(!0),_.setEnabled(!0),y[O].length>1?(A.setState(NSOffState),_.setState(NSOffState)):"proportional"==y[O][0]?(A.setState(NSOnState),_.setState(NSOffState)):"monospaced"==y[O][0]?(A.setState(NSOffState),_.setState(NSOnState)):"disabled"==y[O][0]&&(A.setState(NSOffState),_.setState(NSOffState),A.setEnabled(!1),_.setEnabled(!1))}else if("numberCase"==O){var L=i[d],w=i[f];L.setEnabled(!0),w.setEnabled(!0),y[O].length>1?(L.setState(NSOffState),w.setState(NSOffState)):"lining"==y[O][0]?(L.setState(NSOnState),w.setState(NSOffState)):"oldStyle"==y[O][0]?(L.setState(NSOffState),w.setState(NSOnState)):"disabled"==y[O][0]&&(L.setState(NSOffState),w.setState(NSOffState),L.setEnabled(!1),w.setEnabled(!1))}else if("smallCapsUpperCase"==O){var F=i[c];F.setEnabled(!0),y[O].length>1?F.setState(NSOffState):0==y[O][0]?F.setState(NSOffState):1==y[O][0]?F.setState(NSOnState):"disabled"==y[O][0]&&(F.setState(NSOffState),F.setEnabled(!1))}else if("smallCapsLowerCase"==O){var R=i[u];R.setEnabled(!0),y[O].length>1?R.setState(NSOffState):0==y[O][0]?R.setState(NSOffState):1==y[O][0]?R.setState(NSOnState):"disabled"==y[O][0]&&(R.setState(NSOffState),R.setEnabled(!1))}else"sfSymbolSize"==O?function(){var t=i[m];t.setEnabled(!0),V(),y[O].length>1?(t.selectItemWithTitle("Multiple"),y[O].forEach(function(e){"small"==e?t.itemWithTitle("Small").setState(NSControlStateValueMixed):"medium"==e?t.itemWithTitle("Medium").setState(NSControlStateValueMixed):"large"==e&&t.itemWithTitle("Large").setState(NSControlStateValueMixed)})):"small"==y[O][0]?(t.selectItemWithTitle("Small"),t.itemWithTitle("Small").setState(NSControlStateValueOn)):"medium"==y[O][0]?(t.selectItemWithTitle("Medium"),t.itemWithTitle("Medium").setState(NSControlStateValueOn)):"large"==y[O][0]?(t.selectItemWithTitle("Large"),t.itemWithTitle("Large").setState(NSControlStateValueOn)):"disabled"==y[O][0]&&t.setEnabled(!1)}():y[O]}function W(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:["all"];(e.includes("all")||e.includes("verticalPosition"))&&t[s].setEnabled(!1);if(e.includes("all")||e.includes("numberSpacing")){var i=t[l],a=t[S];i.setEnabled(!1),a.setEnabled(!1)}if(e.includes("all")||e.includes("numberCase")){var n=t[d],r=t[f];n.setEnabled(!1),r.setEnabled(!1)}(e.includes("all")||e.includes("smallCapsUpperCase"))&&t[c].setEnabled(!1);(e.includes("all")||e.includes("smallCapsLowerCase"))&&t[u].setEnabled(!1);(e.includes("all")||e.includes("sfSymbolSize"))&&t[m].setEnabled(!1)}function F(t,e,i){t.close();try{T(),HSMain.alloc().init().stopObservingTextViewSelectionChanges()}catch(a){console.error(a)}e.removeObjectForKey(i),e.panelOpen=!1,COScript.currentCOScript().setShouldKeepAround_(!1)}function M(){var t=NSThread.mainThread().threadDictionary()[s];t.itemWithTitle("Default Position").setState(NSControlStateValueOff),t.itemWithTitle("Superscript").setState(NSControlStateValueOff),t.itemWithTitle("Subscript").setState(NSControlStateValueOff),t.itemWithTitle("Ordinals").setState(NSControlStateValueOff),t.itemWithTitle("Scientific Notation").setState(NSControlStateValueOff)}function V(){var t=NSThread.mainThread().threadDictionary()[m];t.itemWithTitle("Small").setState(NSControlStateValueOff),t.itemWithTitle("Medium").setState(NSControlStateValueOff),t.itemWithTitle("Large").setState(NSControlStateValueOff)}function x(t){var e,i,a,n=arguments.length>1&&void 0!==arguments[1]&&arguments[1],r=t.sketchObject,o=MOPointer.alloc().init(),s=!0;try{a=r.editingDelegate().textView()}catch(f){s=!1}if(s){var l=a.textStorage();i=a.selectedRange(),e=l,n&&(i=NSMakeRange(0,t.text.length))}else{var S=r.attributedStringValue();i=NSMakeRange(0,t.text.length),e=S}var u=[];if(0==i.length){var c=e.attribute_atIndex_longestEffectiveRange_inRange(NSFontAttributeName,i.location,o,i);u.push({font:c,range:o.value()})}for(;i.length>0;){var d=e.attribute_atIndex_longestEffectiveRange_inRange(NSFontAttributeName,i.location,o,i);i=NSMakeRange(NSMaxRange(o.value()),NSMaxRange(i)-NSMaxRange(o.value())),u.push({font:d,range:o.value()})}return u}i.default=function(){var t;(t=NSThread.mainThread().threadDictionary())[o]?F(t[o],t,o):(t.panelOpen=!0,function(t,e){var i=NSPanel.alloc().init();i.setFrame_display(NSMakeRect(0,0,b,y),!0),i.setStyleMask(NSTexturedBackgroundWindowMask|NSTitledWindowMask|NSClosableWindowMask),i.title="BetterTypePanel",i.center(),i.makeKeyAndOrderFront(null),i.setLevel(NSFloatingWindowLevel),i.standardWindowButton(NSWindowMiniaturizeButton).setHidden(!0),i.standardWindowButton(NSWindowZoomButton).setHidden(!0),t[e]=i;var a=O({text:"Number Position:",frame:NSMakeRect(0,0,109,17),alignment:NSTextAlignmentRight});a.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant(a,NSLayoutAttributeWidth,NSLayoutRelationEqual,nil,NSLayoutAttributeNotAnAttribute,1,109));var n=NSPopUpButton.alloc().initWithFrame(NSMakeRect(0,0,150,25));n.addItemsWithTitles(["Default Position","Superscript","Subscript","Ordinals","Scientific Notation","Multiple"]),t[s]=n,n.itemWithTitle("Multiple").setHidden(!0),n.setCOSJSTargetFunction(function(t){return function(t){if("Superscript"==t.title()){var e=L(kVerticalPositionType,kSuperiorsSelector);A(e)}else if("Subscript"==t.title()){var i=L(kVerticalPositionType,kInferiorsSelector);A(i)}else if("Ordinals"==t.title()){var a=L(kVerticalPositionType,kOrdinalsSelector);A(a)}else if("Scientific Notation"==t.title()){var n=L(kVerticalPositionType,kScientificInferiorsSelector);A(n)}else{var r=L(kVerticalPositionType,kNormalPositionSelector);A(r)}}(t)});var r=NSStackView.alloc().initWithFrame(NSMakeRect(0,0,284,25));r.setOrientation(NSUserInterfaceLayoutOrientationHorizontal),r.setAlignment(NSLayoutAttributeFirstBaseline),r.setSpacing(4),r.setViews_inGravity([a,n],NSStackViewGravityLeading);var o=O({text:"Number Spacing:",frame:NSMakeRect(0,0,109,17),alignment:NSTextAlignmentRight});o.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant(o,NSLayoutAttributeWidth,NSLayoutRelationEqual,nil,NSLayoutAttributeNotAnAttribute,1,109));var g=NSButton.alloc().initWithFrame(NSMakeRect(0,0,97,18));g.setButtonType(NSRadioButton),g.setTitle("Proportional"),g.setState(NSOnState),t[l]=g;var h=NSButton.alloc().initWithFrame(NSMakeRect(0,0,150,18));h.setButtonType(NSRadioButton),h.setTitle("Monospaced/Tabular"),h.setState(NSOffState),t[S]=h;var C=function(t){if("Proportional"==t.title()){var e=L(kNumberSpacingType,kProportionalNumbersSelector);A(e)}else{var i=L(kNumberSpacingType,kMonospacedNumbersSelector);A(i)}};g.setCOSJSTargetFunction(function(t){return C(t)}),h.setCOSJSTargetFunction(function(t){return C(t)});var T=NSStackView.stackViewWithViews([g,h]);T.setOrientation(NSUserInterfaceLayoutOrientationVertical),T.setAlignment(NSLayoutAttributeLeading),T.setSpacing(4),T.setTranslatesAutoresizingMaskIntoConstraints(!1);var w=NSStackView.alloc().initWithFrame(NSMakeRect(0,0,284,36));w.setOrientation(NSUserInterfaceLayoutOrientationHorizontal),w.setAlignment(NSLayoutAttributeFirstBaseline),w.setSpacing(4),w.setViews_inGravity([o,T],NSStackViewGravityLeading);var W=O({text:"Number Case:",frame:NSMakeRect(0,0,109,17),alignment:NSTextAlignmentRight});W.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant(W,NSLayoutAttributeWidth,NSLayoutRelationEqual,nil,NSLayoutAttributeNotAnAttribute,1,109));var M=NSButton.alloc().initWithFrame(NSMakeRect(0,0,104,17));M.setButtonType(NSRadioButton),M.setTitle("Lining figures"),M.setState(NSOnState),t[d]=M;var V=NSButton.alloc().initWithFrame(NSMakeRect(0,0,124,18));V.setButtonType(NSRadioButton),V.setTitle("Old-style figures"),V.setState(NSOffState),t[f]=V;var x=function(t){if("Old-style figures"==t.title()){var e=L(kNumberCaseType,kLowerCaseNumbersSelector);A(e)}else{var i=L(kNumberCaseType,kUpperCaseNumbersSelector);A(i)}};M.setCOSJSTargetFunction(function(t){return x(t)}),V.setCOSJSTargetFunction(function(t){return x(t)});var R=NSStackView.stackViewWithViews([M,V]);R.setOrientation(NSUserInterfaceLayoutOrientationVertical),R.setAlignment(NSLayoutAttributeLeading),R.setSpacing(4),R.setTranslatesAutoresizingMaskIntoConstraints(!1);var B=NSStackView.alloc().initWithFrame(NSMakeRect(0,0,284,36));B.setOrientation(NSUserInterfaceLayoutOrientationHorizontal),B.setAlignment(NSLayoutAttributeFirstBaseline),B.setSpacing(4),B.setViews_inGravity([W,R],NSStackViewGravityLeading);var P=O({text:"Small Caps:",frame:NSMakeRect(0,0,109,17),alignment:NSTextAlignmentRight});P.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant(P,NSLayoutAttributeWidth,NSLayoutRelationEqual,nil,NSLayoutAttributeNotAnAttribute,1,109));var I=O({text:"Tt →",frame:NSMakeRect(0,0,32,17),alignment:NSTextAlignmentLeft}),E=NSButton.alloc().initWithFrame(NSMakeRect(0,0,72,32));E.setButtonType(NSButtonTypeOnOff),E.setBezelStyle(NSRoundedBezelStyle);var z=NSMutableAttributedString.new().initWithString("Tt"),D=NSMakeRange(1,1),H=_(37,1);z.setAlignment_range(NSTextAlignmentCenter,NSMakeRange(0,2)),z.addAttribute_value_range(NSFontAttributeName,H,D),E.setAttributedTitle(z),E.setState(NSOffState),E.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant(E,NSLayoutAttributeWidth,NSLayoutRelationEqual,nil,NSLayoutAttributeNotAnAttribute,1,60)),t[u]=E,E.setCOSJSTargetFunction(function(t){return function(t){if(0==t.state()){var e=L(kLowerCaseType,kDefaultLowerCaseSelector);A(e)}else{var i=L(kLowerCaseType,kLowerCaseSmallCapsSelector);A(i)}}(t)});var U=O({text:"Lower Case",frame:NSMakeRect(0,0,65,14),alignment:NSTextAlignmentCenter,fontSize:11}),j=NSStackView.stackViewWithViews([E,U]);j.setOrientation(NSUserInterfaceLayoutOrientationVertical),j.setAlignment(NSLayoutAttributeCenterX),j.setSpacing(4),j.setTranslatesAutoresizingMaskIntoConstraints(!1);var K=NSButton.alloc().initWithFrame(NSMakeRect(0,0,72,32));K.setButtonType(NSButtonTypeOnOff),K.setBezelStyle(NSRoundedBezelStyle);var G=NSMutableAttributedString.new().initWithString("Tt"),q=NSMakeRange(0,1),J=_(38,1);G.setAlignment_range(NSTextAlignmentCenter,NSMakeRange(0,2)),NSMutableParagraphStyle.new().init().setAlignment(NSTextAlignmentCenter),G.addAttribute_value_range(NSFontAttributeName,J,q),K.setAttributedTitle(G),K.setState(NSOffState),K.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant(K,NSLayoutAttributeWidth,NSLayoutRelationEqual,nil,NSLayoutAttributeNotAnAttribute,1,60)),t[c]=K,K.setCOSJSTargetFunction(function(t){return function(t){if(0==t.state()){var e=L(kUpperCaseType,kDefaultUpperCaseSelector);A(e)}else{var i=L(kUpperCaseType,kUpperCaseSmallCapsSelector);A(i)}}(t)});var X=O({text:"Upper Case",frame:NSMakeRect(0,0,66,14),alignment:NSTextAlignmentCenter,fontSize:11}),Z=NSStackView.stackViewWithViews([K,X]);Z.setOrientation(NSUserInterfaceLayoutOrientationVertical),Z.setSpacing(4),Z.setTranslatesAutoresizingMaskIntoConstraints(!1);var Q=NSStackView.stackViewWithViews([I,j,Z]);Q.setOrientation(NSUserInterfaceLayoutOrientationHorizontal),Q.setAlignment(NSLayoutAttributeFirstBaseline),Q.setSpacing(4),Q.setTranslatesAutoresizingMaskIntoConstraints(!1);var Y=NSStackView.alloc().initWithFrame(NSMakeRect(0,0,284,36));Y.setOrientation(NSUserInterfaceLayoutOrientationHorizontal),Y.setAlignment(NSLayoutAttributeFirstBaseline),Y.setSpacing(4),Y.setViews_inGravity([P,Q],NSStackViewGravityLeading);var $=O({text:"SF Symbol Size:",frame:NSMakeRect(0,0,109,17),alignment:NSTextAlignmentRight});$.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant($,NSLayoutAttributeWidth,NSLayoutRelationEqual,nil,NSLayoutAttributeNotAnAttribute,1,109));var tt=NSPopUpButton.alloc().initWithFrame(NSMakeRect(0,0,150,25));tt.addItemsWithTitles(["Small","Medium","Large","Multiple"]),t[m]=tt,tt.itemWithTitle("Multiple").setHidden(!0),tt.setCOSJSTargetFunction(function(t){return function(t){if("Small"==t.title()){var e=L(kStylisticAlternativesType,kStylisticAltFifteenOnSelector),i=L(kStylisticAlternativesType,kStylisticAltSixteenOffSelector),a=[e,i];a.forEach(function(t){A(t)})}else if("Medium"==t.title()){var n=L(kStylisticAlternativesType,kStylisticAltFifteenOffSelector),r=L(kStylisticAlternativesType,kStylisticAltSixteenOffSelector),o=[n,r];o.forEach(function(t){A(t)})}else if("Large"==t.title()){var s=L(kStylisticAlternativesType,kStylisticAltFifteenOffSelector),l=L(kStylisticAlternativesType,kStylisticAltSixteenOnSelector),S=[s,l];S.forEach(function(t){A(t)})}}(t)});var et=NSStackView.alloc().initWithFrame(NSMakeRect(0,0,284,25));et.setOrientation(NSUserInterfaceLayoutOrientationHorizontal),et.setAlignment(NSLayoutAttributeFirstBaseline),et.setSpacing(4),et.setViews_inGravity([$,tt],NSStackViewGravityLeading),t[p]=et,et.setHidden(!0);var it=NSStackView.stackViewWithViews([r,w,B,Y,et]);it.setOrientation(NSUserInterfaceLayoutOrientationVertical),it.setAlignment(NSLayoutAttributeLeading),it.setSpacing(8),it.setTranslatesAutoresizingMaskIntoConstraints(!1),i.contentView().addSubview(it),i.contentView().setFlipped(!0),k(it,i.contentView(),[16,16,8,16]),function(t){var e=NSView.alloc().initWithFrame(NSMakeRect(0,0,b,y));e.setBackgroundColor(NSColor.colorWithRed_green_blue_alpha(0,0,0,.6)),e.setWantsLayer(!0),t.addSubview(e),k(e,t,[0,0,0,0]);var i=O({text:"Selected text doesn't contain any supported font features. Please try a different typeface.",frame:NSMakeRect(0,0,b,17),alignment:NSTextAlignmentCenter,fontSize:12});i.setTextColor(NSColor.whiteColor()),e.addSubview(i),i.setTranslatesAutoresizingMaskIntoConstraints(!1),v(NSLayoutAttributeCenterX,i,e,0),v(NSLayoutAttributeTop,i,e,40),i.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant(i,NSLayoutAttributeWidth,NSLayoutRelationEqual,nil,NSLayoutAttributeNotAnAttribute,1,260)),e.setHidden(!0),NSThread.mainThread().threadDictionary()[N]=e}(i.contentView()),t[e]=i,i.standardWindowButton(NSWindowCloseButton).setCOSJSTargetFunction(function(a){F(i,t,e)})}(t,o)),T(),framework("CoreText");var e=HSMain.alloc().init();e.beginObservingTextViewSelectionChanges(),e.setCallbackForTextViewSelectionChange(function(){w()}),w()}}]);"default"===t&&"function"==typeof i?i(e):i[t](e)}globalThis.onRun=__skpm_run.bind(this,"default"),globalThis.selectionChanged=__skpm_run.bind(this,"selectionChanged"),globalThis.textChanged=__skpm_run.bind(this,"textChanged"); -------------------------------------------------------------------------------- /images/Icon.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KevinGutowski/betterTypePanel/51024af7f07877e50a2cd4ebf8d51061351371e1/images/Icon.sketch -------------------------------------------------------------------------------- /images/Images.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KevinGutowski/betterTypePanel/51024af7f07877e50a2cd4ebf8d51061351371e1/images/Images.sketch -------------------------------------------------------------------------------- /images/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KevinGutowski/betterTypePanel/51024af7f07877e50a2cd4ebf8d51061351371e1/images/banner.png -------------------------------------------------------------------------------- /images/banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard Copy 3 5 | Created with Sketch Beta. 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 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | H2O 64 | 65 | 66 | 1st 67 | 68 | 69 | x2 70 | 71 | 72 | 73 | NUMBER POSITION 74 | 75 | 76 | 77 | 78 | 79 | 12345 80 | 81 | 82 | 12345 83 | 84 | 85 | 86 | NUMBER CASE 87 | 88 | 89 | 90 | 91 | SMALL CAPS 92 | 93 | 94 | Lower 95 | 96 | 97 | Upper 98 | 99 | 100 | & 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | BETA 111 | 112 | 113 | 114 | 115 | 116 | NUMBER SPACING 117 | 118 | 119 | 120 | 12345 121 | 122 | 123 | 12345 124 | 125 | 126 | 127 | 128 | BetterTypePanel 129 | 130 | 131 | Quick access to common 132 | OpenType features 133 | 134 | 135 | 136 | 137 | Works with 138 | Sketch 51+ 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | Download Plugin 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bettertypepanel", 3 | "version": "0.3.3", 4 | "engines": { 5 | "sketch": ">=3.0" 6 | }, 7 | "skpm": { 8 | "name": "betterTypePanel", 9 | "manifest": "src/manifest.json", 10 | "main": "bettertypetool.sketchplugin", 11 | "assets": [ 12 | "assets/**/*" 13 | ] 14 | }, 15 | "scripts": { 16 | "build": "skpm-build", 17 | "watch": "skpm-build --watch", 18 | "start": "skpm-build --watch --run", 19 | "postinstall": "npm run build && skpm-link" 20 | }, 21 | "devDependencies": { 22 | "@skpm/builder": "^0.7.1" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/KevinGutowski/betterTypePanel" 27 | }, 28 | "author": "Kevin Gutowski ", 29 | "description": "A sketch plugin to help manage common OpenType properties" 30 | } 31 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "compatibleVersion": 3, 3 | "bundleVersion": 1, 4 | "icon": "icon.png", 5 | "commands": [ 6 | { 7 | "name": "Toggle BetterTypePanel", 8 | "identifier": "my-command-identifier", 9 | "script": "./my-command.js", 10 | "shortcut": "control cmd t" 11 | }, 12 | { 13 | "name": "selectionChanged", 14 | "identifier": "mySelectionChanged", 15 | "script": "./my-command.js", 16 | "handlers": { 17 | "actions": { 18 | "SelectionChanged.begin" : "selectionChanged", 19 | "TextChanged.finish": "textChanged" 20 | } 21 | } 22 | } 23 | ], 24 | "menu": { 25 | "title": "betterTypePanel", 26 | "items": [ 27 | "my-command-identifier" 28 | ], 29 | "isRoot": true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/my-command.js: -------------------------------------------------------------------------------- 1 | import sketch from 'sketch' 2 | // Documentation: 3 | // https://developer.sketchapp.com/reference/api/ 4 | // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM09/AppendixF.html 5 | 6 | COScript.currentCOScript().setShouldKeepAround_(true) 7 | 8 | let threadIdentifier = "com.betterTypePanel" 9 | let verticalPositionPopupButtonID = "com.betterTypePanel.popupButton.verticalPosition" 10 | let radioButtonProportionalID = "com.betterTypePanel.radioButton.proportional" 11 | let radioButtonMonospacedOrTabularID = "com.betterTypePanel.radioButton.monospaced" 12 | let pushOnOffButtonLowerCaseID = "com.betterTypePanel.button.lowerCase" 13 | let pushOnOffButtonUpperCaseID = "com.betterTypePanel.button.upperCase" 14 | let radioButtonLiningFiguresID = "com.betterTypePanel.radioButton.liningFigures" 15 | let radioButtonOldStyleFiguresID = "com.betterTypePanel.radioButton.oldStyle" 16 | let sfSymbolSizePopupButtonID = "com.betterTypePanel.popupButton.sfSymbolSize" 17 | let sfSymbolSizeRow = "com.betterTypePanel.row.sfSymbolSize" 18 | let vibrancyViewID = "com.betterTypePanel.vibrancy" 19 | var panelWidth = 312 20 | var panelHeight = 210 21 | 22 | export default function() { 23 | runPanel() 24 | 25 | setupFramework() 26 | framework("CoreText"); 27 | 28 | let main = HSMain.alloc().init() 29 | main.beginObservingTextViewSelectionChanges() 30 | main.setCallbackForTextViewSelectionChange(() => { 31 | updateUI() 32 | }) 33 | 34 | updateUI() 35 | } 36 | 37 | export function shutdown() { 38 | try { 39 | setupFramework() 40 | let main = HSMain.alloc().init() 41 | main.stopObservingTextViewSelectionChanges() 42 | } catch (error) { 43 | console.error(error) 44 | } 45 | } 46 | 47 | export function selectionChanged(context) { 48 | framework("CoreText"); 49 | let threadDictionary = NSThread.mainThread().threadDictionary() 50 | // check if the panel is open, if open update UI, else just do nothing 51 | if (threadDictionary[threadIdentifier]) { 52 | updateUI() 53 | } else { 54 | return 55 | } 56 | } 57 | 58 | export function textChanged() { 59 | framework("CoreText"); 60 | let threadDictionary = NSThread.mainThread().threadDictionary() 61 | // check if the panel is open, if open update UI, else just do nothing 62 | if (threadDictionary[threadIdentifier]) { 63 | let useFullSelection = true 64 | updateUI(useFullSelection) 65 | } else { 66 | return 67 | } 68 | } 69 | 70 | function setupFramework() { 71 | var scriptPath = context.scriptPath || COScript.currentCOScript().env().scriptURL.path() 72 | var HelloSketch_FrameworkPath = scriptPath.stringByDeletingLastPathComponent().stringByDeletingLastPathComponent() + "/Resources" 73 | var HelloSketch_Log = HelloSketch_Log || log; 74 | (function() { 75 | var mocha = Mocha.sharedRuntime(); 76 | var frameworkName = "HelloSketch"; 77 | var directory = HelloSketch_FrameworkPath; 78 | if (mocha.valueForKey(frameworkName)) { 79 | //HelloSketch_Log("😎 betterTypePanel loadFramework: `" + frameworkName + "` already loaded."); 80 | return true; 81 | } else if (mocha.loadFrameworkWithName_inDirectory(frameworkName, directory)) { 82 | //HelloSketch_Log("✅ betterTypePanel loadFramework: `" + frameworkName + "` success!"); 83 | mocha.setValue_forKey_(true, frameworkName); 84 | return true; 85 | } else { 86 | HelloSketch_Log("❌ betterTypePanel loadFramework: `" + frameworkName + "` failed!: " + directory + ". Please define HelloSketch_FrameworkPath if you're trying to @import in a custom plugin - scriptPath: " + scriptPath); 87 | return false; 88 | } 89 | })(); 90 | } 91 | 92 | function runPanel() { 93 | let threadDictionary = NSThread.mainThread().threadDictionary() 94 | 95 | // If there is already a panel, close it 96 | if (threadDictionary[threadIdentifier]) { 97 | closePanel(threadDictionary[threadIdentifier], threadDictionary, threadIdentifier) 98 | } else { 99 | threadDictionary.panelOpen = true 100 | setupPanel(threadDictionary, threadIdentifier) 101 | } 102 | } 103 | 104 | function setupPanel(threadDictionary, identifier) { 105 | let panel = NSPanel.alloc().init() 106 | panel.setFrame_display(NSMakeRect(0, 0, panelWidth, panelHeight), true) 107 | panel.setStyleMask(NSTexturedBackgroundWindowMask | NSTitledWindowMask | NSClosableWindowMask) 108 | panel.title = "BetterTypePanel" 109 | 110 | panel.center() 111 | panel.makeKeyAndOrderFront(null) 112 | panel.setLevel(NSFloatingWindowLevel) 113 | 114 | panel.standardWindowButton(NSWindowMiniaturizeButton).setHidden(true) 115 | panel.standardWindowButton(NSWindowZoomButton).setHidden(true) 116 | 117 | threadDictionary[identifier] = panel 118 | 119 | const column1width = 109 120 | const column2width = 171 121 | const columnSpacing = 4 122 | const rowSpacing = 8 123 | const mainViewWidth = column1width + column2width + columnSpacing 124 | 125 | // MARK: SETUP ROW 1 126 | var verticalPositionLabel = createTextField({ 127 | text: "Number Position:", 128 | frame: NSMakeRect(0,0,column1width,17), 129 | alignment: NSTextAlignmentRight 130 | }) 131 | 132 | verticalPositionLabel.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant( 133 | verticalPositionLabel, 134 | NSLayoutAttributeWidth, 135 | NSLayoutRelationEqual, 136 | nil, 137 | NSLayoutAttributeNotAnAttribute, 138 | 1.0, 139 | column1width 140 | )) 141 | 142 | var verticalPositionPopupButton = NSPopUpButton.alloc().initWithFrame(NSMakeRect(0,0,150,25)) 143 | verticalPositionPopupButton.addItemsWithTitles([ 144 | 'Default Position', 145 | 'Superscript', 146 | 'Subscript', 147 | 'Ordinals', 148 | 'Scientific Notation', 149 | 'Multiple' 150 | ]) 151 | 152 | threadDictionary[verticalPositionPopupButtonID] = verticalPositionPopupButton 153 | verticalPositionPopupButton.itemWithTitle('Multiple').setHidden(true) 154 | 155 | let verticalPositionTargetFunction = (sender) => { 156 | // console.log(sender.title() + ' dropdown button was selected') 157 | // Vertical Position 158 | // ID: kVerticalPositionType 159 | // 160 | // Selectors 161 | // 162 | // kNormalPositionSelector 163 | // This is the default. It means to display the text with no vertical displacement. 164 | // 165 | // kSuperiorsSelector 166 | // Changes any characters having superior forms in the font into those forms. 167 | // 168 | // kInferiorsSelector 169 | // Changes any characters having inferior forms in the font into those forms. 170 | // 171 | // kOrdinalsSelector 172 | // Contextually changes certain letters into their superior forms, like in Spanish changing from 1a to 1ª. 173 | // 174 | // kScientificInferiorsSelector 175 | // Changes any characters having them into inferior forms designed for a technical context (as in H2O). 176 | // 177 | if (sender.title() == 'Superscript') { 178 | let settingsAttribute = getSettingsAttributeForKey_Value(kVerticalPositionType, kSuperiorsSelector) 179 | updateFontFeatureSettingsAttribute(settingsAttribute) 180 | } else if (sender.title() == 'Subscript') { 181 | let settingsAttribute = getSettingsAttributeForKey_Value(kVerticalPositionType, kInferiorsSelector) 182 | updateFontFeatureSettingsAttribute(settingsAttribute) 183 | } else if (sender.title() == 'Ordinals') { 184 | let settingsAttribute = getSettingsAttributeForKey_Value(kVerticalPositionType, kOrdinalsSelector) 185 | updateFontFeatureSettingsAttribute(settingsAttribute) 186 | } else if (sender.title() == 'Scientific Notation') { 187 | let settingsAttribute = getSettingsAttributeForKey_Value(kVerticalPositionType, kScientificInferiorsSelector) 188 | updateFontFeatureSettingsAttribute(settingsAttribute) 189 | } else { 190 | let settingsAttribute = getSettingsAttributeForKey_Value(kVerticalPositionType, kNormalPositionSelector) 191 | updateFontFeatureSettingsAttribute(settingsAttribute) 192 | } 193 | } 194 | 195 | verticalPositionPopupButton.setCOSJSTargetFunction(sender => verticalPositionTargetFunction(sender)) 196 | 197 | var row1 = NSStackView.alloc().initWithFrame(NSMakeRect(0,0,mainViewWidth,25)) 198 | row1.setOrientation(NSUserInterfaceLayoutOrientationHorizontal) 199 | row1.setAlignment(NSLayoutAttributeFirstBaseline) 200 | row1.setSpacing(columnSpacing) 201 | row1.setViews_inGravity([verticalPositionLabel,verticalPositionPopupButton],NSStackViewGravityLeading) 202 | 203 | // MARK: Setup Row 2 204 | var numberSpacingLabel = createTextField({ 205 | text: "Number Spacing:", 206 | frame: NSMakeRect(0,0,column1width,17), 207 | alignment: NSTextAlignmentRight 208 | }) 209 | 210 | numberSpacingLabel.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant( 211 | numberSpacingLabel, 212 | NSLayoutAttributeWidth, 213 | NSLayoutRelationEqual, 214 | nil, 215 | NSLayoutAttributeNotAnAttribute, 216 | 1.0, 217 | column1width 218 | )) 219 | 220 | let radioButtonProportional = NSButton.alloc().initWithFrame(NSMakeRect(0,0,97,18)) 221 | radioButtonProportional.setButtonType(NSRadioButton) 222 | radioButtonProportional.setTitle('Proportional') 223 | radioButtonProportional.setState(NSOnState) 224 | 225 | threadDictionary[radioButtonProportionalID] = radioButtonProportional 226 | 227 | let radioButtonMonospacedOrTabular = NSButton.alloc().initWithFrame(NSMakeRect(0,0,150,18)) 228 | radioButtonMonospacedOrTabular.setButtonType(NSRadioButton) 229 | radioButtonMonospacedOrTabular.setTitle('Monospaced/Tabular') 230 | radioButtonMonospacedOrTabular.setState(NSOffState) 231 | 232 | threadDictionary[radioButtonMonospacedOrTabularID] = radioButtonMonospacedOrTabular 233 | 234 | let numberSpacingTargetFunction = (sender) => { 235 | // console.log(sender.title() + ' radio button was clicked') 236 | 237 | if (sender.title() == 'Proportional') { 238 | let settingsAttribute = getSettingsAttributeForKey_Value(kNumberSpacingType, kProportionalNumbersSelector) 239 | updateFontFeatureSettingsAttribute(settingsAttribute) 240 | } else { 241 | let settingsAttribute = getSettingsAttributeForKey_Value(kNumberSpacingType, kMonospacedNumbersSelector) 242 | updateFontFeatureSettingsAttribute(settingsAttribute) 243 | } 244 | } 245 | 246 | radioButtonProportional.setCOSJSTargetFunction(sender => numberSpacingTargetFunction(sender)) 247 | radioButtonMonospacedOrTabular.setCOSJSTargetFunction(sender => numberSpacingTargetFunction(sender)) 248 | 249 | var numberSpacingRadioGroupStackView = NSStackView.stackViewWithViews([radioButtonProportional, radioButtonMonospacedOrTabular]) 250 | numberSpacingRadioGroupStackView.setOrientation(NSUserInterfaceLayoutOrientationVertical) 251 | numberSpacingRadioGroupStackView.setAlignment(NSLayoutAttributeLeading) 252 | numberSpacingRadioGroupStackView.setSpacing(4) 253 | numberSpacingRadioGroupStackView.setTranslatesAutoresizingMaskIntoConstraints(false) 254 | 255 | var row2 = NSStackView.alloc().initWithFrame(NSMakeRect(0,0,mainViewWidth,36)) 256 | row2.setOrientation(NSUserInterfaceLayoutOrientationHorizontal) 257 | row2.setAlignment(NSLayoutAttributeFirstBaseline) 258 | row2.setSpacing(columnSpacing) 259 | row2.setViews_inGravity([numberSpacingLabel, numberSpacingRadioGroupStackView], NSStackViewGravityLeading) 260 | 261 | // MARK: Setup Row 3 262 | var numberCaseLabel = createTextField({ 263 | text: "Number Case:", 264 | frame: NSMakeRect(0,0,column1width,17), 265 | alignment: NSTextAlignmentRight 266 | }) 267 | 268 | numberCaseLabel.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant( 269 | numberCaseLabel, 270 | NSLayoutAttributeWidth, 271 | NSLayoutRelationEqual, 272 | nil, 273 | NSLayoutAttributeNotAnAttribute, 274 | 1.0, 275 | column1width 276 | )) 277 | 278 | var radioButtonLiningFigures = NSButton.alloc().initWithFrame(NSMakeRect(0,0,104,17)) 279 | radioButtonLiningFigures.setButtonType(NSRadioButton) 280 | radioButtonLiningFigures.setTitle('Lining figures') 281 | radioButtonLiningFigures.setState(NSOnState) 282 | 283 | threadDictionary[radioButtonLiningFiguresID] = radioButtonLiningFigures 284 | 285 | var radioButtonOldStyleFigures = NSButton.alloc().initWithFrame(NSMakeRect(0,0,124,18)) 286 | radioButtonOldStyleFigures.setButtonType(NSRadioButton) 287 | radioButtonOldStyleFigures.setTitle('Old-style figures') 288 | radioButtonOldStyleFigures.setState(NSOffState) 289 | 290 | threadDictionary[radioButtonOldStyleFiguresID] = radioButtonOldStyleFigures 291 | 292 | let numberCaseTargetFunction = (sender) => { 293 | // console.log(sender.title() + ' radio button was clicked') 294 | 295 | if (sender.title() == "Old-style figures") { 296 | let settingsAttribute = getSettingsAttributeForKey_Value(kNumberCaseType, kLowerCaseNumbersSelector) 297 | updateFontFeatureSettingsAttribute(settingsAttribute) 298 | } else { 299 | let settingsAttribute = getSettingsAttributeForKey_Value(kNumberCaseType, kUpperCaseNumbersSelector) 300 | updateFontFeatureSettingsAttribute(settingsAttribute) 301 | } 302 | } 303 | 304 | radioButtonLiningFigures.setCOSJSTargetFunction(sender => numberCaseTargetFunction(sender)) 305 | radioButtonOldStyleFigures.setCOSJSTargetFunction(sender => numberCaseTargetFunction(sender)) 306 | 307 | var numberCaseRadioGroupStackView = NSStackView.stackViewWithViews([radioButtonLiningFigures,radioButtonOldStyleFigures]) 308 | numberCaseRadioGroupStackView.setOrientation(NSUserInterfaceLayoutOrientationVertical) 309 | numberCaseRadioGroupStackView.setAlignment(NSLayoutAttributeLeading) 310 | numberCaseRadioGroupStackView.setSpacing(4) 311 | numberCaseRadioGroupStackView.setTranslatesAutoresizingMaskIntoConstraints(false) 312 | 313 | var row3 = NSStackView.alloc().initWithFrame(NSMakeRect(0,0,mainViewWidth,36)) 314 | row3.setOrientation(NSUserInterfaceLayoutOrientationHorizontal) 315 | row3.setAlignment(NSLayoutAttributeFirstBaseline) 316 | row3.setSpacing(columnSpacing) 317 | row3.setViews_inGravity([numberCaseLabel, numberCaseRadioGroupStackView], NSStackViewGravityLeading) 318 | 319 | // MARK: Setup Row 4 320 | var smallCapsLabel = createTextField({ 321 | text: "Small Caps:", 322 | frame: NSMakeRect(0,0,column1width,17), 323 | alignment: NSTextAlignmentRight 324 | }) 325 | 326 | smallCapsLabel.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant( 327 | smallCapsLabel, 328 | NSLayoutAttributeWidth, 329 | NSLayoutRelationEqual, 330 | nil, 331 | NSLayoutAttributeNotAnAttribute, 332 | 1.0, 333 | column1width 334 | )) 335 | 336 | var smallCapsExampleLabel = createTextField({ 337 | text: "Tt →", 338 | frame: NSMakeRect(0,0,32,17), 339 | alignment: NSTextAlignmentLeft, 340 | }) 341 | 342 | var pushOnOffButtonLowerCase = NSButton.alloc().initWithFrame(NSMakeRect(0,0,72,32)) 343 | pushOnOffButtonLowerCase.setButtonType(NSButtonTypeOnOff) 344 | pushOnOffButtonLowerCase.setBezelStyle(NSRoundedBezelStyle) 345 | 346 | let lowerCaseAttributedString = NSMutableAttributedString.new().initWithString("Tt") 347 | let lowerCaseRange = NSMakeRange(1,1) 348 | let lowerCaseFont = getFontForKey_Value(37,1) 349 | lowerCaseAttributedString.setAlignment_range(NSTextAlignmentCenter, NSMakeRange(0,2)) 350 | lowerCaseAttributedString.addAttribute_value_range(NSFontAttributeName,lowerCaseFont,lowerCaseRange) 351 | pushOnOffButtonLowerCase.setAttributedTitle(lowerCaseAttributedString) 352 | pushOnOffButtonLowerCase.setState(NSOffState) 353 | 354 | pushOnOffButtonLowerCase.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant( 355 | pushOnOffButtonLowerCase, 356 | NSLayoutAttributeWidth, 357 | NSLayoutRelationEqual, 358 | nil, 359 | NSLayoutAttributeNotAnAttribute, 360 | 1.0, 361 | 60 362 | )) 363 | 364 | threadDictionary[pushOnOffButtonLowerCaseID] = pushOnOffButtonLowerCase 365 | 366 | let smallCapsLowerCaseTargetFunction = (sender) => { 367 | // console.log(sender.title() + ' toggle was clicked') 368 | // Small Caps Lower Case 369 | // ID: kLowerCaseType 370 | // 371 | // SELECTORS 372 | // kDefaultLowerCaseSelector = 0 373 | // Use standard lower-case glyphs 374 | // 375 | // kLowerCaseSmallCapsSelector = 1 376 | // Display lower-case glyphs as small caps. (This is the most common way of displaying small caps.) 377 | // 378 | // kLowerCasePetiteCapsSelector = 2 379 | // Display lower-case glyphs as petite caps 380 | // 381 | if (sender.state() == 0) { 382 | let settingsAttribute = getSettingsAttributeForKey_Value(kLowerCaseType, kDefaultLowerCaseSelector) 383 | updateFontFeatureSettingsAttribute(settingsAttribute) 384 | } else { 385 | let settingsAttribute = getSettingsAttributeForKey_Value(kLowerCaseType, kLowerCaseSmallCapsSelector) 386 | updateFontFeatureSettingsAttribute(settingsAttribute) 387 | } 388 | } 389 | pushOnOffButtonLowerCase.setCOSJSTargetFunction(sender => smallCapsLowerCaseTargetFunction(sender)) 390 | 391 | var lowerCaseLabel = createTextField({ 392 | text: "Lower Case", 393 | frame: NSMakeRect(0,0,65,14), 394 | alignment: NSTextAlignmentCenter, 395 | fontSize: 11 396 | }) 397 | 398 | var lowerCaseStackView = NSStackView.stackViewWithViews([pushOnOffButtonLowerCase,lowerCaseLabel]) 399 | lowerCaseStackView.setOrientation(NSUserInterfaceLayoutOrientationVertical) 400 | lowerCaseStackView.setAlignment(NSLayoutAttributeCenterX) 401 | lowerCaseStackView.setSpacing(4) 402 | lowerCaseStackView.setTranslatesAutoresizingMaskIntoConstraints(false) 403 | 404 | var pushOnOffButtonUpperCase = NSButton.alloc().initWithFrame(NSMakeRect(0,0,72,32)) 405 | pushOnOffButtonUpperCase.setButtonType(NSButtonTypeOnOff) 406 | pushOnOffButtonUpperCase.setBezelStyle(NSRoundedBezelStyle) 407 | 408 | let upperCaseAttributedString = NSMutableAttributedString.new().initWithString("Tt") 409 | let upperCaseRange = NSMakeRange(0,1) 410 | let upperCaseFont = getFontForKey_Value(38,1) 411 | upperCaseAttributedString.setAlignment_range(NSTextAlignmentCenter, NSMakeRange(0,2)) 412 | let centerAlignUpperCase = NSMutableParagraphStyle.new().init().setAlignment(NSTextAlignmentCenter) 413 | upperCaseAttributedString.addAttribute_value_range(NSFontAttributeName, upperCaseFont, upperCaseRange) 414 | pushOnOffButtonUpperCase.setAttributedTitle(upperCaseAttributedString) 415 | pushOnOffButtonUpperCase.setState(NSOffState) 416 | 417 | pushOnOffButtonUpperCase.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant( 418 | pushOnOffButtonUpperCase, 419 | NSLayoutAttributeWidth, 420 | NSLayoutRelationEqual, 421 | nil, 422 | NSLayoutAttributeNotAnAttribute, 423 | 1.0, 424 | 60 425 | )) 426 | 427 | threadDictionary[pushOnOffButtonUpperCaseID] = pushOnOffButtonUpperCase 428 | 429 | let smallCapsUpperCaseTargetFunction = (sender) => { 430 | // console.log(sender.title() + ' toggle was clicked') 431 | //Small Caps Upper Case 432 | // ID: kUpperCaseType 433 | // 434 | // SELECTORS 435 | // 436 | // kDefaultUpperCaseSelector = 0 437 | // Use standard upper-case glyphs 438 | // 439 | // kUpperCaseSmallCapsSelector = 1 440 | // Display upper-case glyphs as small caps (used commonly with acronyms). 441 | // 442 | // kUpperCasePetiteCapsSelector = 2 443 | // Display upper-case glyphs as petite caps 444 | // 445 | if (sender.state() == 0) { 446 | //Need to set to default setting 447 | let settingsAttribute = getSettingsAttributeForKey_Value(kUpperCaseType, kDefaultUpperCaseSelector) 448 | updateFontFeatureSettingsAttribute(settingsAttribute) 449 | } else { 450 | let settingsAttribute = getSettingsAttributeForKey_Value(kUpperCaseType, kUpperCaseSmallCapsSelector) 451 | updateFontFeatureSettingsAttribute(settingsAttribute) 452 | } 453 | } 454 | pushOnOffButtonUpperCase.setCOSJSTargetFunction(sender => smallCapsUpperCaseTargetFunction(sender)) 455 | 456 | var upperCaseLabel = createTextField({ 457 | text: "Upper Case", 458 | frame: NSMakeRect(0,0,66,14), 459 | alignment: NSTextAlignmentCenter, 460 | fontSize: 11 461 | }) 462 | 463 | var upperCaseStackView = NSStackView.stackViewWithViews([pushOnOffButtonUpperCase,upperCaseLabel]) 464 | upperCaseStackView.setOrientation(NSUserInterfaceLayoutOrientationVertical) 465 | upperCaseStackView.setSpacing(4) 466 | upperCaseStackView.setTranslatesAutoresizingMaskIntoConstraints(false) 467 | 468 | var smallCapsButtonGroupStackView = NSStackView.stackViewWithViews([smallCapsExampleLabel,lowerCaseStackView,upperCaseStackView]) 469 | smallCapsButtonGroupStackView.setOrientation(NSUserInterfaceLayoutOrientationHorizontal) 470 | smallCapsButtonGroupStackView.setAlignment(NSLayoutAttributeFirstBaseline) 471 | smallCapsButtonGroupStackView.setSpacing(4) 472 | smallCapsButtonGroupStackView.setTranslatesAutoresizingMaskIntoConstraints(false) 473 | 474 | var row4 = NSStackView.alloc().initWithFrame(NSMakeRect(0,0,mainViewWidth,36)) 475 | row4.setOrientation(NSUserInterfaceLayoutOrientationHorizontal) 476 | row4.setAlignment(NSLayoutAttributeFirstBaseline) 477 | row4.setSpacing(columnSpacing) 478 | row4.setViews_inGravity([smallCapsLabel, smallCapsButtonGroupStackView], NSStackViewGravityLeading) 479 | 480 | // MARK: SETUP ROW 5 481 | var sfSymbolSizeLabel = createTextField({ 482 | text: "SF Symbol Size:", 483 | frame: NSMakeRect(0,0,column1width,17), 484 | alignment: NSTextAlignmentRight 485 | }) 486 | 487 | sfSymbolSizeLabel.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant( 488 | sfSymbolSizeLabel, 489 | NSLayoutAttributeWidth, 490 | NSLayoutRelationEqual, 491 | nil, 492 | NSLayoutAttributeNotAnAttribute, 493 | 1.0, 494 | column1width 495 | )) 496 | 497 | var sfSymbolSizePopupButton = NSPopUpButton.alloc().initWithFrame(NSMakeRect(0,0,150,25)) 498 | sfSymbolSizePopupButton.addItemsWithTitles([ 499 | 'Small', 500 | 'Medium', 501 | 'Large', 502 | 'Multiple' 503 | ]) 504 | threadDictionary[sfSymbolSizePopupButtonID] = sfSymbolSizePopupButton 505 | sfSymbolSizePopupButton.itemWithTitle('Multiple').setHidden(true) 506 | 507 | let sfSymbolSizeTargetFunction = (sender) => { 508 | //console.log(sender.title() + ' dropdown button was selected') 509 | // sfSymbolSizeLabel 510 | // ID: kStylisticAlternativesType 35 511 | // 512 | // Selectors are very brittle. Need to figure out how to read StylisticAlts 513 | // from font so I'm not guessing what number the selectors are (they could change). 514 | // I should get their selectors progrmatically. 515 | // 516 | // kStylisticAltFifteenOnSelector 517 | // This is referring to Small Symbols or Glyphs for the SF Pro Text Font 518 | // 519 | // kStylisticAltSixteenOnSelector 520 | // This is referring to Large Symbols or Glyphs for the SF Pro Text Font 521 | // 522 | // Note: 523 | // There is no Medium Symbols checkbox for SF Pro Text 524 | if (sender.title() == 'Small') { 525 | let settingsAttributeSFSmall = getSettingsAttributeForKey_Value(kStylisticAlternativesType, kStylisticAltFifteenOnSelector) 526 | let settingsAttributeSFLarge = getSettingsAttributeForKey_Value(kStylisticAlternativesType, kStylisticAltSixteenOffSelector) 527 | let settingsAttributes = [settingsAttributeSFSmall,settingsAttributeSFLarge] 528 | settingsAttributes.forEach(settingsAttribute => { 529 | //console.log(settingsAttribute) 530 | updateFontFeatureSettingsAttribute(settingsAttribute) 531 | }) 532 | } else if (sender.title() == 'Medium') { 533 | let settingsAttributeSFSmall = getSettingsAttributeForKey_Value(kStylisticAlternativesType, kStylisticAltFifteenOffSelector) 534 | let settingsAttributeSFLarge = getSettingsAttributeForKey_Value(kStylisticAlternativesType, kStylisticAltSixteenOffSelector) 535 | let settingsAttributes = [settingsAttributeSFSmall,settingsAttributeSFLarge] 536 | settingsAttributes.forEach(settingsAttribute => { 537 | //console.log(settingsAttribute) 538 | updateFontFeatureSettingsAttribute(settingsAttribute) 539 | }) 540 | } else if (sender.title() == 'Large') { 541 | let settingsAttributeSFSmall = getSettingsAttributeForKey_Value(kStylisticAlternativesType, kStylisticAltFifteenOffSelector) 542 | let settingsAttributeSFLarge = getSettingsAttributeForKey_Value(kStylisticAlternativesType, kStylisticAltSixteenOnSelector) 543 | let settingsAttributes = [settingsAttributeSFSmall,settingsAttributeSFLarge] 544 | settingsAttributes.forEach(settingsAttribute => { 545 | //console.log(settingsAttribute) 546 | updateFontFeatureSettingsAttribute(settingsAttribute) 547 | }) 548 | } else { 549 | logWarning("Out of sfSymbolSizeDropdown bounds") 550 | } 551 | } 552 | 553 | sfSymbolSizePopupButton.setCOSJSTargetFunction(sender => sfSymbolSizeTargetFunction(sender)) 554 | 555 | var row5 = NSStackView.alloc().initWithFrame(NSMakeRect(0,0,mainViewWidth,25)) 556 | row5.setOrientation(NSUserInterfaceLayoutOrientationHorizontal) 557 | row5.setAlignment(NSLayoutAttributeFirstBaseline) 558 | row5.setSpacing(columnSpacing) 559 | row5.setViews_inGravity([sfSymbolSizeLabel,sfSymbolSizePopupButton],NSStackViewGravityLeading) 560 | 561 | threadDictionary[sfSymbolSizeRow] = row5 562 | row5.setHidden(true) 563 | 564 | // MARK: Combine rows together 565 | var mainContentView = NSStackView.stackViewWithViews([row1,row2,row3, row4, row5]) 566 | mainContentView.setOrientation(NSUserInterfaceLayoutOrientationVertical) 567 | mainContentView.setAlignment(NSLayoutAttributeLeading) 568 | mainContentView.setSpacing(8) 569 | mainContentView.setTranslatesAutoresizingMaskIntoConstraints(false) 570 | 571 | panel.contentView().addSubview(mainContentView) 572 | panel.contentView().setFlipped(true) 573 | fitSubviewToView(mainContentView,panel.contentView(),[16.0,16.0,8.0,16.0]) 574 | 575 | addVibrancyView(panel.contentView()) 576 | 577 | threadDictionary[identifier] = panel; 578 | 579 | var closeButton = panel.standardWindowButton(NSWindowCloseButton) 580 | closeButton.setCOSJSTargetFunction(function(sender) { 581 | closePanel(panel, threadDictionary, identifier) 582 | }) 583 | } 584 | 585 | function addVibrancyView(superview) { 586 | var vibrancy = NSView.alloc().initWithFrame(NSMakeRect(0, 0, panelWidth, panelHeight)) 587 | vibrancy.setBackgroundColor(NSColor.colorWithRed_green_blue_alpha(0.0,0.0,0.0,0.6)) 588 | vibrancy.setWantsLayer(true) 589 | 590 | superview.addSubview(vibrancy) 591 | fitSubviewToView(vibrancy,superview, [0.0,0.0,0.0,0.0]) 592 | 593 | var fontWarning = createTextField({ 594 | text: "Selected text doesn't contain any supported font features. Please try a different typeface.", 595 | frame: NSMakeRect(0,0,panelWidth,17), 596 | alignment: NSTextAlignmentCenter, 597 | fontSize: 12 598 | }) 599 | fontWarning.setTextColor(NSColor.whiteColor()) 600 | 601 | vibrancy.addSubview(fontWarning) 602 | fontWarning.setTranslatesAutoresizingMaskIntoConstraints(false) 603 | addEdgeConstraint(NSLayoutAttributeCenterX, fontWarning, vibrancy, 0) 604 | addEdgeConstraint(NSLayoutAttributeTop, fontWarning, vibrancy, 40.0) 605 | fontWarning.addConstraint(NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant( 606 | fontWarning, 607 | NSLayoutAttributeWidth, 608 | NSLayoutRelationEqual, 609 | nil, 610 | NSLayoutAttributeNotAnAttribute, 611 | 1.0, 612 | 260 613 | )) 614 | 615 | vibrancy.setHidden(true) 616 | let threadDictionary = NSThread.mainThread().threadDictionary() 617 | threadDictionary[vibrancyViewID] = vibrancy 618 | } 619 | 620 | function getBlurFilter() { 621 | let blurFilter = CIFilter.filterWithName("CIGaussianBlur") 622 | blurFilter.setDefaults() 623 | blurFilter.setValue_forKey(2, 'inputRadius') 624 | blurFilter.setName("blur") 625 | 626 | return blurFilter 627 | } 628 | 629 | function fitSubviewToView(subview, view, constants) { 630 | subview.setTranslatesAutoresizingMaskIntoConstraints(false) 631 | 632 | addEdgeConstraint(NSLayoutAttributeTop, subview, view, constants[0]) 633 | addEdgeConstraint(NSLayoutAttributeTrailing, subview, view, constants[1]) 634 | addEdgeConstraint(NSLayoutAttributeBottom, subview, view, constants[2]) 635 | addEdgeConstraint(NSLayoutAttributeLeading, subview, view, constants[3]) 636 | } 637 | 638 | function addEdgeConstraint(layoutAttribute, subview, view, constant) { 639 | view.addConstraint( 640 | NSLayoutConstraint.constraintWithItem_attribute_relatedBy_toItem_attribute_multiplier_constant( 641 | subview, 642 | layoutAttribute, 643 | NSLayoutRelationEqual, 644 | view, 645 | layoutAttribute, 646 | 1.0, 647 | constant 648 | ) 649 | ) 650 | } 651 | 652 | function createTextField({text, frame, alignment, fontSize = 13}) { 653 | const label = NSTextField.alloc().initWithFrame(frame) 654 | label.setStringValue(text) 655 | label.setAlignment(alignment) 656 | label.setFont(NSFont.systemFontOfSize(fontSize)) 657 | label.setBezeled(false) 658 | label.setDrawsBackground(false) 659 | label.setEditable(false) 660 | label.setSelectable(false) 661 | return label 662 | } 663 | 664 | function updateFontFeatureSettingsAttribute(settingsAttribute) { 665 | var document = sketch.getSelectedDocument(); 666 | var selectedLayers = document.selectedLayers.layers 667 | var textLayers = selectedLayers.filter(layer => layer.type == "Text") 668 | 669 | textLayers.forEach(textLayer => { 670 | let font = textLayer.sketchObject.font() 671 | let fontSize = font.pointSize() 672 | let fontFeatureSettings = font 673 | .fontDescriptor() 674 | .fontAttributes()[NSFontFeatureSettingsAttribute] 675 | let descriptor = font.fontDescriptor().fontDescriptorByAddingAttributes(settingsAttribute) 676 | let newFont = NSFont.fontWithDescriptor_size(descriptor,fontSize) 677 | textLayer.sketchObject.setFont(newFont) 678 | 679 | if (textLayer.sketchObject.isEditingText() == 1) { 680 | let textView = textLayer.sketchObject.editingDelegate().textView() 681 | let textStorage = textView.textStorage() 682 | let fonts = getFontsFromTextLayer(textLayer) 683 | fonts.forEach(fontWithRange => { 684 | let font = fontWithRange.font 685 | let range = fontWithRange.range 686 | let fontSize = font.pointSize() 687 | 688 | let descriptor = font 689 | .fontDescriptor() 690 | .fontDescriptorByAddingAttributes(settingsAttribute) 691 | 692 | let newFont = NSFont.fontWithDescriptor_size(descriptor,fontSize) 693 | let attrsDict = NSDictionary.dictionaryWithObject_forKey(newFont,NSFontAttributeName) 694 | textStorage.addAttributes_range(attrsDict,range) 695 | }) 696 | textView.didChangeText() 697 | } 698 | }) 699 | document.sketchObject.inspectorController().reload() 700 | } 701 | 702 | function getFontForKey_Value(key, value) { 703 | let defaultButtonFont = NSFont.boldSystemFontOfSize(13) 704 | let settingsAttribute = getSettingsAttributeForKey_Value(key, value) 705 | var fontFeatureSettings = defaultButtonFont.fontDescriptor().fontAttributes()[NSFontFeatureSettingsAttribute] 706 | const descriptor = defaultButtonFont.fontDescriptor().fontDescriptorByAddingAttributes(settingsAttribute) 707 | const newFont = NSFont.fontWithDescriptor_size(descriptor,13) 708 | return newFont 709 | } 710 | 711 | function getSettingsAttributeForKey_Value(key, value) { 712 | let settingsAttribute = { 713 | [NSFontFeatureSettingsAttribute]: [{ 714 | [NSFontFeatureTypeIdentifierKey]: key, 715 | [NSFontFeatureSelectorIdentifierKey]: value 716 | }] 717 | } 718 | 719 | return settingsAttribute 720 | } 721 | 722 | function updateUI(useFullSelection = false) { 723 | var document = sketch.getSelectedDocument() 724 | var selectedLayers = document.selectedLayers.layers 725 | let threadDictionary = NSThread.mainThread().threadDictionary() 726 | 727 | let warning = threadDictionary[vibrancyViewID] 728 | 729 | if (selectedLayers == null) { 730 | disableUI(threadDictionary) 731 | if (!warning.isHidden()) { 732 | warning.layer().setBackgroundFilters([]) 733 | warning.setHidden(true) 734 | } 735 | return 736 | } 737 | 738 | var textLayers = selectedLayers.filter(layer => layer.type == "Text") 739 | if (textLayers.length == 0) { 740 | disableUI(threadDictionary) 741 | if (!warning.isHidden()) { 742 | warning.layer().setBackgroundFilters([]) 743 | warning.setHidden(true) 744 | } 745 | return 746 | } 747 | 748 | var textLayersFeatureSettings = [] 749 | 750 | let fontSettingsObjects = [] 751 | textLayers.forEach(textLayer => { 752 | let fonts = getFontsFromTextLayer(textLayer, useFullSelection) 753 | fonts.forEach(fontWithRange => { 754 | checkToShowSFSymbolOption(fontWithRange.font) 755 | let currentSettings = getSettingsForFont(fontWithRange.font) 756 | fontSettingsObjects.push(currentSettings) 757 | }) 758 | }) 759 | 760 | 761 | // Settings object looks like this 762 | //{ 763 | // smallCapsLowerCase: true 764 | // smallCapsUpperCase: false 765 | // sfSymbolSize: 'medium' 766 | // numberCase: 'disabled' 767 | // verticalPosition: 'default' 768 | // numberSpacing: 'proportional' 769 | // } 770 | 771 | let updatedUISettings = { 772 | "smallCapsLowerCase": [], 773 | "smallCapsUpperCase": [], 774 | "sfSymbolSize": [], 775 | "numberCase": [], 776 | "verticalPosition": [], 777 | "numberSpacing": [] 778 | } 779 | fontSettingsObjects.forEach(fontSetting => { 780 | Object.keys(fontSetting).forEach(key => { 781 | updatedUISettings[key].push(fontSetting[key]) 782 | }) 783 | }) 784 | 785 | // Deduplicate settingsCollection to only have unique entries 786 | for (var property in updatedUISettings) { 787 | updatedUISettings[property] = updatedUISettings[property].filter(onlyUnique) 788 | } 789 | 790 | function onlyUnique(value,index,self) { 791 | return self.indexOf(value) === index 792 | } 793 | 794 | let showWarningMessage = true 795 | for (var setting in updatedUISettings) { 796 | if (updatedUISettings[setting].length > 1 ) { 797 | showWarningMessage = false 798 | break; 799 | } 800 | 801 | if (updatedUISettings[setting][0] !== "disabled") { 802 | showWarningMessage = false 803 | break; 804 | } 805 | } 806 | if ((showWarningMessage) && (warning.isHidden())) { 807 | warning.setHidden(false) 808 | warning.layer().setBackgroundFilters([getBlurFilter()]) 809 | } else if ((!showWarningMessage) && (!warning.isHidden())) { 810 | warning.layer().setBackgroundFilters([]) 811 | warning.setHidden(true) 812 | } else if ((showWarningMessage) && (!warning.isHidden())) { 813 | logWarning("Warning already being shown") 814 | } else { 815 | logWarning("Warning is already hidden") 816 | } 817 | 818 | //Update UI Panel with only one update (to prevent flickering) 819 | for (var uiSetting in updatedUISettings) { 820 | if (uiSetting == 'verticalPosition') { 821 | 822 | let verticalPositionPopupButton = threadDictionary[verticalPositionPopupButtonID] 823 | verticalPositionPopupButton.setEnabled(true) 824 | 825 | //Clear mixed state items before setting them 826 | clearVerticalPositionPopupButtonState() 827 | 828 | if (updatedUISettings[uiSetting].length > 1) { 829 | verticalPositionPopupButton.selectItemWithTitle('Multiple') 830 | 831 | updatedUISettings[uiSetting].forEach(verticalPositionSetting => { 832 | if (verticalPositionSetting == 'default') { 833 | verticalPositionPopupButton.itemWithTitle('Default Position').setState(NSControlStateValueMixed) 834 | } else if (verticalPositionSetting == 'superscript') { 835 | verticalPositionPopupButton.itemWithTitle('Superscript').setState(NSControlStateValueMixed) 836 | } else if (verticalPositionSetting == 'subscript') { 837 | verticalPositionPopupButton.itemWithTitle('Subscript').setState(NSControlStateValueMixed) 838 | } else if (verticalPositionSetting == 'ordinals') { 839 | verticalPositionPopupButton.itemWithTitle('Ordinals').setState(NSControlStateValueMixed) 840 | } else if (verticalPositionSetting == 'scientific inferiors') { 841 | verticalPositionPopupButton.itemWithTitle('Scientific Notation').setState(NSControlStateValueMixed) 842 | } 843 | }) 844 | } else { 845 | if (updatedUISettings[uiSetting][0] == 'default') { 846 | // console.log('Setting UI: Vertical Position = Default Position') 847 | verticalPositionPopupButton.selectItemWithTitle('Default Position') 848 | verticalPositionPopupButton.itemWithTitle('Default Position').setState(NSControlStateValueOn) 849 | } else if (updatedUISettings[uiSetting][0] == 'superscript') { 850 | // console.log('Setting UI: Vertical Position = Superscript') 851 | verticalPositionPopupButton.selectItemWithTitle('Superscript') 852 | verticalPositionPopupButton.itemWithTitle('Superscript').setState(NSControlStateValueOn) 853 | } else if (updatedUISettings[uiSetting][0] == 'subscript') { 854 | // console.log('Setting UI: Vertical Position = Subscript') 855 | verticalPositionPopupButton.selectItemWithTitle('Subscript') 856 | verticalPositionPopupButton.itemWithTitle('Subscript').setState(NSControlStateValueOn) 857 | } else if (updatedUISettings[uiSetting][0] == 'ordinals') { 858 | // console.log('Setting UI: Vertical Position = Ordinals') 859 | verticalPositionPopupButton.selectItemWithTitle('Ordinals') 860 | verticalPositionPopupButton.itemWithTitle('Ordinals').setState(NSControlStateValueOn) 861 | } else if (updatedUISettings[uiSetting][0] == 'scientific inferiors') { 862 | // console.log('Setting UI: Vertical Position = Scientific Notation') 863 | verticalPositionPopupButton.selectItemWithTitle('Scientific Notation') 864 | verticalPositionPopupButton.itemWithTitle('Scientific Notation').setState(NSControlStateValueOn) 865 | } else if (updatedUISettings[uiSetting][0] == 'disabled') { 866 | verticalPositionPopupButton.setEnabled(false) 867 | } else { 868 | logWarning('BetterTypeTool: ERROR Attempting update panel state - Out of scope of verticalPosition options') 869 | } 870 | } 871 | 872 | } else if (uiSetting == 'numberSpacing') { 873 | let radioButtonProportional = threadDictionary[radioButtonProportionalID] 874 | let radioButtonMonospacedOrTabular = threadDictionary[radioButtonMonospacedOrTabularID] 875 | radioButtonProportional.setEnabled(true) 876 | radioButtonMonospacedOrTabular.setEnabled(true) 877 | 878 | if (updatedUISettings[uiSetting].length > 1) { 879 | radioButtonProportional.setState(NSOffState) 880 | radioButtonMonospacedOrTabular.setState(NSOffState) 881 | } else { 882 | if (updatedUISettings[uiSetting][0] == 'proportional') { 883 | // console.log('Setting UI: Number Spacing = Proportional') 884 | radioButtonProportional.setState(NSOnState) 885 | radioButtonMonospacedOrTabular.setState(NSOffState) 886 | } else if (updatedUISettings[uiSetting][0] == 'monospaced') { 887 | // console.log('Setting UI: Number Spacing == Monospaced/Tabular') 888 | radioButtonProportional.setState(NSOffState) 889 | radioButtonMonospacedOrTabular.setState(NSOnState) 890 | } else if (updatedUISettings[uiSetting][0] == 'disabled') { 891 | radioButtonProportional.setState(NSOffState) 892 | radioButtonMonospacedOrTabular.setState(NSOffState) 893 | radioButtonProportional.setEnabled(false) 894 | radioButtonMonospacedOrTabular.setEnabled(false) 895 | } else { 896 | logWarning('BetterTypeTool: ERROR Attempting update panel state - Out of scope of numberSpacing options') 897 | } 898 | } 899 | 900 | } else if (uiSetting == 'numberCase') { 901 | let radioButtonLiningFigures = threadDictionary[radioButtonLiningFiguresID] 902 | let radioButtonOldStyleFigures = threadDictionary[radioButtonOldStyleFiguresID] 903 | radioButtonLiningFigures.setEnabled(true) 904 | radioButtonOldStyleFigures.setEnabled(true) 905 | 906 | if (updatedUISettings[uiSetting].length > 1) { 907 | radioButtonLiningFigures.setState(NSOffState) 908 | radioButtonOldStyleFigures.setState(NSOffState) 909 | } else { 910 | if (updatedUISettings[uiSetting][0] == 'lining') { 911 | // console.log('Setting UI: Number Case = Lining figures') 912 | radioButtonLiningFigures.setState(NSOnState) 913 | radioButtonOldStyleFigures.setState(NSOffState) 914 | } else if (updatedUISettings[uiSetting][0] == 'oldStyle') { 915 | // console.log('Setting UI: Number Case = Old-style figures') 916 | radioButtonLiningFigures.setState(NSOffState) 917 | radioButtonOldStyleFigures.setState(NSOnState) 918 | } else if (updatedUISettings[uiSetting][0] == 'disabled') { 919 | radioButtonLiningFigures.setState(NSOffState) 920 | radioButtonOldStyleFigures.setState(NSOffState) 921 | radioButtonLiningFigures.setEnabled(false) 922 | radioButtonOldStyleFigures.setEnabled(false) 923 | } else { 924 | logWarning('BetterTypeTool: ERROR Attempting to update panel state - Out of scope of numberCase options') 925 | } 926 | } 927 | 928 | } else if (uiSetting == 'smallCapsUpperCase') { 929 | let pushOnOffButtonUpperCase = threadDictionary[pushOnOffButtonUpperCaseID] 930 | pushOnOffButtonUpperCase.setEnabled(true) 931 | 932 | if (updatedUISettings[uiSetting].length > 1) { 933 | pushOnOffButtonUpperCase.setState(NSOffState) 934 | } else { 935 | if (updatedUISettings[uiSetting][0] == false) { 936 | // console.log('Setting UI: Small Caps Upper Case = Off') 937 | pushOnOffButtonUpperCase.setState(NSOffState) 938 | } else if (updatedUISettings[uiSetting][0] == true) { 939 | // console.log('Setting UI: Small Caps Upper Case = On') 940 | pushOnOffButtonUpperCase.setState(NSOnState) 941 | } else if (updatedUISettings[uiSetting][0] == 'disabled') { 942 | pushOnOffButtonUpperCase.setState(NSOffState) 943 | pushOnOffButtonUpperCase.setEnabled(false) 944 | } else { 945 | logWarning('BetterTypeTool: ERROR Attempting to update panel state - Out of scope of smallCapsUpperCase options') 946 | } 947 | } 948 | 949 | } else if (uiSetting == 'smallCapsLowerCase') { 950 | let pushOnOffButtonLowerCase = threadDictionary[pushOnOffButtonLowerCaseID] 951 | pushOnOffButtonLowerCase.setEnabled(true) 952 | 953 | if (updatedUISettings[uiSetting].length > 1) { 954 | pushOnOffButtonLowerCase.setState(NSOffState) 955 | } else { 956 | if (updatedUISettings[uiSetting][0] == false) { 957 | // console.log('Setting UI: Small Caps Lower Case = Off') 958 | pushOnOffButtonLowerCase.setState(NSOffState) 959 | } else if (updatedUISettings[uiSetting][0] == true) { 960 | // console.log('Setting UI: Small Caps Lower Case = On') 961 | pushOnOffButtonLowerCase.setState(NSOnState) 962 | } else if (updatedUISettings[uiSetting][0] == 'disabled') { 963 | pushOnOffButtonLowerCase.setState(NSOffState) 964 | pushOnOffButtonLowerCase.setEnabled(false) 965 | } else { 966 | logWarning('BetterTypeTool: ERROR Attempting to update panel state - Out of scope of smallCapsLowerCase options') 967 | } 968 | } 969 | 970 | } else if (uiSetting == 'sfSymbolSize') { 971 | let sfSymbolSizePopupButton = threadDictionary[sfSymbolSizePopupButtonID] 972 | sfSymbolSizePopupButton.setEnabled(true) 973 | 974 | clearSFSymbolSizePopupButton() 975 | 976 | if (updatedUISettings[uiSetting].length > 1) { 977 | sfSymbolSizePopupButton.selectItemWithTitle('Multiple') 978 | 979 | updatedUISettings[uiSetting].forEach(sfSymbolSizeSetting => { 980 | if (sfSymbolSizeSetting == 'small') { 981 | sfSymbolSizePopupButton.itemWithTitle('Small').setState(NSControlStateValueMixed) 982 | } else if (sfSymbolSizeSetting == 'medium') { 983 | sfSymbolSizePopupButton.itemWithTitle('Medium').setState(NSControlStateValueMixed) 984 | } else if (sfSymbolSizeSetting == 'large') { 985 | sfSymbolSizePopupButton.itemWithTitle('Large').setState(NSControlStateValueMixed) 986 | } 987 | }) 988 | } else { 989 | if (updatedUISettings[uiSetting][0] == 'small') { 990 | sfSymbolSizePopupButton.selectItemWithTitle('Small') 991 | sfSymbolSizePopupButton.itemWithTitle('Small').setState(NSControlStateValueOn) 992 | } else if (updatedUISettings[uiSetting][0] == 'medium') { 993 | sfSymbolSizePopupButton.selectItemWithTitle('Medium') 994 | sfSymbolSizePopupButton.itemWithTitle('Medium').setState(NSControlStateValueOn) 995 | } else if (updatedUISettings[uiSetting][0] == 'large'){ 996 | sfSymbolSizePopupButton.selectItemWithTitle('Large') 997 | sfSymbolSizePopupButton.itemWithTitle('Large').setState(NSControlStateValueOn) 998 | } else if (updatedUISettings[uiSetting][0] == 'disabled') { 999 | sfSymbolSizePopupButton.setEnabled(false) 1000 | } else { 1001 | logWarning('BetterTypeTool: ERROR Attempting update panel state - Out of scope of sfSymbolSize options') 1002 | } 1003 | } 1004 | } else { 1005 | logWarning('Error: Unhandled uiSetting Property') 1006 | logWarning(updatedUISettings[uiSetting]) 1007 | } 1008 | } 1009 | } 1010 | 1011 | function disableUI(threadDictionary, optionsToDisableArray = ['all']) { 1012 | // optionsToDisable is an array that can include "all", "verticalPosition", "numberSpacing", "numberCase", "smallCapsUppercase", "smallCapsLowerCase", "sfSymbolSize" 1013 | //TODO: Maybe reset the state to the deault params when UI is disabled 1014 | 1015 | if (optionsToDisableArray.includes('all') || optionsToDisableArray.includes('verticalPosition')) { 1016 | let verticalPositionPopupButton = threadDictionary[verticalPositionPopupButtonID] 1017 | verticalPositionPopupButton.setEnabled(false) 1018 | } 1019 | 1020 | if (optionsToDisableArray.includes('all') || optionsToDisableArray.includes('numberSpacing')) { 1021 | let radioButtonProportional = threadDictionary[radioButtonProportionalID] 1022 | let radioButtonMonospacedOrTabular = threadDictionary[radioButtonMonospacedOrTabularID] 1023 | radioButtonProportional.setEnabled(false) 1024 | radioButtonMonospacedOrTabular.setEnabled(false) 1025 | } 1026 | 1027 | if (optionsToDisableArray.includes('all') || optionsToDisableArray.includes('numberCase')) { 1028 | let radioButtonLiningFigures = threadDictionary[radioButtonLiningFiguresID] 1029 | let radioButtonOldStyleFigures = threadDictionary[radioButtonOldStyleFiguresID] 1030 | radioButtonLiningFigures.setEnabled(false) 1031 | radioButtonOldStyleFigures.setEnabled(false) 1032 | } 1033 | 1034 | if (optionsToDisableArray.includes('all') || optionsToDisableArray.includes('smallCapsUpperCase')) { 1035 | let pushOnOffButtonUpperCase = threadDictionary[pushOnOffButtonUpperCaseID] 1036 | pushOnOffButtonUpperCase.setEnabled(false) 1037 | } 1038 | 1039 | if (optionsToDisableArray.includes('all') || optionsToDisableArray.includes('smallCapsLowerCase')) { 1040 | let pushOnOffButtonLowerCase = threadDictionary[pushOnOffButtonLowerCaseID] 1041 | pushOnOffButtonLowerCase.setEnabled(false) 1042 | } 1043 | 1044 | if (optionsToDisableArray.includes('all') || optionsToDisableArray.includes('sfSymbolSize')) { 1045 | let sfSymbolSizePopupButton = threadDictionary[sfSymbolSizePopupButtonID] 1046 | sfSymbolSizePopupButton.setEnabled(false) 1047 | } 1048 | } 1049 | 1050 | function closePanel(panel, threadDictionary, threadIdentifier) { 1051 | panel.close() 1052 | 1053 | try { 1054 | setupFramework() 1055 | let main = HSMain.alloc().init() 1056 | // Stop text selection listening 1057 | main.stopObservingTextViewSelectionChanges() 1058 | } catch(error) { 1059 | console.error(error); 1060 | } 1061 | 1062 | // Remove the reference to the panel 1063 | threadDictionary.removeObjectForKey(threadIdentifier) 1064 | threadDictionary.panelOpen = false 1065 | 1066 | // Stop this script 1067 | COScript.currentCOScript().setShouldKeepAround_(false) 1068 | } 1069 | 1070 | //Start with Default Settings 1071 | function getDefaultUISettings() { 1072 | return { 1073 | 'verticalPosition': 'default', // 'default', 'superscript', 'subscript', 'ordinals', 'scientific inferiors', 'disabled' 1074 | 'numberSpacing': 'proportional', // 'proportional', 'monospaced', 'disabled' 1075 | 'numberCase': 'lining', // 'lining', 'oldStyle', 'disabled' 1076 | 'smallCapsLowerCase': false, // bool, 'disabled' 1077 | 'smallCapsUpperCase': false, // bool, 'disabled' 1078 | 'sfSymbolSize': 'medium' // 'small', 'medium', 'large', 'disabled' 1079 | // If updating this list remember to update the default updatedUISettings 1080 | // TODO: Refactor so that the Default UI settings is in one place. 1081 | } 1082 | } 1083 | 1084 | // TODO: Make more generic to support both popupbuttons 1085 | function clearVerticalPositionPopupButtonState() { 1086 | let threadDictionary = NSThread.mainThread().threadDictionary() 1087 | let verticalPositionPopupButton = threadDictionary[verticalPositionPopupButtonID] 1088 | verticalPositionPopupButton.itemWithTitle('Default Position').setState(NSControlStateValueOff) 1089 | verticalPositionPopupButton.itemWithTitle('Superscript').setState(NSControlStateValueOff) 1090 | verticalPositionPopupButton.itemWithTitle('Subscript').setState(NSControlStateValueOff) 1091 | verticalPositionPopupButton.itemWithTitle('Ordinals').setState(NSControlStateValueOff) 1092 | verticalPositionPopupButton.itemWithTitle('Scientific Notation').setState(NSControlStateValueOff) 1093 | } 1094 | 1095 | function clearSFSymbolSizePopupButton() { 1096 | let threadDictionary = NSThread.mainThread().threadDictionary() 1097 | let sfSymbolSizePopupButton = threadDictionary[sfSymbolSizePopupButtonID] 1098 | sfSymbolSizePopupButton.itemWithTitle('Small').setState(NSControlStateValueOff) 1099 | sfSymbolSizePopupButton.itemWithTitle('Medium').setState(NSControlStateValueOff) 1100 | sfSymbolSizePopupButton.itemWithTitle('Large').setState(NSControlStateValueOff) 1101 | } 1102 | 1103 | function logWarning(warning) { 1104 | //console.log(warning) 1105 | } 1106 | 1107 | function getFontsFromTextLayer(textLayer, useFullSelection = false) { 1108 | let msTextLayer = textLayer.sketchObject 1109 | let effectiveRange = MOPointer.alloc().init() 1110 | 1111 | // if editing text then need to use the textStorage rather than the attrString 1112 | let mutableAttrString 1113 | let selectedRange 1114 | let textView 1115 | 1116 | // infer editing by checking if textView exists 1117 | let textViewExists = true 1118 | try { 1119 | textView = msTextLayer.editingDelegate().textView() 1120 | } catch { 1121 | textViewExists = false 1122 | } 1123 | 1124 | if (textViewExists) { 1125 | let textStorage = textView.textStorage() 1126 | selectedRange = textView.selectedRange() 1127 | mutableAttrString = textStorage 1128 | 1129 | // need this because selected range is 0 when going from editing state to selected frame state 1130 | if (useFullSelection) { 1131 | selectedRange = NSMakeRange(0,textLayer.text.length) 1132 | } 1133 | } else { 1134 | let attributedString = msTextLayer.attributedStringValue() 1135 | selectedRange = NSMakeRange(0,textLayer.text.length) 1136 | mutableAttrString = attributedString 1137 | } 1138 | 1139 | let fonts = [] 1140 | 1141 | if (selectedRange.length == 0) { 1142 | let font = mutableAttrString.attribute_atIndex_longestEffectiveRange_inRange( 1143 | NSFontAttributeName, 1144 | selectedRange.location, 1145 | effectiveRange, 1146 | selectedRange 1147 | ) 1148 | fonts.push({"font": font, "range": effectiveRange.value()}) 1149 | } 1150 | 1151 | while (selectedRange.length > 0) { 1152 | let font = mutableAttrString.attribute_atIndex_longestEffectiveRange_inRange( 1153 | NSFontAttributeName, 1154 | selectedRange.location, 1155 | effectiveRange, 1156 | selectedRange 1157 | ) 1158 | selectedRange = NSMakeRange( 1159 | NSMaxRange(effectiveRange.value()), 1160 | NSMaxRange(selectedRange) - NSMaxRange(effectiveRange.value()) 1161 | ) 1162 | 1163 | fonts.push({"font": font, "range": effectiveRange.value()}) 1164 | } 1165 | return fonts 1166 | } 1167 | 1168 | function checkToShowSFSymbolOption(font) { 1169 | let familyName = font.familyName() 1170 | let showSFSymbolOption = false 1171 | 1172 | let supportedFontFamilies = [ 1173 | "SF Pro Text", 1174 | "SF Pro Rounded", 1175 | "SF Pro Display", 1176 | "SF Compact Text", 1177 | "SF Compact Rounded", 1178 | "SF Compact Display" 1179 | ] 1180 | 1181 | let threadDictionary = NSThread.mainThread().threadDictionary() 1182 | let row5 = threadDictionary[sfSymbolSizeRow] 1183 | let panel = threadDictionary[threadIdentifier] 1184 | 1185 | let panelX = panel.frame().origin.x 1186 | let panelY = panel.frame().origin.y 1187 | let panelWidth = panel.frame().size.height 1188 | let panelHeight = panel.frame().size.height 1189 | supportedFontFamilies.forEach(fontFamily => { 1190 | if (familyName == fontFamily) { 1191 | showSFSymbolOption = true 1192 | // TODO Don't hard code these values 1193 | if (panelHeight != 235) { 1194 | panel.setFrame_display_animate(NSMakeRect(panelX, panelY - 25, 312, 210 + 25), true, true) 1195 | row5.setHidden(false) 1196 | } 1197 | return; 1198 | } 1199 | }) 1200 | 1201 | if (!showSFSymbolOption) { 1202 | // hide UI 1203 | if (panelHeight != 210) { 1204 | row5.setHidden(true) 1205 | panel.setFrame_display_animate(NSMakeRect(panelX, panelY + 25, 312, 210), true, true) 1206 | } 1207 | } 1208 | } 1209 | 1210 | function getOptionsToDisableFromFont(font) { 1211 | framework('CoreText') 1212 | let optionsToDisableForFont = [] 1213 | 1214 | try { 1215 | setupFramework() 1216 | let main = HSMain.alloc().init() 1217 | 1218 | const coreTextFont = CTFontCreateWithName(font.fontName(), font.pointSize(), null) 1219 | const features = CTFontCopyFeatures(coreTextFont) 1220 | let featuresArray = main.bridgeArray(features) 1221 | 1222 | optionsToDisableForFont = getOptionsToDisableFromFeaturesArray(featuresArray) 1223 | } catch (e) { 1224 | console.error(e) 1225 | } 1226 | 1227 | let familyName = font.familyName().toLowerCase().trim() 1228 | let supportedFontFamilies = [ 1229 | "sf pro text", 1230 | "sf pro rounded", 1231 | "sf pro display", 1232 | "sf compact text", 1233 | "sf compact rounded", 1234 | "sf compact display" 1235 | ] 1236 | if (!supportedFontFamilies.includes(familyName)) { 1237 | optionsToDisableForFont.push('sfSymbolSize') 1238 | } 1239 | 1240 | return optionsToDisableForFont 1241 | } 1242 | 1243 | function getOptionsToDisableFromFeaturesArray(featuresArray) { 1244 | let optionsToDisableForFont = [] 1245 | 1246 | if(!featuresArray) { 1247 | optionsToDisableForFont.push('verticalPosition','numberSpacing','numberCase', 'smallCapsLowerCase', 'smallCapsUpperCase') 1248 | } else { 1249 | let featureIDs = [] 1250 | featuresArray.forEach(feature => { 1251 | featureIDs.push(Number(feature["CTFeatureTypeIdentifier"])) 1252 | }) 1253 | 1254 | if (!featureIDs.includes(10)) { 1255 | // Vertical Position 1256 | optionsToDisableForFont.push('verticalPosition') 1257 | } 1258 | if (!featureIDs.includes(6)) { 1259 | // Number Spacing 1260 | optionsToDisableForFont.push('numberSpacing') 1261 | } 1262 | if (!featureIDs.includes(21)) { 1263 | // Number Case 1264 | optionsToDisableForFont.push('numberCase') 1265 | } 1266 | if (!featureIDs.includes(37)) { 1267 | // Small Caps Lower Case 1268 | optionsToDisableForFont.push('smallCapsLowerCase') 1269 | } 1270 | if (!featureIDs.includes(38)) { 1271 | // Small Caps Upper Case 1272 | optionsToDisableForFont.push('smallCapsUpperCase') 1273 | } 1274 | } 1275 | return optionsToDisableForFont 1276 | } 1277 | 1278 | function getSettingsForFont(font) { 1279 | let currentOptions = getDefaultUISettings() 1280 | let disableOptions = getOptionsToDisableFromFont(font) 1281 | 1282 | disableOptions.forEach(option => { 1283 | switch (option) { 1284 | case "verticalPosition": 1285 | currentOptions.verticalPosition = "disabled" 1286 | break; 1287 | case "numberSpacing": 1288 | currentOptions.numberSpacing = "disabled" 1289 | break; 1290 | case "numberCase": 1291 | currentOptions.numberCase = "disabled" 1292 | break; 1293 | case "smallCapsLowerCase": 1294 | currentOptions.smallCapsLowerCase = "disabled" 1295 | break; 1296 | case "smallCapsUpperCase": 1297 | currentOptions.smallCapsUpperCase = "disabled" 1298 | break; 1299 | case "sfSymbolSize": 1300 | currentOptions.sfSymbolSize = "disabled" 1301 | break; 1302 | } 1303 | }) 1304 | 1305 | let fontFeatureSettings = font.fontDescriptor().fontAttributes()[NSFontFeatureSettingsAttribute] 1306 | if (fontFeatureSettings) { 1307 | fontFeatureSettings.forEach(featureSetting => { 1308 | const featureTypeIdentifierKey = Number(featureSetting[NSFontFeatureTypeIdentifierKey]) 1309 | const featureSelectorIdentifierKey = Number(featureSetting[NSFontFeatureSelectorIdentifierKey]) 1310 | 1311 | switch (featureTypeIdentifierKey) { 1312 | // kVerticalPosition 1313 | case 10: 1314 | switch (featureSelectorIdentifierKey) { 1315 | // kNormalPositionSelector 1316 | case 0: 1317 | currentOptions.verticalPosition = 'default' 1318 | break; 1319 | 1320 | // kSuperiorsSelector 1321 | case 1: 1322 | currentOptions.verticalPosition = 'superscript' 1323 | break; 1324 | 1325 | // kInferiorsSelector 1326 | case 2: 1327 | currentOptions.verticalPosition = 'subscript' 1328 | break; 1329 | 1330 | // kOrdinalsSelector 1331 | case 3: 1332 | currentOptions.verticalPosition = 'ordinals' 1333 | break; 1334 | 1335 | // kScientificInferiorsSelector 1336 | case 4: 1337 | currentOptions.verticalPosition = 'scientific inferiors' 1338 | break; 1339 | 1340 | default: 1341 | logWarning("BetterTypeTool: Unknown Feature for Vertical Position") 1342 | break; 1343 | } 1344 | break; 1345 | 1346 | // kNumberSpacing 1347 | case 6: 1348 | switch(featureSelectorIdentifierKey) { 1349 | // kMonospacedNumbersSelector 1350 | case 0: 1351 | currentOptions.numberSpacing = 'monospaced' 1352 | break; 1353 | 1354 | // kProportionalNumbersSelector 1355 | case 1: 1356 | currentOptions.numberSpacing = 'proportional' 1357 | break; 1358 | 1359 | // kThirdWidthNumbersSelector 1360 | case 2: 1361 | logWarning("BetterTypeTool: Unsupported Number Spacing Feature - Third-width Numerals (Thin numerals)") 1362 | break; 1363 | 1364 | // kQuarterWidthNumbersSelector 1365 | case 3: 1366 | logWarning("BetterTypeTool: Unsupported Number Spacing Feature - Quarter-width Numerals (Very Yhin Numerals") 1367 | break; 1368 | 1369 | default: 1370 | logWarning("BetterTypeTool: Unknown feature for Number Spacing") 1371 | break; 1372 | } 1373 | break; 1374 | 1375 | // kNumberCaseType 1376 | case 21: 1377 | switch(featureSelectorIdentifierKey) { 1378 | // kLowerCaseNumbersSelector 1379 | case 0: 1380 | currentOptions.numberCase = 'oldStyle' 1381 | break; 1382 | 1383 | // kUpperCaseNumbersSelector 1384 | case 1: 1385 | currentOptions.numberCase = 'lining' 1386 | break; 1387 | 1388 | default: 1389 | logWarning("BetterTypeTool: Unknown feature for Number Case") 1390 | break; 1391 | } 1392 | break; 1393 | 1394 | // kLowerCase 1395 | case 37: 1396 | switch(featureSelectorIdentifierKey) { 1397 | // kDefaultLowerCaseSelector (aka OFF) 1398 | case 0: 1399 | currentOptions.smallCapsLowerCase = false 1400 | break; 1401 | 1402 | // kLowerCaseSmallCapsSelector 1403 | case 1: 1404 | currentOptions.smallCapsLowerCase = true 1405 | break; 1406 | 1407 | // kLowerCasePetiteCapsSelector 1408 | case 2: 1409 | logWarning("Unsupported Lower Case Small Caps Feature - Lower Case Petite Caps") 1410 | break; 1411 | 1412 | default: 1413 | logWarning("BetterTypeTool: Unknown feature for Lower Case Small Caps") 1414 | break; 1415 | } 1416 | break; 1417 | 1418 | // kUpperCase 1419 | case 38: 1420 | switch(featureSelectorIdentifierKey) { 1421 | // kDefaultUpperCaseSelector (aka OFF) 1422 | case 0: 1423 | currentOptions.smallCapsUpperCase = false 1424 | break; 1425 | 1426 | // kUpperCaseSmallCapsSelector 1427 | case 1: 1428 | currentOptions.smallCapsUpperCase = true 1429 | break; 1430 | 1431 | // kUpperCasePetiteCapsSelector 1432 | case 2: 1433 | logWarning("Unsupported Upper Case Small Caps Feature - Upper Case Petite Caps") 1434 | break; 1435 | 1436 | default: 1437 | logWarning("BetterTypeTool: Unknown feature for Upper Case Small Caps") 1438 | break; 1439 | } 1440 | break; 1441 | 1442 | 1443 | // kStylisticAlternatives 1444 | case 35: 1445 | switch(featureSelectorIdentifierKey) { 1446 | // kStylisticAltFifteenOnSelector 1447 | case 30: 1448 | currentOptions.sfSymbolSize = 'small' 1449 | break; 1450 | 1451 | // kStylisticAltFifteenOffSelector 1452 | case 31: 1453 | logWarning("WARNING: Unhandled Attempt to Set 15th Stylistic Alternative off") 1454 | break; 1455 | 1456 | // kStylisticAltSixteenOnSelector 1457 | case 32: 1458 | currentOptions.sfSymbolSize = 'large' 1459 | break; 1460 | 1461 | // kStylisticAltSixteenOffSelector 1462 | case 33: 1463 | logWarning("WARNING: Unhandled Attempt to Set 16th Stylistic Alternative off") 1464 | break; 1465 | } 1466 | break; 1467 | } 1468 | }) 1469 | } 1470 | 1471 | return currentOptions 1472 | } 1473 | --------------------------------------------------------------------------------