//DESCRIPTION: List missing glyphs or apply a font or a character style to missing glyphs. // Peter Kahrel // Based on Peter Baumgartner's method, see http://forums.adobe.com/thread/1037284?tstart=0 (function () { if (parseInt (app.version) < 6) { alert ('This script requires InDesign CS4 or later.'); exit(); } if (app.documents.length == 0) { alert ('Please open a document'); exit(); } //================================================================================ // Interface function find_fonts () { var known = {}; var typefaces = []; var fontfamilies = app.fonts.everyItem().fontFamily; for (var i = 0; i < fontfamilies.length; i++) { if (!known[fontfamilies[i]]) { known[fontfamilies[i]] = true; typefaces.push(fontfamilies[i]); } } return typefaces; } function getCharStyles () { var s = app.documents[0].characterStyles.everyItem().name; return s; } function get_data () { var fontnames = find_fonts(); //var charStyles = getCharStyles(); function buildListCharacterStyles (scope, list, str, path) { styles = scope.characterStyles.everyItem().getElements(); for (var i = 0; i < styles.length; i++) { if (styles[i] != app.documents[0].characterStyles[0]) { temp = list.add ('item', styles[i].name + (str == '' ? '' : ' ('+str+')')); temp.id = styles[i].id; // Add property so we can easily get a handle on the style later temp.path = path + styles[i].name; } } for (var j = 0; j < scope.characterStyleGroups.length; j++) { buildListCharacterStyles ( scope.characterStyleGroups[j], list, str + (str == '' ? '' : ':') + scope.characterStyleGroups[j].name, path + scope.characterStyleGroups[j].name +'%%' ); } } var w = new Window ('dialog {text: "Missing glyphs", properties: {closeButton: false}}'); var main = w.add ('panel {alignChildren: "left"}'); var fontgroup = main.add ('group'); var replacefonts = fontgroup.add ('checkbox {text: "Replace missing glyphs with font:"}'); var replacement_font = fontgroup.add ('dropdownlist', undefined, fontnames); var cstylegroup = main.add ('group'); var applyStyle = cstylegroup.add ('checkbox {text: "Apply character style: "}'); var charStyles = cstylegroup.add ('dropdownlist'); charStyles.preferredSize.width = 200; var display = main.add ('checkbox {text: "Display missing glyphs and fonts"}'); var buttons = w.add ('group {alignment: "right"}'); buttons.add ('button {text: "OK", name: "ok"}'); buttons.add ('button {text: "Cancel", name: "cancel"}'); replacefonts.onClick = function () { applyStyle.value = !replacefonts.value; } replacement_font.onChange = function () { if (!replacefonts.value) { replacefonts.notify(); } } applyStyle.onClick = function () { replacefonts.value = !applyStyle.value; } charStyles.onChange = function () { if (!applyStyle.value) { applyStyle.notify(); } } display.onClick = function () { if (display.value) { replacefonts.value = applyStyle.value = false; } } //-------------------------------------------------------------- var minion = replacement_font.find ('Minion Pro'); if (minion != null) { replacement_font.selection = minion; } else { replacement_font.selection = 0; } replacefonts.value = 1; //replacement_font.active = true; current = replacement_font.selection.text; //-------------------------------------------------------------- var i; var buffer = ""; replacement_font.onActivate = function () { buffer = ''; current = replacement_font.selection.text; } replacement_font.addEventListener ('keydown', function (k) { if (k.keyName == 'Backspace') { buffer = buffer.replace (/.$/, ""); if (buffer.length == 0) { buffer = current; } } else { buffer += k.keyName.toLowerCase(); } i = 0; while (i < fontnames.length-1 && fontnames[i].toLowerCase().indexOf (buffer) != 0) { ++i; } if (fontnames[i].toLowerCase().indexOf (buffer) == 0) { replacement_font.selection = i; } } ); w.onShow = function () { buildListCharacterStyles (app.activeDocument, charStyles, '', ''); charStyles.selection = 0; } if (w.show() == 2) { return null; } return { replace: replacefonts.value, replacement_font: replacement_font.selection.text, applyStyle: applyStyle.value, character_style: app.documents[0].characterStyles.itemByID (charStyles.selection.id), display: display.value, } } //================================================================================ function replace_missing_glyphs (data) { var j; var current; var found; var errormsg = ''; var docFonts = app.activeDocument.fonts.everyItem().getElements(); app.findGlyphPreferences = null; for (var i = 0; i < docFonts.length; i++) { //$.bp(docFonts[i].name == 'Arial Bold') if (docFonts[i].status == FontStatus.INSTALLED && docFonts[i].isValid) { app.findGlyphPreferences.glyphID = 0; app.findGlyphPreferences.appliedFont = docFonts[i].fontFamily; app.findGlyphPreferences.fontStyle = docFonts[i].fontStyleName; found = app.activeDocument.findGlyph (); for (j = 0; j < found.length; j++) { if (data.replace) { found[j].appliedFont = data.replacement_font; } else { found[j].appliedCharacterStyle = data.character_style; } } } else { try { errormsg += docFonts[i].fontFamily + '\r'; } catch(_) { } } } if (errormsg.length > 1) { errormsg = 'The following fonts are not installed\r(or have an illegal font style specified)\rand were skipped:\r\r' + errormsg; alert (errormsg); } } function list_missing_glyphs () { var j, known, miss; var report = ''; var docFonts = app.activeDocument.fonts.everyItem().getElements(); for (var i = 0; i < docFonts.length; i++) { if (docFonts[i].status != FontStatus.INSTALLED) { //app.findGrepPreferences.appliedFont = docFonts[i] //report += '['+docFonts[i].fontFamily+']\r'; report += '['+docFonts[i].name+']\r'; } else { app.findGlyphPreferences = null; app.findGlyphPreferences.glyphID = 0; app.findGlyphPreferences.appliedFont = docFonts[i].fontFamily; app.findGlyphPreferences.fontStyle = docFonts[i].fontStyleName; found = app.activeDocument.findGlyph(); if (found.length > 0) { known = {}; report += docFonts[i].fontFamily + ' (' + docFonts[i].fontStyleName + ')\r'; for (j = 0; j < found.length; j++) { miss = get_hex (found[j]); if (!known[miss]) { report += found[j].contents + '\t' + miss + '\r'; known[miss] = true; } } report += '\r'; } } } display_missing_glyphs (report); } function get_hex (ch) { function pad (n) { return ('0000' + n).slice(-4); } try { if (ch.contents.length === 2) { // Plane-1 and higher algorithm adapted from // http://www.russellcottrell.com/greek/utilities/SurrogatePairCalculator.htm var H = parseInt (ch.contents.charCodeAt(0).toString(16), 16); var L = parseInt (ch.contents.charCodeAt(1).toString(16), 16); var s = ((H - 0xD800) * 0x400) + (L - 0xDC00) + 0x10000; return '\\x{' + s.toString(16).toUpperCase() + '}'; } // Plane-0 character; pad hex up to four characters var s = ch.contents.charCodeAt(0).toString(16); return '\\x{' + pad (s).toUpperCase() + '}'; } catch (_) { return 'U+????'; } } function display_missing_glyphs (s) { var e; var w = new Window ('dialog {text: "Missing glyphs", properties: {closeButton: false}}'); if (s.length == 0) { s = 'No missing glyphs.' e = w.add ('edittext', undefined, s); } else { e = w.add ('edittext', undefined, s, {multiline: true, scrolling: true}); } e.maximumSize.height = w.maximumSize.height-150; var b = w.add ('group'); b.add ('button {text: "Close", properties: {name: "ok"}}') var save = b.add ('button {text: "Save"}'); save.onClick = function () { var outfile = File('~/Desktop/' + app.activeDocument.name.replace(/\.indd$/, '_missing_glyphs.txt')); outfile.encoding = 'UTF-8'; outfile.open ('w'); outfile.write (e.text); outfile.close(); outfile.execute(); } w.show(); } //-------------------------------------------------------------- function main () { app.findChangeGlyphOptions.includeFootnotes = true; var data = get_data(); if (!data) { return; } if (data.replace || data.applyStyle) { replace_missing_glyphs (data); } if (data.display) { list_missing_glyphs(); } } main(); }());