DarkModeGenerator_V2.jsx (550 lines of code) (raw):

/* Description: Script for generating dark mode artboards for Guardian graphics Requirements: Adobe Illustrator CC and later Date: March, 2022 Author: The Guardian, Garry Blight Based on Duplicate_Artboards_Light.jsx for Adobe Illustrator Description: Script for copying the selected Artboard with his artwork Requirements: Adobe Illustrator CS6 and later Date: October, 2020 Author: Sergey Osokin, email: hi@sergosokin.ru */ var i, artboardOriginal, artboardOriginalRect, artboardCopy, artboardCopyRect, abHeight; var doc = app.activeDocument; var artboards = doc.artboards; var spacing = 300; var suffix = "_dark-mode"; var abLength = artboards.length; var mode = 0; var colourMode = 0; var invertWhiteText = false; var opacityBoost = 1.6; var darkModeBaseVal = 26; // #1A1A1A rgb(26, 26, 26) dark mode background var skippedColors = []; var darkArtboardsTotal = 0; var neutralThreshold = 13; //was 6 // The maximum variation in r, g and b values that comprises a neutral (This figure may need to be adjusted upwards) var guardianNeutralsMap = []; var guardianColoursMap = []; var guardianColoursLookup = {}; guardianNeutralsMap.push( { light: "#ffffff", dark: "#1a1a1a" } ); // White to dark mode background colour // guardianNeutralsMap.push( { light: "#f6f6f6", dark: "#333333" } ); // guardianNeutralsMap.push( { light: "#efefef", dark: "#565656" } ); // 565656 NOT GUARDIAN COLOUR BUT NEEDS THIS DARKER TONE!!!!! // guardianNeutralsMap.push( { light: "#eaeaea", dark: "#565656" } ); // 565656 NOT GUARDIAN COLOUR BUT NEEDS THIS DARKER TONE!!!!! // guardianNeutralsMap.push( { light: "#dcdcdc", dark: "#767676" } ); // guardianNeutralsMap.push( { light: "#dadada", dark: "#767676" } ); // guardianNeutralsMap.push( { light: "#b3b3b4", dark: "#767676" } ); // guardianNeutralsMap.push( { light: "#999999", dark: "#999999" } ); // guardianNeutralsMap.push( { light: "#929297", dark: "#999999" } ); // guardianNeutralsMap.push( { light: "#767676", dark: "#dcdcdc" } ); // guardianNeutralsMap.push( { light: "#676767", dark: "#dcdcdc" } ); // guardianNeutralsMap.push( { light: "#333333", dark: "#eaeaea" } ); // guardianNeutralsMap.push( { light: "#1d1d1b", dark: "#f6f6f6" } ); // Dark text/objects currently not made pure white // guardianNeutralsMap.push( { light: "#1a1a1a", dark: "#f6f6f6" } ); // Dark text/objects currently not made pure white // guardianNeutralsMap.push( { light: "#121212", dark: "#f6f6f6" } ); // Dark text/objects currently not made pure white // guardianNeutralsMap.push( { light: "#000000", dark: "#ffffff" } ); guardianNeutralsMap.push( { light: "#f3f3f3", dark: "#383838" } ); guardianNeutralsMap.push( { light: "#dcdcdc", dark: "#494949" } ); guardianNeutralsMap.push( { light: "#bababa", dark: "#707070" } ); guardianNeutralsMap.push( { light: "#a1a1a1", dark: "#a1a1a1" } ); guardianNeutralsMap.push( { light: "#707070", dark: "#c8c8c8" } ); guardianNeutralsMap.push( { light: "#333333", dark: "#c8c8c8" } ); guardianNeutralsMap.push( { light: "#121212", dark: "#dcdcdc" } ); guardianNeutralsMap.push( { light: "#1a1a1a", dark: "#dcdcdc" } ); guardianNeutralsMap.push( { light: "#000000", dark: "#ffffff" } ); // analysis colours guardianNeutralsMap.push( { light: "#a19a99", dark: "#a1a1a1" } ); guardianNeutralsMap.push( { light: "#bab2b1", dark: "#707070" } ); guardianNeutralsMap.push( { light: "#dcd3d1", dark: "#494949" } ); guardianNeutralsMap.push( { light: "#f3e8e7", dark: "#383838" } ); guardianNeutralsMap.push( { light: "#fff4f2", dark: "#1a1a1a" } ); // Analysis pink to dark mode background colour guardianColoursMap.push( { type: "news", light: "#0094DA", dark: "#009CE3" } ); guardianColoursMap.push( { type: "news", light: "#C70000", dark: "#CE0E09" } ); guardianColoursMap.push( { type: "news", light: "#23B4A9", dark: "#35BBB1" } ); guardianColoursMap.push( { type: "news", light: "#A1A1A1", dark: "#A1A1A1" } ); guardianColoursMap.push( { type: "news", light: "#CBA36E", dark: "#CBA36E" } ); guardianColoursMap.push( { type: "news", light: "#F678BB", dark: "#F678BB" } ); guardianColoursMap.push( { type: "news", light: "#FF7F0F", dark: "#FF8B25" } ); guardianColoursMap.push( { type: "news", light: "#005689", dark: "#00669D" } ); guardianColoursMap.push( { type: "news", light: "#8B0000", dark: "#9A0000" } ); guardianColoursMap.push( { type: "news", light: "#0C7A73", dark: "#128981" } ); guardianColoursMap.push( { type: "news", light: "#494949", dark: "#5F5F5F" } ); guardianColoursMap.push( { type: "news", light: "#866D50", dark: "#866D50" } ); guardianColoursMap.push( { type: "news", light: "#9C2274", dark: "#9C2274" } ); guardianColoursMap.push( { type: "news", light: "#C74600", dark: "#C74600" } ); guardianColoursMap.push( { type: "news", light: "#D4EDFF", dark: "#244057" } ); guardianColoursMap.push( { type: "news", light: "#FFDBD4", dark: "#5f2116" } ); guardianColoursMap.push( { type: "news", light: "#D9F2EF", dark: "#254a46" } ); guardianColoursMap.push( { type: "news", light: "#E7E7E7", dark: "#383838" } ); guardianColoursMap.push( { type: "news", light: "#F7EBDC", dark: "#493D30" } ); guardianColoursMap.push( { type: "news", light: "#FFE6F4", dark: "#6b2251" } ); guardianColoursMap.push( { type: "news", light: "#FFE2CD", dark: "#64381a" } ); guardianColoursMap.push( { type: "sentiment", light: "#004E7C", dark: "#004E7C" } ); guardianColoursMap.push( { type: "sentiment", light: "#0077B6", dark: "#0077B6" } ); guardianColoursMap.push( { type: "sentiment", light: "#00B2FF", dark: "#00B2FF" } ); guardianColoursMap.push( { type: "sentiment", light: "#FF5943", dark: "#FF5943" } ); guardianColoursMap.push( { type: "sentiment", light: "#C70000", dark: "#C70000" } ); guardianColoursMap.push( { type: "sentiment", light: "#8B0000", dark: "#8B0000" } ); guardianColoursMap.push( { type: "politics", party: "Lab", light: "#C70000", dark: "#e33824" } ); guardianColoursMap.push( { type: "politics", party: "Con", light: "#0077B6", dark: "#009ae1" } ); guardianColoursMap.push( { type: "politics", party: "Lib_Dem", light: "#FF7F0F", dark: "#FF7F0F" } ); guardianColoursMap.push( { type: "politics", party: "Reform", light: "#3DBBE2", dark: "#3DBBE2" } ); guardianColoursMap.push( { type: "politics", party: "Green", light: "#39A566", dark: "#39A566" } ); guardianColoursMap.push( { type: "politics", party: "SNP", light: "#F5DC00", dark: "#F5DC00" } ); guardianColoursMap.push( { type: "politics", party: "Other", light: "#848484", dark: "#707070" } ); guardianColoursMap.push( { type: "structure", light: "#121212", dark: "#DCDCDC" } ); guardianColoursMap.push( { type: "structure", light: "#707070", dark: "#C8C8C8" } ); guardianColoursMap.push( { type: "structure", light: "#BABABA", dark: "#707070" } ); guardianColoursMap.push( { type: "structure", light: "#DCDCDC", dark: "#494949" } ); guardianColoursMap.push( { type: "structure", light: "#F3F3F3", dark: "#383838" } ); // additional guardianColoursMap.push( { type: "structure", light: "#FFFFFF", dark: "#1a1a1a" } ); var guardianNeutralsInversion = {}; for (var i = 0; i < guardianNeutralsMap.length; i++) { var lightColRGB = hexToRgb(guardianNeutralsMap[i].light); var darkColRGB = hexToRgb(guardianNeutralsMap[i].dark); var key = String(lightColRGB.r); guardianNeutralsInversion[key] = { hex: guardianNeutralsMap[i].dark, rgb: darkColRGB }; } var nearestGuardianNeutralArray = []; // used for finding nearest guardian neutral for non-Guardian neutrals for (var key in guardianNeutralsInversion) { if (guardianNeutralsInversion.hasOwnProperty(key)) { //console.log(key + " -> " + p[key]); nearestGuardianNeutralArray.push(+key); //guardianNeutralsArray.push( { hex: key, rgb: hexToRgb(key)} ); // Change to this } } // Main Window var dialog = new Window('dialog', "Dark mode artboards generator V2"); dialog.orientation = 'column'; dialog.alignChildren = ['fill', 'center']; // Input fields var abGroup = dialog.add('group'); abGroup.orientation = 'column'; abGroup.alignChildren = ['fill', 'top']; // abGroup.add('statictext', undefined, "Neutrals/structure conversion mode"); // var modeList = abGroup.add('dropdownlist', [0, 0, 280, 30], ["1. Guardian graphics neutrals (Strict)", "2. Guardian graphics neutrals (Nearest)", "3. Invert - lighten darks", "4. Invert - lighten darks +", "5. Invert - lighten darks ++", "6. Invert - lighten darks +++"]); // modeList.selection = 0; abGroup.add('statictext', undefined, "Colour conversion mode"); var colourModeList = abGroup.add('dropdownlist', [0, 0, 280, 30], ["1. Structure (neutrals only)", "2. Categorical: news", "3. Categorical: politics", "4. Categorical: sentiment"]); colourModeList.selection = 0; var inputsGroup = dialog.add('group'); inputsGroup.orientation = 'row'; inputsGroup.add('statictext', undefined, "Y offset"); var spacingVal = inputsGroup.add('edittext', [0, 0, 60, 30], spacing); inputsGroup.add('statictext', undefined, "Opacity multiplier"); var opacityVal = inputsGroup.add('edittext', [0, 0, 60, 30], opacityBoost); // var invertWhiteTextVal = inputsGroup.add('checkbox', undefined, 'Invert white text'); // invertWhiteTextVal.value = false; // Buttons var btnsGroup = dialog.add('group'); btnsGroup.orientation = 'row'; btnsGroup.alignChildren = ['fill', 'center']; var cancel = btnsGroup.add('button', undefined, "Cancel", { name: 'cancel' }); var ok = btnsGroup.add('button', undefined, "OK", { name: 'ok' }); // Change listeners // modeList.onChange = function() { // mode = modeList.selection.index; // } colourModeList.onChange = function() { colourMode = colourModeList.selection.index; } spacingVal.onChange = function () { this.text = convertToNum(this.text, spacing); spacing = +this.text; } opacityVal.onChange = function () { this.text = convertToNum(this.text, opacityBoost); opacityBoost = +this.text; } cancel.onClick = dialog.close; ok.onClick = okClick; dialog.center(); dialog.show(); function okClick() { //invertWhiteText = invertWhiteTextVal.value; dialog.close(); getColours(); generate(); } function convertToNum(str, def) { // Remove unnecessary characters str = str.replace(/,/g, '.').replace(/[^\d.]/g, ''); // Remove duplicate Point str = str.split('.'); str = str[0] ? str[0] + '.' + str.slice(1).join('') : ''; if (isNaN(str) || str.length == 0) return parseFloat(def); return parseFloat(str); } function getColours() { for (var i = 0; i < guardianColoursMap.length; i++) { var key = String(guardianColoursMap[i].light).toLowerCase(); var val = String(guardianColoursMap[i].dark).toLowerCase(); if (guardianColoursMap[i].type == "structure") { guardianColoursLookup[key] = val; } if (colourMode == 1 && guardianColoursMap[i].type == "news") { guardianColoursLookup[key] = val; } if (colourMode == 2 && guardianColoursMap[i].type == "politics") { guardianColoursLookup[key] = val; } if (colourMode == 3 && guardianColoursMap[i].type == "sentiment") { guardianColoursLookup[key] = val; } } } // Main wrapper function function generate() { //alert("Mode=" + mode + " Y offset=" + spacing + " Invert white text=" + invertWhiteText); selection = null; unlockLayers(doc.layers); saveItemsState(doc.layers, '%isLocked', '%isHidden'); for (i = 0; i < abLength; i++) { doc.artboards.setActiveArtboardIndex(i); // Copy Artwork doc.selectObjectsOnActiveArtboard(); var abItems = selection; duplicateArtboard(i, abItems); darkArtboardsTotal++; } restoreItemsState(doc.layers, '%isLocked', '%isHidden'); selection = null; showCompletionAlert(); function showCompletionAlert() { var rule = "\n================\n"; var alertText, alertHed; alertHed = "Dark Mode Generator has generated " + darkArtboardsTotal + " dark mode artboards!"; alertText = "\n"; alertText += skippedColors.length + " neutral colours skipped:\n"; for (var i = 0; i < skippedColors.length; i++) { alertText += skippedColors[i]; if (i != skippedColors.length-1) { alertText += ", "; } } //alertText = makeList(errors, "Error", "Errors"); //alertText += makeList(warnings, "Warning", "Warnings"); //alertText += makeList(feedback, "Information", "Information"); alertText += "\n"; alert(alertHed + alertText); } /** * Unlock all Layers & Sublayers * @param {object} _layers - the collection of layers */ function unlockLayers(_layers) { for (var i = 0, len = _layers.length; i < len; i++) { if (_layers[i].locked) _layers[i].locked = false; if (_layers[i].layers.length) unlockLayers(_layers[i].layers); } } /** * Collect items * @param {object} obj - collection of items * @param {array} arr - output array with childrens */ function getItems(obj, arr) { for (var i = 0, len = obj.length; i < len; i++) { var currItem = obj[i]; try { switch (currItem.typename) { case 'GroupItem': arr.push(currItem); getItems(currItem.pageItems, arr); break; default: arr.push(currItem); break; } } catch (e) {} } } /** * Save information about locked & hidden pageItems & layers * @param {object} _layers - the collection of layers * @param {string} lKey - keyword for locked items * @param {string} hKey - keyword for hidden items */ function saveItemsState(_layers, lKey, hKey) { var allItems = []; for (var i = 0, len = _layers.length; i < len; i++) { var currLayer = _layers[i]; if (currLayer.layers.length > 0) { saveItemsState(currLayer.layers, lKey, hKey); } getItems(currLayer.pageItems, allItems); for (var j = 0, iLen = allItems.length; j < iLen; j++) { var currItem = allItems[j]; if (currItem.locked) { currItem.locked = false; currItem.note += lKey; } if (currItem.hidden) { currItem.hidden = false; currItem.note += hKey; } } } redraw(); } /** * Restoring locked & hidden pageItems & layers * @param {object} _layers - the collection of layers * @param {string} lKey - keyword for locked items * @param {string} hKey - keyword for hidden items */ function restoreItemsState(_layers, lKey, hKey) { var allItems = [], regexp = new RegExp(lKey + '|' + hKey, 'gi'); for (var i = 0, len = _layers.length; i < len; i++) { var currLayer = _layers[i]; if (currLayer.layers.length > 0) { restoreItemsState(currLayer.layers, lKey, hKey); } getItems(currLayer.pageItems, allItems); for (var j = 0, iLen = allItems.length; j < iLen; j++) { var currItem = allItems[j]; if (currItem.note.match(lKey) != null) { currItem.note = currItem.note.replace(regexp, ''); currItem.locked = true; } if (currItem.note.match(hKey) != null) { currItem.note = currItem.note.replace(regexp, ''); currItem.hidden = true; } } } } /** * Duplicate the selected artboard. Based on the idea of @Silly-V * @param {number} i - current artboard index * @param {object} items - collection of items on the artboard * @param {string} suffix - copy name suffix * */ function duplicateArtboard(i, items) { var thisAb = doc.artboards[i], thisAbRect = thisAb.artboardRect, abHeight = thisAbRect[1] - thisAbRect[3]; //doc.artboards.setActiveArtboardIndex(i); var newAb = doc.artboards.add(thisAbRect); newAb.artboardRect = [ thisAbRect[0], thisAbRect[1] - spacing - abHeight, thisAbRect[2], thisAbRect[3] - spacing - abHeight ] // add detect code for steps var additionalSuffix = ""; var splitNameArray = thisAb.name.split("_"); // or should this be a space ? var splitNameIndex = +splitNameArray[splitNameArray.length - 1]; if (typeof (splitNameIndex) == 'number' && splitNameIndex < 300) { // assume is a step for stepper type graphic (has to be less than 300 because of mobile artboard naming convention) additionalSuffix = " " + splitNameIndex; } newAb.name = thisAb.name + suffix + additionalSuffix; // Create dark mode background rectangle //doc.activeLayer = doc.layers[doc.layers.length-1]; // select base layer var top=newAb.artboardRect[1] + 1; var left=newAb.artboardRect[0] - 1; var width=newAb.artboardRect[2]-newAb.artboardRect[0] + 2; var height=newAb.artboardRect[1]-newAb.artboardRect[3] + 2; var rect = doc.pathItems.rectangle (top, left, width, height); var backgroundColor = new RGBColor(); backgroundColor.red = darkModeBaseVal; // #1A1A1A dark mode background backgroundColor.green = darkModeBaseVal; backgroundColor.blue = darkModeBaseVal; rect.fillColor = backgroundColor; rect.strokeColor = new NoColor(); var layer = doc.layers[doc.layers.length-1]; // rect.move(layer, ElementPlacement.INSIDE ); rect.move(layer, ElementPlacement.PLACEATEND ); var docCoordSystem = CoordinateSystem.DOCUMENTCOORDINATESYSTEM, abCoordSystem = CoordinateSystem.ARTBOARDCOORDINATESYSTEM, isDocCoords = app.coordinateSystem == docCoordSystem, dupArr = getDuplicates(items); dupArr = makeDarkMode2(dupArr); // Move copied items to the new artboard for (var i = 0, dLen = dupArr.length; i < dLen; i++) { var pos = isDocCoords ? dupArr[i].position : doc.convertCoordinate(dupArr[i].position, docCoordSystem, abCoordSystem); dupArr[i].position = [pos[0], pos[1] - (abHeight + spacing)]; } } /** * Duplicate all items * @param {object} collection - selected items on active artboard * @return {array} arr - duplicated items */ function getDuplicates(collection) { var arr = []; for (var i = 0, len = collection.length; i < len; i++) { arr.push(collection[i].duplicate()); } return arr; } // DARK MODE STUFF // Cycle through items and tweak opacities and colours function makeDarkMode2(collection) { for (var i = 0, ii, len = collection.length; i < len; i++) { if(collection[i].typename == "GroupItem") { adjustOpacity(collection[i]); makeDarkMode2(collection[i].pageItems); } if(collection[i].typename == "TextFrame") { changeCharacterColors2(collection[i]); } if(collection[i].typename == "PathItem") { adjustOpacity(collection[i]); changePathColors2(collection[i]); } if(collection[i].typename == "CompoundPathItem" && collection[i].pathItems.length) { adjustOpacity(collection[i]); for (ii = 0; ii < collection[i].pathItems.length; ii ++) { adjustOpacity(collection[i]); changePathColors2(collection[i].pathItems[ii]); } } } return collection; } function changeCharacterColors2(textObject) { if(textObject.textRange.length > 0) { var textRange = textObject.textRange; var paras = textRange.paragraphs; for (var iii=0; iii<paras.length; iii++) { if (paras != undefined && paras.length != 0) { try { var p = paras[iii]; for (var ii=0, n=p.characters.length; ii<n; ii++) { var c = p.characters[ii]; c.fillColor = invertTextColor2(c.fillColor); } } catch(error) { //alert(error); } } } } } function changePathColors2(pathObject) { try { //if (pathObject.stroked) { pathObject.strokeColor = invertPathColor2(pathObject.strokeColor); //} //if (pathObject.filled) { pathObject.fillColor = invertPathColor2(pathObject.fillColor); //} } catch(error) { //alert(error); } } function invertTextColor2(col) { if (col.typename == 'RGBColor') { r = col.red; g = col.green; b = col.blue; var hexCol = rgbToHex(r, g, b); var newCol = guardianColoursLookup[hexCol]; if (newCol != undefined) { var newColRGB = hexToRgb(newCol); if (newColRGB != null) { col.red = newColRGB.r; col.green = newColRGB.g; col.blue = newColRGB.b; } } } return col; } function invertPathColor2(col) { if (col.typename == 'RGBColor') { r = col.red; g = col.green; b = col.blue; var hexCol = rgbToHex(r, g, b); //alert("hexCol=" + hexCol); var newCol = guardianColoursLookup[hexCol]; //alert("newCol=" + newCol); if (newCol != undefined) { var newColRGB = hexToRgb(newCol); if (newColRGB != null) { col.red = newColRGB.r; col.green = newColRGB.g; col.blue = newColRGB.b; } } } return col; } function adjustOpacity(item) { var newOpacity; if (item.opacity && item.opacity != 100) { newOpacity = item.opacity * opacityBoost; // boost opacity against dark background as can appear a bit lost if (newOpacity > 100) { newOpacity = 100; } item.opacity = newOpacity; } } } function getNearestGuardianNeutralsInversion(val) { var nearest = closest(nearestGuardianNeutralArray, val); return guardianNeutralsInversion[String(nearest)].rgb; } function closest(array, num) { var i = 0; var minDiff = 1000; var ans; for (i in array) { var m = Math.abs(num - array[i]); if (m < minDiff) { minDiff = m; ans = array[i]; } } return ans; } function isApproxMatch(n1, n2) { if (Math.abs(n1-n2) < neutralThreshold) { return true; } else { return false; } } function componentToHex(c) { var hex = c.toString(16); return hex.length == 1 ? "0" + hex : hex; } function rgbToHex(r, g, b) { return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); } function hexToRgb(hex) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } function invertHexColor(hex) { if (hex.indexOf('#') === 0) { hex = hex.slice(1); } // convert 3-digit hex to 6-digits. if (hex.length === 3) { hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; } if (hex.length !== 6) { throw new Error('Invalid HEX color.'); } // invert color components var r = (255 - parseInt(hex.slice(0, 2), 16)).toString(16), g = (255 - parseInt(hex.slice(2, 4), 16)).toString(16), b = (255 - parseInt(hex.slice(4, 6), 16)).toString(16); // pad each with zeros and return return '#' + padZero(r) + padZero(g) + padZero(b); } function padZero(str, len) { len = len || 2; var zeros = new Array(len).join('0'); return (zeros + str).slice(-len); }