
Illustrator 2022(v26)からリンク画像の扱いが変わりました。それ以前のバージョンではリンク先が行方不明な画像は何も表示されませんでしたが,2022(v26)では低解像度サムネイルが必ず付きます(Illustrator 2020方式で保存した場合。参考:Illustrator2020形式ファイルフォーマットとは | Automation Skill)。
PDFなどを書き出した際は,低解像度サムネイルが見えている状態になります。リンク画像がなくても何があるかわかるようになりました。親切です。
しかしこの仕様はリンク切れを気づきにくくしてしまいました。低解像度でもまあまあ綺麗なので,異常を見逃すのです。
もし保存や書き出しの際にリンク切れを警告してくれたら,ミスを防げそうですね。ただIllustratorでは,保存や書き出しの動作を感知して警告を入れるのは難しいようです。
そこで今回は,Illustratorで画像のリンク切れを確認したあと,特定のメニューを実行するスクリプトを紹介します。
別名で保存や書き出しなど,通常のキーボードショートカットをこのスクリプトで置き換えると,特定のメニュー実行前にリンク切れ確認処理が入るという作戦です。
残念ながら手動でメニューを選んだときは発動しません。それで十分役に立つと思うかたのみご利用ください。
あらましを教えて
Illustratorで画像のリンク切れを確認したあと,別名で保存や書き出しなど指定したメニューを実行するスクリプトです。リンクに問題があればダイアログが現れ,中止するか無視してメニューを実行するか選べます。

スクリプトにキーボードショートカットを割り当てるアプリを併用し,別名で保存や書き出しなどをこのスクリプトに置き換えることでミスを防ぎます。アプリはKeyboard MaestroやSPAi・Sppyなどがおすすめです。
スクリプトのダウンロードはこちらからどうぞ。
どのバージョンに対応してるの?
Illustrator CS6(v16) かそれ以降のバージョンに対応しています。
どうやってメニューの種類を指定するの?
スクリプトのファイル名を checkLinksBefore=saveas.jsx のような特定の形式にすることで,対象のメニューを指定します。saveasの部分が別名で保存を表す単語です。そこの文字を打ち変えると発動するメニューも変わります。
どんなメニューが指定できるの?
次のようになっています。
メニュー識別単語 | 動作説明 |
---|---|
save | 保存(上書き保存) |
saveas | 別名で保存 |
saveacopy | 複製を保存 |
exportForScreens | スクリーン用に書き出し |
export | 書き出し形式 |
whichsave | 複製を保存・別名で保存のどちらを実行するかダイアログで指定する |
メニュー[whichsave]は創作オリジナル動作です。複製を保存がしたいのに指がどうしても別名で保存を押してしまうかたは,ぜひ[shift+command+S]から置き換えてご利用ください。

メニュー識別単語は,基本的にIllustratorスクリプトのapp.executeMenuCommand(menuCommandString)に準拠します。一覧をまとめてありますので,上の表にない項目を使いたいかたはご参照ください。
ちなみに
以前の記事【解決】元データを消さずいつもの設定でPDFを書き出したい! のスクリプトにてPDFを書き出す際には,この記事と同じようにリンク切れ確認をします。併せてご利用をどうぞ。
記事を書いてから気づきましたが,リンク切れがあったら警告するエクステンションがすでにありました。「ドキュメントが切り替えられた時」と「ドキュメントを保存した時」にダイアログが出るそうです。こちらもご検討ください。
これでまた少し仕事が速くなりました。今日もさっさと仕事を切り上げて好きなことをしましょう!
コードはこちら。
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 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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 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 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
/** * @file メニュー項目を実行する前にリンク切れを確認し,異常があれば処理を続けるかダイアログで尋ねる * @version 1.0.0 * @author sttk3.com * @copyright © 2022 sttk3.com */ //#target 'illustrator' $.localize = true ; (function(argv) { if(app.documents.length <= 0) {return ;} var doc = app.documents[0] ; if(appVersion()[0] < 16) { alert({en: 'This script requires Illustrator CS6 or later', ja: 'このスクリプトは Illustrator CS6 以降のみに対応しています。'}) ; } if(existsInvalidLink(doc)) { var msgInvalidLink = { ja: 'この書類には無効なリンク画像が含まれています。それでも処理を続けますか?\n「いいえ」を選んでリンクを修正するか「はい」を選んで続行してください。', en: 'This document contains invalid links. Do you accept it?\nSelect "No" to correct the link or "Yes" to continue.' } ; var retval = confirm(msgInvalidLink) ; if(!retval) {return ;} } // スクリプトファイル名から引数を生成する var tempArgv = parseArguments(decodeURIComponent(combineDakuten(new File($.fileName).name)), argv) ; if(tempArgv == null) { alert({en: 'The command name was unknown.', ja: '動作の指定が不明です。'}) ; return ; } var command = tempArgv.option ; try { switch(command) { case 'whichsave' : var command2 = chooseTargetMenu(command) ; if(command2) {app.executeMenuCommand(command2) ;} break ; default : app.executeMenuCommand(command) ; break ; } } catch(e) { var msg ; if(e.number == 1200) { msg = {en: 'Menu "' + command + '" was unknown.', ja: 'メニュー「' + command + '」は見つかりませんでした。'} ; } else { msg = e.message ; } alert(msg) ; return ; } })(arguments) ; /** * スクリプト実行元アプリケーションのバージョンを取得して数値の配列にする。16.0.4の場合[16, 0, 4] * @return {Array of numbers} */ function appVersion() { var tmp = app.version.toString().split('.') ; var res = [] ; for(var i = 0, len = tmp.length ; i < len ; i++) { res.push(Number(tmp[i])) ; } return res ; } /** * checkLinksBefore=export.jsxのような文字列を解析する * @param {String} str 対象の文字列 * @param {Arguments} argv ファイル実行引数 * @return {Object} {mode: String, option: String} */ function parseArguments(str, argv) { var res ; var matchObj = str.match(/^(checkLinksBefore)[\s ]*[==](.+?)(?:\.js(?:x|xbin)?)?$/i) ; if(!matchObj) {return res ;} var mode = matchObj[1] ; // コマンド名。'saveas'のような文字が来る var opt = matchObj[2] ; // 単一ファイルで引数を受け取るパターンに対応する if(/^argv$/i.test(opt)) { var tempStr = $.getenv('sttk3_arguments') ; if((tempStr == null) || (tempStr == '')) { if(argv.length <= 0) {return res ;} tempStr = argv[0].toString() ; } res = {mode: mode, option: tempStr} ; } else { res = {mode: mode, option: opt} ; } return res ; } /** * 分割された濁点・半濁点を結合された文字にする。ウムラウトなどは無視する * @param {String} str 対象の文字列。URIエンコードされたもの * @return {String} 変換されたテキスト */ function combineDakuten(str) { // 置換対象の文字とその順番 // ゔがぎぐげござじずぜぞだぢづでどばぱびぴぶぷべぺぼぽヴガギグゲゴザジズゼゾダヂヅデドバパビピブプベペボポ // 分割された文字 var nfd = ['%E3%81%86%E3%82%99', '%E3%81%8B%E3%82%99', '%E3%81%8D%E3%82%99', '%E3%81%8F%E3%82%99', '%E3%81%91%E3%82%99', '%E3%81%93%E3%82%99', '%E3%81%95%E3%82%99', '%E3%81%97%E3%82%99', '%E3%81%99%E3%82%99', '%E3%81%9B%E3%82%99', '%E3%81%9D%E3%82%99', '%E3%81%9F%E3%82%99', '%E3%81%A1%E3%82%99', '%E3%81%A4%E3%82%99', '%E3%81%A6%E3%82%99', '%E3%81%A8%E3%82%99', '%E3%81%AF%E3%82%99', '%E3%81%AF%E3%82%9A', '%E3%81%B2%E3%82%99', '%E3%81%B2%E3%82%9A', '%E3%81%B5%E3%82%99', '%E3%81%B5%E3%82%9A', '%E3%81%B8%E3%82%99', '%E3%81%B8%E3%82%9A', '%E3%81%BB%E3%82%99', '%E3%81%BB%E3%82%9A', '%E3%82%A6%E3%82%99', '%E3%82%AB%E3%82%99', '%E3%82%AD%E3%82%99', '%E3%82%AF%E3%82%99', '%E3%82%B1%E3%82%99', '%E3%82%B3%E3%82%99', '%E3%82%B5%E3%82%99', '%E3%82%B7%E3%82%99', '%E3%82%B9%E3%82%99', '%E3%82%BB%E3%82%99', '%E3%82%BD%E3%82%99', '%E3%82%BF%E3%82%99', '%E3%83%81%E3%82%99', '%E3%83%84%E3%82%99', '%E3%83%86%E3%82%99', '%E3%83%88%E3%82%99', '%E3%83%8F%E3%82%99', '%E3%83%8F%E3%82%9A', '%E3%83%92%E3%82%99', '%E3%83%92%E3%82%9A', '%E3%83%95%E3%82%99', '%E3%83%95%E3%82%9A', '%E3%83%98%E3%82%99', '%E3%83%98%E3%82%9A', '%E3%83%9B%E3%82%99', '%E3%83%9B%E3%82%9A'] ; // 結合された文字 var nfc = ['%E3%82%94', '%E3%81%8C', '%E3%81%8E', '%E3%81%90', '%E3%81%92', '%E3%81%94', '%E3%81%96', '%E3%81%98', '%E3%81%9A', '%E3%81%9C', '%E3%81%9E', '%E3%81%A0', '%E3%81%A2', '%E3%81%A5', '%E3%81%A7', '%E3%81%A9', '%E3%81%B0', '%E3%81%B1', '%E3%81%B3', '%E3%81%B4', '%E3%81%B6', '%E3%81%B7', '%E3%81%B9', '%E3%81%BA', '%E3%81%BC', '%E3%81%BD', '%E3%83%B4', '%E3%82%AC', '%E3%82%AE', '%E3%82%B0', '%E3%82%B2', '%E3%82%B4', '%E3%82%B6', '%E3%82%B8', '%E3%82%BA', '%E3%82%BC', '%E3%82%BE', '%E3%83%80', '%E3%83%82', '%E3%83%85', '%E3%83%87', '%E3%83%89', '%E3%83%90', '%E3%83%91', '%E3%83%93', '%E3%83%94', '%E3%83%96', '%E3%83%97', '%E3%83%99', '%E3%83%9A', '%E3%83%9C', '%E3%83%9D'] ; var currentPattern ; for(var i = 0, len = nfd.length ; i < len ; i++) { currentPattern = new RegExp(nfd[i], 'ig') ; if(currentPattern.test(str)) { str = str.replace(currentPattern, nfc[i]) ; } } return str ; } /** * 書類にリンク切れ画像があるかを返す。ある場合true。 * 画像名は取得方法が複雑なので,このメソッドでは取得を諦める * @param {Array} doc 対象の書類 * @return {Boolean} */ function existsInvalidLink(doc) { var res = false ; var linkItems = doc.placedItems ; for(var i = 0, len = linkItems.length ; i < len ; i++) { try { linkItems[i].file ; } catch(e) { res = true ; break ; } } return res ; } /** * 複製を保存・別名で保存のどちらかをダイアログで選ぶ * @param {String} title 表示するダイアログのタイトル * @return {String} */ function chooseTargetMenu(title) { // 候補リストを作る var hotKeys = '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ' ; var delim = '\t' ; var menuDB = [ [localize({ja: '複製を保存', en: 'Save a Copy'}), 'saveacopy'], [localize({ja: '別名で保存', en: 'Save As'}), 'saveas'] ] ; var menuList = [] ; for(var i = 0, len = menuDB.length ; i < len ; i++) { menuList.push((hotKeys[i] || ' ') + delim + menuDB[i][0]) ; } if(menuList.length <= 0) {return ;} // ウインドウ var win = new Window('dialog', title) ; // リスト var body = win.add('group', undefined) ; body.orientation = 'column' ; body.alignChildren = ['fill', 'fill'] ; body.add('statictext', undefined, {en : 'Choose menu to execute', ja : '実行するメニューを選択'}) ; var list = body.add('listbox', undefined, menuList) ; list.selection = list.items[0] ; list.active = true ; win.addEventListener('keyup', function(e) { var dstIndex = hotKeys.indexOf(e.keyName) ; if(dstIndex >= 0) {list.selection = list.items[dstIndex] ;} }) ; // Cancel | OK var footer = win.add('group', undefined) ; footer.orientation = 'row' ; footer.alignChildren = ['center', 'fill'] ; var cancelButton, okButton ; var os = $.os ; var cancelLabel = 'Cancel' ; var okLabel = 'OK' ; if(/^w/i.test(os)) { okButton = footer.add('button', undefined, okLabel) ; cancelButton = footer.add('button', undefined, cancelLabel) ; } else { cancelButton = footer.add('button', undefined, cancelLabel) ; okButton = footer.add('button', undefined, okLabel) ; } okButton.selected = true ; var doCancel = false ; okButton.onClick = okButton.onSubmit = function(e) {win.close() ;} ; cancelButton.onClick = function(e) { doCancel = true ; win.close() ; win = null ; } ; // ダイアログを表示する win.show() ; if(doCancel || list.selection == null) {return ;} // 返す項目を決定する var dstMenu = menuDB[list.selection.index][1] ; return dstMenu ; } |
このサイトで配布しているスクリプトやその他のファイルを,無断で転載・配布・販売することを禁じます。
それらの使用により生じたあらゆる損害について,私どもは責任を負いません。
スクリプトやファイルのダウンロードを行った時点で,上記の規定に同意したとみなします。