├── README.md └── txt2img.sketchplugin └── Contents ├── Resources └── txt2img@2x.png └── Sketch ├── manifest.json ├── plugindefaults.js └── txt2img.js /README.md: -------------------------------------------------------------------------------- 1 | # txt2img 2 | 3 | Sketch plugin to replace selected texts or shapes with images from various APIs 4 | 5 | # Usage 6 | 7 | 1. Write some text / name your shape layer 8 | 2. Hit the shortcut ```⌘``` + ```⌥``` + ```g``` 9 | 3. Boom! 10 | 11 | 12 | ![demo gif](http://wuwa.github.com/txt2image.gif "demo") 13 | 14 | 15 | > The fine print: 16 | > - Select as many layers as you wish to. If a text layer - it will be substituted with an image. If a shape layer - the shape will be filled with the image. If a group - nothing will happen... 17 | > - Works with single words or more. It really gets interesting when writing longer sentences. 18 | > - Works with SVG or BMP sources. 19 | > - Plugins > txt2img > Settings you can choose a different source for the icons / images. It should be easy enough to add some more... 20 | > - Hit the command again and again and you will get a different image every time (almost). 21 | 22 | 23 | # Installation 24 | 25 | - Download the latest release and unzip it. 26 | - Double click "txt2img.sketchplugin" 27 | 28 | # License 29 | WTFPL – Do What the Fuck You Want to Public License 30 | 31 | [![alt text](http://www.wtfpl.net/wp-content/uploads/2012/12/wtfpl-badge-4.png "WTFPL")](http://www.wtfpl.net/) 32 | 33 | 34 | -------------------------------------------------------------------------------- /txt2img.sketchplugin/Contents/Resources/txt2img@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuwa/sketch-txt-2-img/8deac114e789087eb9c3ac780e79bd318bff9aee/txt2img.sketchplugin/Contents/Resources/txt2img@2x.png -------------------------------------------------------------------------------- /txt2img.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands" : [ 3 | { 4 | "script" : "txt2img.js", 5 | "handler" : "getImage", 6 | "shortcut" : "command alt g", 7 | "name" : "Run", 8 | "identifier" : "run" 9 | }, 10 | { 11 | "script" : "txt2img.js", 12 | "handler" : "changeAPI", 13 | "name" : "Settings", 14 | "identifier" : "settings" 15 | } 16 | ], 17 | "menu" : { 18 | "title" : "txt2img", 19 | "items" : [ 20 | "run", 21 | "settings" 22 | ] 23 | }, 24 | "identifier" : "com.wuwacorp.sketch.txt2img", 25 | "version" : "0.1", 26 | "description" : "Replace selected texts or shapes with images from various APIs", 27 | "author" : "Ben Benhorin", 28 | "authorEmail" : "ben@wuwa.org", 29 | "name" : "txt2img" 30 | } 31 | 32 | -------------------------------------------------------------------------------- /txt2img.sketchplugin/Contents/Sketch/plugindefaults.js: -------------------------------------------------------------------------------- 1 | 2 | var kPluginDomain; 3 | 4 | var initDefaults = function(pluginDomain, initialValues) { 5 | kPluginDomain = pluginDomain 6 | 7 | var defaults = [[NSUserDefaults standardUserDefaults] objectForKey:kPluginDomain] 8 | var defaultValues = {} 9 | var dVal; 10 | 11 | for (var key in defaults) { 12 | defaultValues[key] = defaults[key] 13 | } 14 | 15 | for (var key in initialValues) { 16 | dVal = defaultValues[key] 17 | if (dVal == nil) defaultValues[key] = initialValues[key] 18 | } 19 | 20 | return defaultValues 21 | } 22 | 23 | var saveDefaults = function(newValues) { 24 | if (kPluginDomain) { 25 | var defaults = [NSUserDefaults standardUserDefaults] 26 | [defaults setObject: newValues forKey: kPluginDomain]; 27 | } 28 | } -------------------------------------------------------------------------------- /txt2img.sketchplugin/Contents/Sketch/txt2img.js: -------------------------------------------------------------------------------- 1 | @import 'pluginDefaults.js' 2 | 3 | var presets = { 4 | setting: 0 5 | } 6 | var userDefaults = initDefaults("com.wuwa.sketch.txt2img", presets) 7 | 8 | function changeAPI(context){ 9 | 10 | var dialog = buildDialog(context); 11 | userDefaults.setting = handleAlertResponse(dialog, dialog.runModal()); 12 | saveDefaults(userDefaults); 13 | //log(userDefaults.setting); 14 | 15 | } 16 | 17 | function createSelect (options) { 18 | 19 | var select = NSPopUpButton.alloc().initWithFrame(NSMakeRect(0, 0, 200, 28)); 20 | select.addItemsWithTitles(options); 21 | select.selectItemAtIndex(userDefaults.setting); 22 | return select; 23 | 24 | } 25 | 26 | function buildDialog(context) { 27 | var dialogWindow = COSAlertWindow.new(); 28 | 29 | dialogWindow.setMessageText('Image Source Settings'); 30 | dialogWindow.setInformativeText('Please select the API which you want to pull images from:'); 31 | 32 | var ops = ["SVG Icons 8","Giphy","Flickr"]; 33 | 34 | var apiselect = createSelect(ops); 35 | dialogWindow.addAccessoryView(apiselect); 36 | 37 | dialogWindow.addButtonWithTitle('OK'); 38 | dialogWindow.addButtonWithTitle('Cancel'); 39 | 40 | dialogWindow.setIcon(NSImage.alloc().initByReferencingFile(context.plugin.urlForResourceNamed("txt2img@2x.png").path())); 41 | 42 | return dialogWindow; 43 | } 44 | 45 | function handleAlertResponse (dialog, responseCode) { 46 | if (responseCode == "1000") { 47 | return dialog.viewAtIndex(0).indexOfSelectedItem(); 48 | } else { 49 | return dialog.viewAtIndex(0).indexOfSelectedItem(); 50 | // crashes if null on cancel? 51 | //return null; 52 | } 53 | } 54 | 55 | 56 | function getImage(context) { 57 | 58 | // var doc = context.document; 59 | // var selection = context.selection; 60 | switch(parseInt(userDefaults.setting)) { 61 | case 0: 62 | log("icon8"); 63 | setSVG(context); 64 | break; 65 | case 1: 66 | log("giphy"); 67 | setImage(context); 68 | break; 69 | case 2: 70 | log("flickr"); 71 | setImage(context); 72 | break; 73 | default: 74 | log("none"); 75 | } 76 | 77 | } // end main 78 | 79 | function apiCall(url, setting, context) { 80 | 81 | switch(setting) { 82 | case 0: 83 | var queryURL = 'https://api.icons8.com/api/iconsets/v3/search?term='+ encodeURI(url) +'&amount=3'; 84 | break; 85 | case 1: 86 | var queryURL = 'https://api.giphy.com/v1/gifs/search?q=' + encodeURI(url) + '&api_key=dc6zaTOxFJmzC&limit=10'; 87 | break; 88 | case 2: 89 | var queryURL = "https://api.flickr.com/services/feeds/photos_public.gne?format=json&nojsoncallback=1&tags=" + encodeURI(url); 90 | break; 91 | default: 92 | log("none"); 93 | } 94 | 95 | var request = NSMutableURLRequest.new(); 96 | [request setHTTPMethod:@"GET"]; 97 | [request setURL:[NSURL URLWithString:queryURL]]; 98 | 99 | var error = NSError.new(); 100 | var responseCode = null; 101 | 102 | var oResponseData = [NSURLConnection sendSynchronousRequest:request returningResponse:responseCode error:error]; 103 | var dataString = [[NSString alloc] initWithData:oResponseData encoding:NSUTF8StringEncoding]; 104 | 105 | //var pattern = new RegExp("\\\\'", "g"); 106 | //var validJSONString = dataString.replace(pattern, "'"); 107 | //var images = JSON.parse(validJSONString); 108 | 109 | var images = JSON.parse(dataString); 110 | 111 | //log(images); 112 | 113 | try { 114 | 115 | switch(setting) { 116 | case 0: 117 | var imageUrl = images.result.search[0].svg; 118 | //var imageUrl = images.result.search[Math.floor(Math.random() * images.result.search.length)].svg; 119 | break; 120 | case 1: 121 | var imageUrl = images.data[Math.floor(Math.random() * images.data.length)].images.downsized_still.url; 122 | break; 123 | case 2: 124 | var imageUrl = images.items[Math.floor(Math.random() * images.items.length)].media.m; 125 | imageUrl = imageUrl.replace("_m", "_b"); 126 | break; 127 | default: 128 | log("none"); 129 | return; 130 | } 131 | return imageUrl; 132 | 133 | } 134 | catch(err) { 135 | var doc = context.document; 136 | var errMessage = "Unfortunately, the API did not send back any results for " + url; 137 | [doc showMessage:errMessage]; 138 | } 139 | 140 | } 141 | 142 | 143 | 144 | function setImage(context){ 145 | 146 | var doc = context.document; 147 | var selection = context.selection; 148 | 149 | if (selection.length > 0) { 150 | 151 | for (var i = 0; i < [selection count]; i++) { 152 | var layer = selection[i]; 153 | 154 | var imageurl = apiCall(layer.name(),parseInt(userDefaults.setting),context); 155 | 156 | var imageurl_nsurl = NSURL.alloc().initWithString(imageurl); 157 | var imageData = NSImage.alloc().initByReferencingURL(imageurl_nsurl); 158 | 159 | if ([layer class] == MSTextLayer) { 160 | 161 | var rectangle = MSShapeGroup.shapeWithRect({origin:{x:layer.frame().x(), y:layer.frame().y()-100}, size:{width:200, height:200}}); 162 | var fill = rectangle.style().addStylePartOfType(0); 163 | fill.color = MSColor.colorWithRed_green_blue_alpha(0, 0, 0, 1); 164 | context.document.currentPage().currentArtboard().addLayer(rectangle); 165 | rectangle.setName(layer.name()); 166 | 167 | layerStyle = rectangle.style().fills().firstObject(); 168 | layerStyle.isEnabled = true; 169 | layerStyle.setFillType(4); 170 | layerStyle.patternFillType = 1; 171 | 172 | /* ////// old sketch versions 173 | if (MSApplicationMetadata.metadata().appVersion < 47) { 174 | layerStyle.setImage(MSImageData.alloc().initWithImage_convertColorSpace(imageData, false)); 175 | } else { 176 | layerStyle.setImage(MSImageData.alloc().initWithImage(imageData)); 177 | } 178 | */ 179 | 180 | layerStyle.setImage(MSImageData.alloc().initWithImage(imageData)); 181 | 182 | layer.removeFromParent(); 183 | 184 | } else if ([layer class] == MSShapeGroup) { 185 | 186 | layerStyle = layer.style().fills().firstObject(); 187 | layerStyle.isEnabled = true; 188 | layerStyle.setFillType(4); 189 | layerStyle.patternFillType = 1; 190 | layerStyle.setImage(MSImageData.alloc().initWithImage(imageData)); 191 | 192 | 193 | } //end if 194 | 195 | } // end loop of layers 196 | 197 | } else { 198 | [doc showMessage:"Please select at least one TEXT or SHAPE layer."]; 199 | } //end no selection 200 | 201 | } 202 | 203 | function setSVG(context){ 204 | 205 | var doc = context.document; 206 | var selection = context.selection; 207 | if (selection.length > 0) { 208 | 209 | for (var i = 0; i < [selection count]; i++) { 210 | var layer = selection[i]; 211 | 212 | var imageurl = apiCall(layer.name(),parseInt(userDefaults.setting),context); 213 | 214 | //var imageurl_nsurl = NSURL.alloc().initWithString(imageurl); 215 | //var imageData = NSImage.alloc().initByReferencingURL(imageurl_nsurl); 216 | 217 | if ([layer class] == MSTextLayer) { 218 | 219 | var rectangle = MSShapeGroup.shapeWithRect({origin:{x:layer.frame().x(), y:layer.frame().y()-25}, size:{width:50, height:50}}); 220 | //var fill = rectangle.style().addStylePartOfType(0); 221 | //fill.color = MSColor.colorWithRed_green_blue_alpha(0, 0, 0, 1); 222 | //context.document.currentPage().currentArtboard().addLayer(rectangle); 223 | //rectangle.setName(layer.name()); 224 | 225 | var selectedFrame = rectangle.frame(); 226 | //rectangle.removeFromParent(); 227 | 228 | } else if ([layer class] == MSShapeGroup) { 229 | 230 | var selectedFrame = layer.frame(); 231 | 232 | } //end if 233 | 234 | var svgdata = [[NSString stringWithString:imageurl] dataUsingEncoding:4]; 235 | 236 | var svgImporter = MSSVGImporter.svgImporter(); 237 | svgImporter.prepareToImportFromData(svgdata); 238 | var importedSVGLayer = svgImporter.importAsLayer(); 239 | importedSVGLayer.name = layer.name(); 240 | 241 | 242 | // Scale SVG to selection frame 243 | var svgFrame = importedSVGLayer.frame(); 244 | var ratio = svgFrame.width() / svgFrame.height(); 245 | var newWidth = selectedFrame.width(); 246 | var newHeight = newWidth / ratio; 247 | if (newHeight > selectedFrame.height()) { 248 | newHeight = selectedFrame.height(); 249 | newWidth = newHeight * ratio; 250 | } 251 | 252 | // Center in selection frame 253 | [svgFrame setX:selectedFrame.x() + selectedFrame.width() / 2 - newWidth / 2]; 254 | [svgFrame setY:selectedFrame.y() + selectedFrame.height() / 2 - newHeight / 2]; 255 | [svgFrame setWidth:newWidth]; 256 | [svgFrame setHeight:newHeight]; 257 | 258 | // Add label layer 259 | var page = doc.currentPage(); 260 | var canvas = page.currentArtboard() || page; 261 | canvas.addLayers([importedSVGLayer]); 262 | 263 | // get rid of useless groups inside the svg 264 | //unrollGroupsInLayer(importedSVGLayer); 265 | 266 | //log(importedSVGLayer.layers().firstObject().name) 267 | importedSVGLayer.layers().firstObject().name = layer.name(); 268 | importedSVGLayer.ungroup(); 269 | 270 | // Remove selection frame 271 | layer.removeFromParent(); 272 | 273 | } // end loop of layers 274 | } else { 275 | [doc showMessage:"Please select at least one text or shape layer."]; 276 | } //end no selection 277 | 278 | 279 | } // end setSVG 280 | 281 | 282 | function unrollGroupsInLayer(layer){ 283 | log("unrollGroupsInLayer: " + layer); 284 | 285 | if (layer.className() == "MSLayerGroup" || layer.className() == "MSArtboardGroup") { 286 | if(layer.layers().count() == 0) { 287 | layer.removeFromParent() 288 | } 289 | if(layer.layers().count() == 1 && layer.layers().firstObject().className() == "MSLayerGroup") { 290 | // Group contains just another group, so let's call ourselves again 291 | var newLayer = layer.ungroup() 292 | unrollGroupsInLayer(newLayer.firstObject()) 293 | } else { 294 | var layers = layer.layers() 295 | for (var i=0; i < [layers count]; i++) { 296 | var layer = [layers objectAtIndex:i] 297 | if(layer.className() == "MSLayerGroup"){ 298 | unrollGroupsInLayer(layer) 299 | } 300 | } 301 | } 302 | } 303 | } 304 | 305 | --------------------------------------------------------------------------------