├── .DS_Store ├── .gitignore ├── README.md ├── LICENSE.txt └── Text to Specs.sketchplugin /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kenmoore/sketch-text-to-specs/HEAD/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Do not track any sketch files or screenflow files: 2 | *.sketch 3 | *.screenflow 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Text to Specs plugin for Sketch 2 | Convert a text layer into a styled annotation overlay, grouped with a styled rectangle background. 3 | 4 | Here's a [quick video demo](https://www.youtube.com/watch?v=gntOFEGyCVk). 5 | 6 | Creating an Annotation 7 | Simply select a text layer and press Cmd+Ctrl+T. 8 | 9 | Re-fitting the Background 10 | If you alter the text, you can re-fit the background rectangle by re-invoking the plugin. 11 | 12 | PRO TIP: Since the group layer name begins with "specs", you can use the [Toggle Specs 13 | plugin](https://github.com/hrescak/sketchplugins) to quickly toggle all annotations off and on. 14 | 15 | ## Installation 16 | 1. [Download Text to Specs.sketchplugin.](https://github.com/kenmoore/sketch-text-to-specs/archive/master.zip) 17 | 2. Unzip the archive 18 | 3. Double-click the .sketchplugin file to install it 19 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ken Moore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Text to Specs.sketchplugin: -------------------------------------------------------------------------------- 1 | // Text to Specs Plugin (cmd ctrl t) 2 | /* 3 | Author: Ken Moore 4 | 5 | The Text to Specs plugin lets you easily turn a text layer into a 'specs' layer (grouped 6 | with a background bounding rectangle w/ 15px padding). If you later edit the text, you 7 | can invoke the plugin again to re-fit the background layer. 8 | 9 | Since the text and background use shared styles, it's easy to change the style of all 10 | annotations to suit your preference. 11 | 12 | PRO TIP: Since the group layer name begins with "specs", you can use the Toggle Specs 13 | plugin to quickly toggle all annotations off and on. 14 | (https://github.com/hrescak/sketchplugins) 15 | */ 16 | 17 | 18 | var textLayer = nil; 19 | var rectLayer = nil; 20 | var groupLayer = nil; 21 | 22 | // Display an error alert 23 | function alert(msg, title) { 24 | title = title || "alert"; 25 | var app = [NSApplication sharedApplication]; 26 | [app displayDialog:msg withTitle:title]; 27 | } 28 | 29 | // Determine if a layer is an artboard 30 | function isArtboard(layer) { 31 | var artboards = [[doc currentPage] artboards]; 32 | for (var i = 0; i < [artboards length]; i++) { 33 | if ([artboards objectAtIndex: i] == layer) { 34 | return true; 35 | } 36 | } 37 | 38 | return false; 39 | } 40 | 41 | // Display an error alert and exit 42 | function invalidSelection(details) { 43 | text = 'Select the text layer you wish to convert to a spec, or the group layer you wish to re-layout.'; 44 | if (details) text += ' [' + details + ']'; 45 | alert(text, 'Select text layer'); 46 | throw(nil); // exit the plugin 47 | } 48 | 49 | // Validate selection, and assign proper layers to textLayer, rectLayer and groupLayer 50 | function initSelection() { 51 | // Ensure there's only one layer selected 52 | if ([selection count] != 1) { 53 | invalidSelection('One layer must be selected'); 54 | } 55 | 56 | layer = [selection objectAtIndex:0]; 57 | 58 | // Assign / create layers to achieve group layer containing text + background layers 59 | if (layer instanceof MSLayerGroup) { 60 | groupLayer = layer; 61 | 62 | // If group is selected, only consider it valid if it has a text layer with a rect layer below 63 | children = [groupLayer layers]; 64 | if ([children count] == 2) { 65 | textLayer = [children lastObject]; 66 | rectLayer = [children firstObject]; 67 | 68 | if (!(textLayer instanceof MSTextLayer) || rectLayer instanceof MSTextLayer) { 69 | invalidSelection('Group must contain a text layer with a rectangle layer below'); 70 | } 71 | } else { 72 | invalidSelection('Group must contain only a text layer with a rectangle layer below'); 73 | } 74 | } else if (layer instanceof MSTextLayer) { 75 | textLayer = layer; 76 | 77 | // If already inside a group, use that group, otherwise create a new one with rect layer 78 | groupLayer = [textLayer parentGroup]; 79 | 80 | if (groupLayer && !(groupLayer instanceof MSPage) && !isArtboard(groupLayer)) { 81 | children = [groupLayer layers]; 82 | if ([children count] == 2) { 83 | rectLayer = [children firstObject]; 84 | } else { 85 | invalidSelection('Group must contain only a text layer with a rectangle layer below'); 86 | } 87 | } else { 88 | // create the group layer 89 | groupLayer = MSLayerGroup.new(); 90 | parent = textLayer.parentGroup(); 91 | parent.addLayers([groupLayer]); 92 | 93 | // create the background layer 94 | var rectShape = MSRectangleShape.alloc().init(); 95 | rectShape.frame = MSRect.rectWithRect(NSMakeRect(0, 0, 100, 100)); 96 | rectLayer = [MSShapeGroup shapeWithPath: rectShape]; 97 | rectLayer.name = 'spec background'; 98 | groupLayer.addLayers([rectLayer]); 99 | 100 | // insert textLayer into group 101 | parent.removeLayer(textLayer); 102 | groupLayer.addLayers([textLayer]); 103 | } 104 | } else { 105 | invalidSelection(); 106 | } 107 | } 108 | 109 | 110 | // Create shared style for specs text if it doesn't exist yet 111 | function initTextSharedStyle() { 112 | var textStyles = doc.documentData().layerTextStyles(); 113 | var textStylesLibrary = textStyles.objectsSortedByName(); 114 | 115 | // loop through existing text styles, if "Specs text" already exists apply it and return 116 | for (var i = 0; i < [textStylesLibrary length]; i++) { 117 | if (textStylesLibrary[i].name() == 'Specs text') { 118 | [textLayer finishEditing]; 119 | [textLayer setIsEditingText:false]; 120 | print('just set editing false'); 121 | textLayer.setStyle(textStylesLibrary[i].newInstance()); 122 | return; 123 | } 124 | } 125 | 126 | // "Specs text" style doesn't exist, create it from current layer 127 | // But first, set the current text color to mid gray 128 | var color = MSColor.colorWithSVGString("#6C6C6C"); 129 | [textLayer setTextColor: color]; 130 | textStyles.addSharedStyleWithName_firstInstance("Specs text", textLayer.style()); 131 | } 132 | 133 | // Restore the text style (color, shadow) to what it was prior to invoking the plugin 134 | function restoreTextStyle() { 135 | 136 | } 137 | 138 | // Create shared style for specs background rect if it doesn't exist yet 139 | function initRectSharedStyle() { 140 | var layerStyles = doc.documentData().layerStyles(); 141 | var layerStylesLibrary = layerStyles.objectsSortedByName(); 142 | 143 | // loop through existing styles, if "Specs background" already exists apply it and return 144 | for (var i = 0; i < [layerStylesLibrary length]; i++) { 145 | if (layerStylesLibrary[i].name() == 'Specs background') { 146 | rectLayer.setStyle(layerStylesLibrary[i].newInstance()); 147 | return; 148 | } 149 | } 150 | 151 | // "Specs background" style doesn't exist, create it 152 | var style = MSStyle.alloc().init(); 153 | 154 | // Setup fill 155 | var fill = style.fills().addNewStylePart(); 156 | fill.color = MSColor.colorWithSVGString("#FFFCDC"); 157 | 158 | // Setup stroke 159 | var border = style.borders().addNewStylePart(); 160 | border.color = MSColor.colorWithSVGString("#CCCCCC"); 161 | border.thickness = 1; 162 | 163 | // Setup shadow with default settings. 164 | var shadow = style.shadows().addNewStylePart(); 165 | shadow.offsetX = 0; 166 | shadow.offsetY = 1; 167 | shadow.blurRadius = 0; 168 | shadow.spread = 0; 169 | var shadowColor = MSColor.colorWithSVGString("#000000"); 170 | shadowColor.alpha = 0.2; 171 | shadow.color = shadowColor; 172 | 173 | // Add style to the container of shared styles. 174 | layerStyles.addSharedStyleWithName_firstInstance("Specs background", style); 175 | 176 | // Apply the style to rectLayer 177 | [rectLayer setStyle: style]; 178 | } 179 | 180 | function resizeBackground(padding) { 181 | var textFrame = [textLayer frame]; 182 | var rectFrame = [rectLayer frame]; 183 | [rectFrame setX: [textFrame x] - padding]; 184 | [rectFrame setY: [textFrame y] - padding]; 185 | [rectFrame setWidth: [textFrame width] + padding * 2]; 186 | [rectFrame setHeight: [textFrame height] + padding * 2]; 187 | groupLayer.resizeRoot(true); // resize the group layer to match new contents 188 | } 189 | 190 | 191 | // MAIN 192 | 193 | // validate selection, and assign proper layers to textLayer, rectLayer and groupLayer 194 | initSelection(); 195 | 196 | // Create shared style for specs text if it doesn't exist yet 197 | initTextSharedStyle(); 198 | 199 | // Create shared style for specs background rect if it doesn't exist yet 200 | initRectSharedStyle(); 201 | 202 | // Set the name of the group layer to 'specs' so it can be targeted with toggle visibility plugin 203 | [groupLayer setName: "specs: " + textLayer.stringValue().substr(0,20) + (textLayer.stringValue().length() > 20 ? '...' : '')]; 204 | 205 | // Resize the rect to wrap the text, specifying px of padding 206 | resizeBackground(15); 207 | 208 | // Select the group layer 209 | textLayer.setIsSelected(false); 210 | groupLayer.setIsSelected(true); 211 | 212 | // Restore the prior text style 213 | restoreTextStyle(); 214 | --------------------------------------------------------------------------------