fitToIndesignFrame.scptは、InDesignで配置したフレームに合わせて、不足分を画像サイズで変えたり、フレームでトリムされている箇所にガイドを引くAppleScriptです。
jsx版:Photoshop_indesign_crop.jsx
正立・水平垂直のみ対応。
解像度変更はしません。
InDesignで位置合わせする際には、フレーム内を選択して拡大率をコピーしてから差し替え、「伸ばした側」のXYをフレームぴったり&拡大率を合わせれば同じ位置に差し替えられます。
| -- InDesign側でなく、実行側のPsでアラートを出すための関数 | |
| on PSMsg(DispMesg) | |
| tell application id "com.adobe.photoshop" to display alert DispMesg | |
| end PSMsg | |
| --PSドキュメントチェック | |
| tell application id "com.adobe.photoshop" | |
| if (count of documents) = 0 then | |
| my PSMsg("画像が開かれていません") | |
| return | |
| end if | |
| set myFile to current document | |
| set myFilePath to file path of current document | |
| end tell | |
| tell application id "com.adobe.indesign" | |
| --IDドキュメントチェック | |
| if (count of documents) = 0 then | |
| my PSMsg("⚠️InDesignドキュメントが開かれていません") | |
| return | |
| end if | |
| -- 単位をmmに、定規をスプレッドに | |
| tell view preferences | |
| set idoriRuler to ruler origin | |
| set idoriHoriunits to horizontal measurement units | |
| set idoriVerunits to vertical measurement units | |
| set ruler origin to spread origin | |
| set horizontal measurement units to millimeters | |
| set vertical measurement units to millimeters | |
| end tell | |
| --リンク名が同じものをリストアップ。なければ終了 | |
| set myLinkObj to active document's links whose file path = myFilePath | |
| if (count of myLinkObj) = 0 then | |
| my PSMsg("この画像は配置されていません") | |
| return | |
| end if | |
| --一番大きく配置したものを選択 | |
| set myMaxScale to 0 | |
| repeat with i in myLinkObj | |
| if status of i is normal then | |
| set myScale to absolute horizontal scale of parent of i | |
| if myScale > myMaxScale then | |
| set myMaxScale to myScale | |
| set myLargestLink to i | |
| end if | |
| end if | |
| end repeat | |
| --リンク内容の実効ppi(タテヨコ)、プレースホルダオブジェクトを取得 | |
| set myLink to parent of myLargestLink | |
| set myLinkParent to parent of myLink | |
| set myPPI to effective ppi of myLink | |
| set a to item 1 of geometric bounds of myLink | |
| --内容の位置とプレースホルダの位置を計算(上・左・下・右) | |
| set myLinkBounds to {} | |
| repeat with i from 1 to 4 | |
| set mL to item i of geometric bounds of myLink | |
| set mP to item i of geometric bounds of myLinkParent | |
| --タテヨコの実効PPIを取得しておく | |
| if i = 1 or i = 3 then | |
| set myPPIXY to item 1 of myPPI | |
| else | |
| set myPPIXY to item 2 of myPPI | |
| end if | |
| --上・左はホルダ-内容、下・右は内容-ホルダ | |
| if i ≤ 2 then | |
| set end of myLinkBounds to round (myPPIXY / 25.4 * (mP - mL)) | |
| else | |
| set end of myLinkBounds to round (myPPIXY / 25.4 * (mL - mP)) | |
| end if | |
| end repeat | |
| end tell | |
| tell application id "com.adobe.photoshop" | |
| --■ガイドを四辺に引く | |
| --単位をpxに(元を保存) | |
| set oriUnit to ruler units of settings | |
| set ruler units of settings to pixel units | |
| set myX to width of myFile | |
| set myY to height of myFile | |
| --伸ばす場合は元の画像位置、食い込む場合はタテヨコで計算 | |
| set myGuide to {} | |
| repeat with i in myLinkBounds | |
| if i ≤ 0 then | |
| set end of myGuide to 0 | |
| else | |
| set end of myGuide to i as number | |
| end if | |
| end repeat | |
| --ガイド位置設定 | |
| set horizontalGuides to {item 1 of myGuide, myY - (item 3 of myGuide)} | |
| set verticalGuides to {item 2 of myGuide, myX - (item 4 of myGuide)} | |
| --jsxでガイド描画 | |
| repeat with i in horizontalGuides | |
| do javascript "app.activeDocument.guides.add(Direction.HORIZONTAL, " & i & ");" | |
| end repeat | |
| repeat with i in verticalGuides | |
| do javascript "app.activeDocument.guides.add(Direction.VERTICAL, " & i & ");" | |
| end repeat | |
| --■画像をカンバスサイズで伸ばし | |
| -- マイナス値があるかどうかをテキスト化で判定 | |
| if (myLinkBounds as string) contains "-" then | |
| --あとから選択範囲をとりやすいように、背景レイヤーなら通常レイヤーにする | |
| try | |
| set bgLayer to background layer of myFile | |
| set name of bgLayer to "背景" | |
| end try | |
| --食い込む場合は0、伸ばす場合は数値を残す | |
| set myCanvas to {} | |
| repeat with i in myLinkBounds | |
| if i ≥ 0 then | |
| set end of myCanvas to 0 | |
| else | |
| set end of myCanvas to (i as number) * -1 | |
| end if | |
| end repeat | |
| --jsxでカンバスサイズ変更 | |
| do javascript "app.activeDocument.resizeCanvas(" & myX + (item 4 of myCanvas) & ", " & myY + (item 3 of myCanvas) & ", AnchorPosition.TOPLEFT);" | |
| do javascript "app.activeDocument.resizeCanvas(" & myX + (item 2 of myCanvas) + (item 4 of myCanvas) & ", " & myY + (item 1 of myCanvas) + (item 3 of myCanvas) & ", AnchorPosition.BOTTOMRIGHT);" | |
| end if | |
| --単位を元に戻す | |
| set ruler units of settings to oriUnit | |
| end tell | |
| --単位を元に戻す | |
| tell application id "com.adobe.indesign" | |
| tell view preferences | |
| set ruler origin to idoriRuler | |
| set horizontal measurement units to idoriHoriunits | |
| set vertical measurement units to idoriVerunits | |
| end tell | |
| end tell |
| /* | |
| <javascriptresource> | |
| <name>InDesignに合わせてクロップ</name> | |
| <category>YPresets</category> | |
| </javascriptresource> | |
| */ | |
| // Ver 1.0(2025/11/03) | |
| // --- NFC/NFD 正規化ヘルパ --- | |
| function toNFCJa(s) { | |
| if (!s) return s; | |
| var map = { | |
| "カ\u3099": "ガ", | |
| "キ\u3099": "ギ", | |
| "ク\u3099": "グ", | |
| "ケ\u3099": "ゲ", | |
| "コ\u3099": "ゴ", | |
| "サ\u3099": "ザ", | |
| "シ\u3099": "ジ", | |
| "ス\u3099": "ズ", | |
| "セ\u3099": "ゼ", | |
| "ソ\u3099": "ゾ", | |
| "タ\u3099": "ダ", | |
| "チ\u3099": "ヂ", | |
| "ツ\u3099": "ヅ", | |
| "テ\u3099": "デ", | |
| "ト\u3099": "ド", | |
| "ハ\u3099": "バ", | |
| "ヒ\u3099": "ビ", | |
| "フ\u3099": "ブ", | |
| "ヘ\u3099": "ベ", | |
| "ホ\u3099": "ボ", | |
| "ウ\u3099": "ヴ", | |
| "ワ\u3099": "ヷ", | |
| "ヰ\u3099": "ヸ", | |
| "ヱ\u3099": "ヹ", | |
| "ヲ\u3099": "ヺ", | |
| "ハ\u309A": "パ", | |
| "ヒ\u309A": "ピ", | |
| "フ\u309A": "プ", | |
| "ヘ\u309A": "ペ", | |
| "ホ\u309A": "ポ", | |
| "か\u3099": "が", | |
| "き\u3099": "ぎ", | |
| "く\u3099": "ぐ", | |
| "け\u3099": "げ", | |
| "こ\u3099": "ご", | |
| "さ\u3099": "ざ", | |
| "し\u3099": "じ", | |
| "す\u3099": "ず", | |
| "せ\u3099": "ぜ", | |
| "そ\u3099": "ぞ", | |
| "た\u3099": "だ", | |
| "ち\u3099": "ぢ", | |
| "つ\u3099": "づ", | |
| "て\u3099": "で", | |
| "と\u3099": "ど", | |
| "は\u3099": "ば", | |
| "ひ\u3099": "び", | |
| "ふ\u3099": "ぶ", | |
| "へ\u3099": "べ", | |
| "ほ\u3099": "ぼ", | |
| "う\u3099": "ゔ", | |
| "は\u309A": "ぱ", | |
| "ひ\u309A": "ぴ", | |
| "ふ\u309A": "ぷ", | |
| "へ\u309A": "ぺ", | |
| "ほ\u309A": "ぽ", | |
| "A\u0301": "Á", | |
| "E\u0301": "É", | |
| "I\u0301": "Í", | |
| "O\u0301": "Ó", | |
| "U\u0301": "Ú", | |
| "Y\u0301": "Ý", | |
| "a\u0301": "á", | |
| "e\u0301": "é", | |
| "i\u0301": "í", | |
| "o\u0301": "ó", | |
| "u\u0301": "ú", | |
| "y\u0301": "ý", | |
| "A\u0300": "À", | |
| "E\u0300": "È", | |
| "I\u0300": "Ì", | |
| "O\u0300": "Ò", | |
| "U\u0300": "Ù", | |
| "a\u0300": "à", | |
| "e\u0300": "è", | |
| "i\u0300": "ì", | |
| "o\u0300": "ò", | |
| "u\u0300": "ù", | |
| "A\u0302": "Â", | |
| "E\u0302": "Ê", | |
| "I\u0302": "Î", | |
| "O\u0302": "Ô", | |
| "U\u0302": "Û", | |
| "a\u0302": "â", | |
| "e\u0302": "ê", | |
| "i\u0302": "î", | |
| "o\u0302": "ô", | |
| "u\u0302": "û", | |
| "A\u0308": "Ä", | |
| "E\u0308": "Ë", | |
| "I\u0308": "Ï", | |
| "O\u0308": "Ö", | |
| "U\u0308": "Ü", | |
| "Y\u0308": "Ÿ", | |
| "a\u0308": "ä", | |
| "e\u0308": "ë", | |
| "i\u0308": "ï", | |
| "o\u0308": "ö", | |
| "u\u0308": "ü", | |
| "y\u0308": "ÿ", | |
| "A\u0303": "Ã", | |
| "N\u0303": "Ñ", | |
| "O\u0303": "Õ", | |
| "a\u0303": "ã", | |
| "n\u0303": "ñ", | |
| "o\u0303": "õ", | |
| "A\u030A": "Å", | |
| "a\u030A": "å", | |
| "A\u0304": "Ā", | |
| "E\u0304": "Ē", | |
| "I\u0304": "Ī", | |
| "O\u0304": "Ō", | |
| "U\u0304": "Ū", | |
| "a\u0304": "ā", | |
| "e\u0304": "ē", | |
| "i\u0304": "ī", | |
| "o\u0304": "ō", | |
| "u\u0304": "ū", | |
| "C\u030C": "Č", | |
| "D\u030C": "Ď", | |
| "E\u030C": "Ě", | |
| "N\u030C": "Ň", | |
| "R\u030C": "Ř", | |
| "S\u030C": "Š", | |
| "T\u030C": "Ť", | |
| "Z\u030C": "Ž", | |
| "c\u030C": "č", | |
| "d\u030C": "ď", | |
| "e\u030C": "ě", | |
| "n\u030C": "ň", | |
| "r\u030C": "ř", | |
| "s\u030C": "š", | |
| "t\u030C": "ť", | |
| "z\u030C": "ž", | |
| "Z\u0307": "Ż", | |
| "z\u0307": "ż", | |
| "A\u0328": "Ą", | |
| "E\u0328": "Ę", | |
| "a\u0328": "ą", | |
| "e\u0328": "ę", | |
| "C\u0327": "Ç", | |
| "c\u0327": "ç" | |
| }; | |
| var out = String(s); | |
| for (var k in map) out = out.split(k).join(map[k]); | |
| return out; | |
| } | |
| // 実体解決 + fsName + NFC化 | |
| function _normPathLocal(p) { | |
| try { | |
| var f = new File(p); | |
| try { | |
| f = f.resolve(); | |
| } catch (_e) {} | |
| var s = f.fsName; | |
| return toNFCJa(s); | |
| } catch (e) { | |
| try { | |
| return toNFCJa(String(p)); | |
| } catch (e2) { | |
| return String(p); | |
| } | |
| } | |
| } | |
| var NFC_HELPER_SRC = toNFCJa.toString(); | |
| var NORM_HELPER_SRC = _normPathLocal.toString(); | |
| // Photoshopでファイルを開いているか確認 | |
| if (app.documents.length > 0) { | |
| app.activeDocument.suspendHistory("画像伸ばし", "mainProcess()"); | |
| } | |
| function mainProcess() { | |
| // InDesign応答格納 | |
| var inDesignResponse = null; | |
| // 単位の保存と設定 | |
| var doc = app.activeDocument; | |
| var originalRulerUnits = preferences.rulerUnits; | |
| preferences.rulerUnits = Units.PIXELS; | |
| try { | |
| var guides = doc.guides; | |
| var canvasWidth = doc.width.value; | |
| var canvasHeight = doc.height.value; | |
| // `'` を含むファイル名でも壊れないよう encodeURIComponent を使用 | |
| var encodedFilePath = encodeURIComponent(_normPathLocal(doc.fullName.fsName)); | |
| sendToInDesign(encodedFilePath); | |
| // 戻り値の処理 | |
| if (typeof inDesignResponse === 'string' && inDesignResponse.indexOf("!") === 0) { | |
| alert(inDesignResponse); | |
| return; | |
| } | |
| try { | |
| // 背景を通常レイヤに(背景ロックを外したい場合) | |
| doc.backgroundLayer.isBackgroundLayer = false; | |
| } catch (e) {} | |
| var adjustments = inDesignResponse.split(","); | |
| doc.guides.removeAll(); | |
| for (var k = 0; k < 4; k++) { | |
| var adjustmentValue = adjustments[k]; | |
| var guidePosition = 0; | |
| if (adjustmentValue.indexOf("g") === 0) { | |
| // g=ガイドオフセットだけ入れてキャンバス増分は0 | |
| guidePosition = Number(adjustmentValue.slice(1)); | |
| adjustments[k] = 0; | |
| } else { | |
| var v = parseInt(adjustmentValue, 10); | |
| adjustments[k] = isNaN(v) ? 0 : v; | |
| } | |
| if (k >= 2) { | |
| guidePosition = (k === 2) ? canvasHeight - guidePosition : canvasWidth - guidePosition; | |
| } | |
| var direction = (k % 2 === 0) ? Direction.HORIZONTAL : Direction.VERTICAL; | |
| guides.add(direction, UnitValue(guidePosition, 'px')); | |
| } | |
| // カンバスサイズの変更 | |
| doc.resizeCanvas(canvasWidth + adjustments[1], canvasHeight + adjustments[0], AnchorPosition.BOTTOMRIGHT); | |
| doc.resizeCanvas(canvasWidth + adjustments[1] + adjustments[3], canvasHeight + adjustments[0] + adjustments[2], AnchorPosition.TOPLEFT); | |
| } finally { | |
| // 単位を元に戻す | |
| preferences.rulerUnits = originalRulerUnits; | |
| } | |
| // ─────────────────────────────────────────────────────────── | |
| // InDesignをBridgeTalkで呼び出す(同期待機) | |
| function sendToInDesign(encodedPath) { | |
| // InDesign側で実行する関数(InDesignコンテキスト) | |
| var inDesignFunction = function(encodedKey) { | |
| var decodedKey = decodeURIComponent(encodedKey); | |
| /*__INJECT_TO_NFC_JA__*/ | |
| decodedKey = toNFCJa(decodedKey); | |
| try { | |
| decodedKey = _normPath(decodedKey); | |
| } catch (_e) {} | |
| if (app.documents.length === 0) { | |
| return "!ドキュメントが開かれていません。"; | |
| } | |
| // 環境復元のため退避 | |
| var origUnit = app.scriptPreferences.measurementUnit; | |
| var origOrigin = app.activeDocument.viewPreferences.rulerOrigin; | |
| try { | |
| app.scriptPreferences.measurementUnit = MeasurementUnits.MILLIMETERS; | |
| app.activeDocument.viewPreferences.rulerOrigin = RulerOrigin.SPREAD_ORIGIN; | |
| if (app.scriptPreferences.measurementUnit !== MeasurementUnits.MILLIMETERS) { return "!単位設定に失敗しました(MILLIMETERS 以外)"; } | |
| var links = app.activeDocument.links; | |
| var matchingLinks = []; | |
| for (var i = 0; i < links.length; i++) { | |
| var linkPath = _normPath(links[i].filePath); | |
| var matched = (linkPath === decodedKey); | |
| if (!matched) { | |
| // フォールバック: ファイル名(NFC)一致 + ファイルサイズ一致 | |
| try { | |
| var lf = new File(links[i].filePath); | |
| var tf = new File(decodedKey); | |
| var nameEq = toNFCJa(lf.name) === toNFCJa(tf.name); | |
| var lenEq = (lf.length === tf.length && lf.length > 0); | |
| if (nameEq && lenEq) matched = true; | |
| } catch (__e) {} | |
| } | |
| if (matched) { | |
| if (links[i].status === LinkStatus.NORMAL) { | |
| matchingLinks.push(links[i]); | |
| } else { | |
| return "!リンクが更新されていないため、正確な拡大率を取得できません。\rInDesignでリンクパネルを確認してください。"; | |
| } | |
| } | |
| } | |
| if (matchingLinks.length === 0) { | |
| var firstLinkPath = links.length > 0 ? _normPath(links[0].filePath) : "リンクなし"; | |
| return "!この画像はInDesignドキュメント中に存在しません。\n" + | |
| "最初のリンクのパス: " + firstLinkPath + "\n" + | |
| "検索キー: " + decodedKey; | |
| } | |
| matchingLinks.sort(function(a, b) { | |
| return b.parent.verticalScale - a.parent.verticalScale; | |
| }); | |
| var item = matchingLinks[0].parent; | |
| var parentItem = item.parent; | |
| var effectivePpi = item.effectivePpi; // [xPPI, yPPI] | |
| var adjustments = []; | |
| for (var j = 0; j < 4; j++) { | |
| var itemBound = item.geometricBounds[j]; | |
| var parentBound = parentItem.geometricBounds[j]; | |
| // j=0,2 → Y方向なので yPPI、j=1,3 → X方向なので xPPI | |
| var ppi = (j % 2 === 0) ? effectivePpi[1] : effectivePpi[0]; | |
| var difference = (j < 2) ? itemBound - parentBound : parentBound - itemBound; | |
| var adjustment = Math.round(ppi / 25.4 * Math.abs(difference)); | |
| adjustments.push(difference > 0 ? adjustment : "g" + adjustment); | |
| } | |
| return adjustments.join(","); | |
| } catch (e) { | |
| return "!InDesign側エラー: " + e; | |
| } finally { | |
| // 環境復元 | |
| try { | |
| app.scriptPreferences.measurementUnit = origUnit; | |
| } catch (_) {} | |
| try { | |
| app.activeDocument.viewPreferences.rulerOrigin = origOrigin; | |
| } catch (_) {} | |
| } | |
| }; | |
| var bt = new BridgeTalk(); | |
| bt.target = "indesign"; | |
| // ヘルパをInDesign側スコープへ注入 | |
| var injected = inDesignFunction.toString().replace( | |
| "/*__INJECT_TO_NFC_JA__*/", | |
| "var toNFCJa = " + NFC_HELPER_SRC + ";\nvar _normPath = " + NORM_HELPER_SRC + ";" | |
| ); | |
| function _escJS(str) { | |
| // BridgeTalk 本文に安全に埋め込むための最小限エスケープ | |
| return String(str).replace(/\\/g, "\\\\").replace(/'/g, "\\'"); | |
| } | |
| bt.body = "(" + injected + ")('" + _escJS(encodedPath) + "');"; | |
| inDesignResponse = null; | |
| bt.onResult = function(response) { | |
| inDesignResponse = response.body; | |
| }; | |
| bt.onError = function(err) { | |
| inDesignResponse = "!BridgeTalkエラー: " + (err && err.body ? err.body : "unknown"); | |
| }; | |
| bt.send(); | |
| // 同期待機(最大10秒) | |
| var start = new Date().getTime(); | |
| var timeoutMs = 10000; // 10s | |
| while (inDesignResponse === null && (new Date().getTime() - start) < timeoutMs) { | |
| try { BridgeTalk.pump(); } catch (_e) {} | |
| $.sleep(50); | |
| } | |
| if (inDesignResponse === null) { | |
| inDesignResponse = "!InDesignから応答がありません(タイムアウト)"; | |
| } | |
| } | |
| } |
| /* | |
| <javascriptresource> | |
| <name>InDesignに合わせてリサイズ</name> | |
| <category>YPresets</category> | |
| </javascriptresource> | |
| */ | |
| // Ver 1.3 (2025/10/27) | |
| // InDesignのリンク配置サイズに合わせてPhotoshop画像を指定ppiへ非破壊リサイズするユーティリティ(BridgeTalk + ScriptUI) | |
| #target photoshop | |
| // 設定値: ユーザー選択可能なppi候補と注意/上限しきい値 | |
| var targetPPIList = [350, 400, 600, 1200]; | |
| var defaultTargetPPI = 350; | |
| var efScaleMin = 0.9; | |
| var efScaleMax = 1.1; | |
| var scaleMax = 2; | |
| // ==== CustomOptions(前回設定の保存/復元: InDesign 連携版) ==== | |
| var PREF_ID_IDRESIZE = "com.yamo.psIDresize_v1"; // 指定識別子 | |
| var K_VER_ID = stringIDToTypeID("version"); | |
| var K_RADIO_INDEX_ID = stringIDToTypeID("radioIndex"); // targetPPIList のインデックス | |
| var K_UPSCALE_ID = stringIDToTypeID("upscaleMethod"); // methodValues の文字列 | |
| var K_DOWNSCALE_ID = stringIDToTypeID("downMethod"); // downMethodValues の文字列 | |
| var K_USE_PREV_ID = stringIDToTypeID("usePrevSettings"); // 前回設定値を使用(bool) | |
| var SCHEMA_VERSION_ID = 1; | |
| var defaultsPrefsID = { | |
| usePrev: false, | |
| radioIndex: 0, // PPI リスト先頭 | |
| upscaleMethod: "deepUpscale", // 既定アップスケール | |
| downMethod: "bicubic" // 既定ダウンスケール | |
| }; | |
| function cloneDefaultsPrefsID() { | |
| return { | |
| usePrev: defaultsPrefsID.usePrev, | |
| radioIndex: defaultsPrefsID.radioIndex, | |
| upscaleMethod: defaultsPrefsID.upscaleMethod, | |
| downMethod: defaultsPrefsID.downMethod | |
| }; | |
| } | |
| function loadPrefsCO_ID() { | |
| var p = cloneDefaultsPrefsID(); | |
| try { | |
| var d = app.getCustomOptions(PREF_ID_IDRESIZE); | |
| if (d.hasKey(K_USE_PREV_ID)) p.usePrev = d.getBoolean(K_USE_PREV_ID); | |
| if (d.hasKey(K_RADIO_INDEX_ID)) { | |
| var idx = d.getInteger(K_RADIO_INDEX_ID); | |
| if (idx >= 0 && idx < targetPPIList.length) p.radioIndex = idx; | |
| } | |
| if (d.hasKey(K_UPSCALE_ID)) p.upscaleMethod = d.getString(K_UPSCALE_ID); | |
| if (d.hasKey(K_DOWNSCALE_ID)) p.downMethod = d.getString(K_DOWNSCALE_ID); | |
| } catch (e) { | |
| /* 初回/破損は既定 */ | |
| } | |
| return p; | |
| } | |
| function savePrefsCO_ID(p) { | |
| var d = new ActionDescriptor(); | |
| d.putInteger(K_VER_ID, SCHEMA_VERSION_ID); | |
| d.putBoolean(K_USE_PREV_ID, !!p.usePrev); | |
| d.putInteger(K_RADIO_INDEX_ID, Math.max(0, Math.min(targetPPIList.length - 1, p.radioIndex))); | |
| d.putString(K_UPSCALE_ID, String(p.upscaleMethod)); | |
| d.putString(K_DOWNSCALE_ID, String(p.downMethod)); | |
| app.putCustomOptions(PREF_ID_IDRESIZE, d, true); | |
| } | |
| // 「前回設定値を使用」だけを保存(他は維持) | |
| function saveUsePrevOnly_ID(flag) { | |
| var cur = loadPrefsCO_ID(); | |
| savePrefsCO_ID({ | |
| usePrev: !!flag, | |
| radioIndex: cur.radioIndex, | |
| upscaleMethod: cur.upscaleMethod, | |
| downMethod: cur.downMethod | |
| }); | |
| } | |
| // 日本語向け+欧文の簡易NFC正規化(結合濁点/半濁点+欧文合成文字を合成文字へ) | |
| function toNFCJa(s) { | |
| if (!s) return s; | |
| var map = { | |
| // ===== Japanese: dakuten / handakuten ===== | |
| "カ\u3099": "ガ", | |
| "キ\u3099": "ギ", | |
| "ク\u3099": "グ", | |
| "ケ\u3099": "ゲ", | |
| "コ\u3099": "ゴ", | |
| "サ\u3099": "ザ", | |
| "シ\u3099": "ジ", | |
| "ス\u3099": "ズ", | |
| "セ\u3099": "ゼ", | |
| "ソ\u3099": "ゾ", | |
| "タ\u3099": "ダ", | |
| "チ\u3099": "ヂ", | |
| "ツ\u3099": "ヅ", | |
| "テ\u3099": "デ", | |
| "ト\u3099": "ド", | |
| "ハ\u3099": "バ", | |
| "ヒ\u3099": "ビ", | |
| "フ\u3099": "ブ", | |
| "ヘ\u3099": "ベ", | |
| "ホ\u3099": "ボ", | |
| "ウ\u3099": "ヴ", | |
| "ワ\u3099": "ヷ", | |
| "ヰ\u3099": "ヸ", | |
| "ヱ\u3099": "ヹ", | |
| "ヲ\u3099": "ヺ", | |
| "ハ\u309A": "パ", | |
| "ヒ\u309A": "ピ", | |
| "フ\u309A": "プ", | |
| "ヘ\u309A": "ペ", | |
| "ホ\u309A": "ポ", | |
| "か\u3099": "が", | |
| "き\u3099": "ぎ", | |
| "く\u3099": "ぐ", | |
| "け\u3099": "げ", | |
| "こ\u3099": "ご", | |
| "さ\u3099": "ざ", | |
| "し\u3099": "じ", | |
| "す\u3099": "ず", | |
| "せ\u3099": "ぜ", | |
| "そ\u3099": "ぞ", | |
| "た\u3099": "だ", | |
| "ち\u3099": "ぢ", | |
| "つ\u3099": "づ", | |
| "て\u3099": "で", | |
| "と\u3099": "ど", | |
| "は\u3099": "ば", | |
| "ひ\u3099": "び", | |
| "ふ\u3099": "ぶ", | |
| "へ\u3099": "べ", | |
| "ほ\u3099": "ぼ", | |
| "う\u3099": "ゔ", | |
| "は\u309A": "ぱ", | |
| "ひ\u309A": "ぴ", | |
| "ふ\u309A": "ぷ", | |
| "へ\u309A": "ぺ", | |
| "ほ\u309A": "ぽ", | |
| // ===== Latin: acute U+0301 ===== | |
| "A\u0301": "Á", | |
| "E\u0301": "É", | |
| "I\u0301": "Í", | |
| "O\u0301": "Ó", | |
| "U\u0301": "Ú", | |
| "Y\u0301": "Ý", | |
| "a\u0301": "á", | |
| "e\u0301": "é", | |
| "i\u0301": "í", | |
| "o\u0301": "ó", | |
| "u\u0301": "ú", | |
| "y\u0301": "ý", | |
| // grave U+0300 | |
| "A\u0300": "À", | |
| "E\u0300": "È", | |
| "I\u0300": "Ì", | |
| "O\u0300": "Ò", | |
| "U\u0300": "Ù", | |
| "a\u0300": "à", | |
| "e\u0300": "è", | |
| "i\u0300": "ì", | |
| "o\u0300": "ò", | |
| "u\u0300": "ù", | |
| // circumflex U+0302 | |
| "A\u0302": "Â", | |
| "E\u0302": "Ê", | |
| "I\u0302": "Î", | |
| "O\u0302": "Ô", | |
| "U\u0302": "Û", | |
| "a\u0302": "â", | |
| "e\u0302": "ê", | |
| "i\u0302": "î", | |
| "o\u0302": "ô", | |
| "u\u0302": "û", | |
| // diaeresis U+0308 (umlaut – e.g., ü) | |
| "A\u0308": "Ä", | |
| "E\u0308": "Ë", | |
| "I\u0308": "Ï", | |
| "O\u0308": "Ö", | |
| "U\u0308": "Ü", | |
| "Y\u0308": "Ÿ", | |
| "a\u0308": "ä", | |
| "e\u0308": "ë", | |
| "i\u0308": "ï", | |
| "o\u0308": "ö", | |
| "u\u0308": "ü", | |
| "y\u0308": "ÿ", | |
| // tilde U+0303 | |
| "A\u0303": "Ã", | |
| "N\u0303": "Ñ", | |
| "O\u0303": "Õ", | |
| "a\u0303": "ã", | |
| "n\u0303": "ñ", | |
| "o\u0303": "õ", | |
| // ring above U+030A (e.g., å) | |
| "A\u030A": "Å", | |
| "a\u030A": "å", | |
| // macron U+0304 (āēīōū) | |
| "A\u0304": "Ā", | |
| "E\u0304": "Ē", | |
| "I\u0304": "Ī", | |
| "O\u0304": "Ō", | |
| "U\u0304": "Ū", | |
| "a\u0304": "ā", | |
| "e\u0304": "ē", | |
| "i\u0304": "ī", | |
| "o\u0304": "ō", | |
| "u\u0304": "ū", | |
| // caron U+030C (čšžřň etc.) | |
| "C\u030C": "Č", | |
| "D\u030C": "Ď", | |
| "E\u030C": "Ě", | |
| "N\u030C": "Ň", | |
| "R\u030C": "Ř", | |
| "S\u030C": "Š", | |
| "T\u030C": "Ť", | |
| "Z\u030C": "Ž", | |
| "c\u030C": "č", | |
| "d\u030C": "ď", | |
| "e\u030C": "ě", | |
| "n\u030C": "ň", | |
| "r\u030C": "ř", | |
| "s\u030C": "š", | |
| "t\u030C": "ť", | |
| "z\u030C": "ž", | |
| // dot above U+0307 (ż) | |
| "Z\u0307": "Ż", | |
| "z\u0307": "ż", | |
| // ogonek U+0328 (ą ę) | |
| "A\u0328": "Ą", | |
| "E\u0328": "Ę", | |
| "a\u0328": "ą", | |
| "e\u0328": "ę", | |
| // cedilla U+0327 (ç) | |
| "C\u0327": "Ç", | |
| "c\u0327": "ç" | |
| }; | |
| var out = String(s); | |
| for (var k in map) { | |
| out = out.split(k).join(map[k]); | |
| } | |
| return out; | |
| } | |
| // 上の正規化関数をBridgeTalk本文に埋め込むためのソース文字列 | |
| var NFC_HELPER_SRC = toNFCJa.toString(); | |
| // パス正規化: toNFCJa + File.resolve + fsName を統一的に適用する関数 | |
| function _normPathLocal(p) { | |
| try { | |
| var f = new File(p); | |
| try { | |
| f = f.resolve(); | |
| } catch (_e) {}; | |
| var s = f.fsName; | |
| return toNFCJa(s); | |
| } catch (e) { | |
| try { | |
| return toNFCJa(String(p)); | |
| } catch (e2) { | |
| return String(p); | |
| } | |
| } | |
| } | |
| var NORM_HELPER_SRC = _normPathLocal.toString(); | |
| function main() { | |
| // 前提チェック: ドキュメント未オープンなら中止 | |
| if (!app.documents.length) { | |
| alert("開いているドキュメントがありません。"); | |
| return; | |
| } | |
| var doc = app.activeDocument; | |
| var imgPath = _normPathLocal(doc.fullName.fsName); // 送信前に toNFCJa + resolve + fsName で正規化 | |
| var docWidthPx = doc.width.as("px"); | |
| var docHeightPx = doc.height.as("px"); | |
| var currentPPI = doc.resolution; | |
| // ----- BridgeTalk: InDesignへリンク状態と配置スケール(absoluteHorizontalScale)を問い合わせ | |
| var bt = new BridgeTalk(); | |
| bt.target = "indesign"; | |
| var idSideSrc = inDesignSide.toString() | |
| .replace("/*__INJECT_TO_NFC_JA__*/", "var toNFCJa = " + NFC_HELPER_SRC + ";\nvar _normPath = " + NORM_HELPER_SRC + ";"); | |
| bt.body = "(" + idSideSrc + ")(" + encodeURI(imgPath).toSource() + ");"; // InDesign側関数を文字列化し、エンコード済みパスを引数に即時実行 | |
| // 応答処理: { absScale(%), linkStatus(0=正常,1=切れ,2=未更新) } | |
| bt.onResult = function(res) { | |
| var data = res.body; | |
| if (!data || data == "null") { | |
| alert("InDesignで該当リンク画像が見つかりません。"); | |
| return; | |
| } | |
| var obj = eval('(' + data + ')'); | |
| var absScale = obj.absScale; // 最大 absoluteHorizontalScale (%) | |
| var effectivePPI = currentPPI * 100 / absScale; // 実効ppi = 画像ppi / (配置スケール[%]/100) | |
| var linkStatus = obj.linkStatus; // 0:正常、1:リンク切れ、2:未更新 | |
| // 安全策: リンク異常は処理中止 | |
| if (linkStatus === 1) { | |
| alert("リンク切れ画像です。"); | |
| return; | |
| } else if (linkStatus === 2) { | |
| alert("リンクが更新されていません。"); | |
| return; | |
| } | |
| // InDesign配置の長辺(mm) = Photoshop長辺(px) × 配置スケール / 現在ppi × 25.4 | |
| var longPx = Math.max(docWidthPx, docHeightPx); | |
| var placedLongMM = longPx * (absScale / 100) / currentPPI * 25.4; | |
| // 指定ppiで必要となる長辺ピクセル数を算出 | |
| function calcRequiredPx(longMM, ppi) { | |
| return Math.round(longMM * ppi / 25.4); | |
| } | |
| // ダイアログ本文(配置サイズ・現在ppi・配置スケール・現在px) | |
| var messageBase = "InDesign配置サイズ(長辺): " + placedLongMM.toFixed(2) + " mm\n" + | |
| "画像ppi: " + currentPPI + "(実効ppi: " + effectivePPI.toFixed(2) + ")\n" + | |
| "配置スケール: " + absScale.toFixed(3) + " %\n" + | |
| "画像ピクセル: " + longPx + "\n"; | |
| // 確認ダイアログを表示し、ppiと拡大/縮小メソッドを取得 | |
| var dialogResult = showConfirmDialog(messageBase, placedLongMM, longPx, imgPath); | |
| try { | |
| if (dialogResult && dialogResult.hasOwnProperty('usePrev')) saveUsePrevOnly_ID(dialogResult.usePrev); | |
| } catch (_) {} | |
| if (!dialogResult || dialogResult.cancelled) return; | |
| var targetPPI = dialogResult.ppi; | |
| var upscaleMethod = dialogResult.method; | |
| var downscaleMethod = dialogResult.downMethod; | |
| // 出力解像度に合わせた新規ピクセル寸法(縦横比維持) | |
| var newLongPx = calcRequiredPx(placedLongMM, targetPPI); | |
| var scaleRatio = newLongPx / longPx; | |
| var newWidthPx, newHeightPx; | |
| if (docWidthPx >= docHeightPx) { | |
| newWidthPx = newLongPx; | |
| newHeightPx = Math.round(docHeightPx * scaleRatio); | |
| } else { | |
| newHeightPx = newLongPx; | |
| newWidthPx = Math.round(docWidthPx * scaleRatio); | |
| } | |
| // 縮小/拡大でリサンプルメソッドを分岐 | |
| if (scaleRatio < 1) { | |
| // 縮小 | |
| if (downscaleMethod === "bicubic") { | |
| doc.resizeImage(UnitValue(newWidthPx, "px"), UnitValue(newHeightPx, "px"), targetPPI, ResampleMethod.BICUBIC); | |
| } else if (downscaleMethod === "nearestNeighbor") { | |
| doc.resizeImage(UnitValue(newWidthPx, "px"), UnitValue(newHeightPx, "px"), targetPPI, ResampleMethod.NEARESTNEIGHBOR); | |
| } else { | |
| doc.resizeImage(UnitValue(newWidthPx, "px"), UnitValue(newHeightPx, "px"), targetPPI, ResampleMethod.BICUBIC); | |
| } | |
| } else { | |
| // 拡大 | |
| if (upscaleMethod === "deepUpscale") { | |
| var desc = new ActionDescriptor(); | |
| desc.putUnitDouble(charIDToTypeID('Wdth'), charIDToTypeID('#Pxl'), newWidthPx); | |
| desc.putUnitDouble(charIDToTypeID('Hght'), charIDToTypeID('#Pxl'), newHeightPx); | |
| desc.putUnitDouble(charIDToTypeID('Rslt'), charIDToTypeID('#Rsl'), targetPPI); | |
| desc.putBoolean(stringIDToTypeID('scaleStyles'), true); | |
| desc.putEnumerated(charIDToTypeID('Intr'), charIDToTypeID('Intp'), stringIDToTypeID('deepUpscale')); | |
| executeAction(charIDToTypeID('ImgS'), desc, DialogModes.NO); | |
| } else if (upscaleMethod === "preserveDetails") { | |
| doc.resizeImage(UnitValue(newWidthPx, "px"), UnitValue(newHeightPx, "px"), targetPPI, ResampleMethod.PRESERVEDETAILS); | |
| } else if (upscaleMethod === "nearestNeighbor") { | |
| doc.resizeImage(UnitValue(newWidthPx, "px"), UnitValue(newHeightPx, "px"), targetPPI, ResampleMethod.NEARESTNEIGHBOR); | |
| } else { | |
| doc.resizeImage(UnitValue(newWidthPx, "px"), UnitValue(newHeightPx, "px"), targetPPI, ResampleMethod.BICUBIC); | |
| } | |
| } | |
| // 完了後は100%表示に切替 | |
| app.runMenuItem(stringIDToTypeID("actualPixels")); | |
| }; | |
| // 通信エラー(InDesign未起動/応答なし等) | |
| bt.onError = function(e) { | |
| alert("InDesign通信エラー: " + e.body); | |
| }; | |
| bt.send(30); // 30秒タイムアウト | |
| } | |
| // InDesign側: 一致するリンクの最大absoluteHorizontalScale(%)とリンク状態を返す | |
| function inDesignSide(imgPath) { | |
| var decodedPath = decodeURI(imgPath); // Photoshopから渡されたURIをファイルパスへ | |
| /*__INJECT_TO_NFC_JA__*/ | |
| decodedPath = toNFCJa(decodedPath); | |
| // さらに実体解決 + fsName までそろえる | |
| try { | |
| decodedPath = _normPath(decodedPath); | |
| } catch (_e) {} | |
| if (app.documents.length === 0) return null; | |
| var doc = app.activeDocument; | |
| var links = doc.links; | |
| // 走査: 一致リンクのうち最大スケールを採用(複数配置に対応) | |
| var mlMax = 0; | |
| var found = false; | |
| var linkStat = -1; // 0=正常,1=切れ,2=未更新,-1=該当なし | |
| for (var i = 0; i < links.length; i++) { | |
| var link = links[i]; | |
| var lnkNorm = _normPath(link.filePath); | |
| var matched = (lnkNorm === decodedPath); | |
| if (!matched) { | |
| // フォールバック: ファイル名(正規化後)一致 && サイズ一致 | |
| try { | |
| var lf = new File(link.filePath); | |
| var tf = new File(decodedPath); | |
| var nameEq = toNFCJa(lf.name) === toNFCJa(tf.name); | |
| var lenEq = (lf.length === tf.length && lf.length > 0); | |
| if (nameEq && lenEq) matched = true; | |
| } catch (__e) {} | |
| } | |
| if (matched) { | |
| found = true; | |
| var parent = link.parent; | |
| if (link.status == LinkStatus.NORMAL) { | |
| var ml = parent.absoluteHorizontalScale; | |
| if (ml > mlMax) { | |
| mlMax = ml; | |
| linkStat = 0; | |
| } | |
| // 削除: 中央表示・選択・ズームの副作用ブロック | |
| } else if (link.status == LinkStatus.LINK_MISSING) { | |
| linkStat = 1; | |
| } else { | |
| linkStat = 2; | |
| } | |
| } | |
| } | |
| if (!found) return null; // 一致するリンクなし | |
| // JSON文字列として返却(BridgeTalk経由でPhotoshopに渡す) | |
| return '{"absScale":' + mlMax + ',"linkStatus":' + linkStat + '}'; | |
| } | |
| // リサイズ確認ダイアログ: 画像情報(非ボールド)+ 配置情報(ボールド)+ ppi/メソッド選択 + 警告 | |
| function showConfirmDialog(messageBase, placedLongMM, longPx, imgPath) { | |
| // ----- UI: 全体レイアウトと画像情報パネル | |
| var dlg = new Window("dialog", "画像リサイズ確認"); | |
| dlg.orientation = "column"; | |
| dlg.alignChildren = ["fill", "top"]; | |
| dlg.margins = 15; | |
| var infoPanel = dlg.add("panel", undefined, "画像情報"); | |
| infoPanel.orientation = "column"; | |
| infoPanel.alignChildren = ["fill", "top"]; | |
| infoPanel.margins = [10, 12, 10, 12]; // 画像情報(ファイル名/フォルダ/カラーモード)と配置情報の見出し | |
| var doc = app.activeDocument; | |
| // カラーモード表記の整形 | |
| function modeToString(m) { | |
| switch (m) { | |
| case DocumentMode.BITMAP: | |
| return "Bitmap"; | |
| case DocumentMode.GRAYSCALE: | |
| return "Grayscale"; | |
| case DocumentMode.INDEXEDCOLOR: | |
| return "Indexed"; | |
| case DocumentMode.RGB: | |
| return "RGB"; | |
| case DocumentMode.CMYK: | |
| return "CMYK"; | |
| case DocumentMode.LAB: | |
| return "Lab"; | |
| case DocumentMode.MULTICHANNEL: | |
| return "Multichannel"; | |
| case DocumentMode.DUOTONE: | |
| return "Duotone"; | |
| default: | |
| return "Unknown"; | |
| } | |
| } | |
| // 上段(非ボールド): ファイル名・フォルダ・カラーモード(プロファイル) | |
| var fileNamePS = doc.name; | |
| var filePathPS; | |
| try { | |
| filePathPS = doc.fullName.parent.fsName; | |
| } catch (e) { | |
| filePathPS = "(未保存)"; | |
| } | |
| var colorProfilePS; | |
| try { | |
| colorProfilePS = doc.colorProfileName; | |
| } catch (e2) { | |
| colorProfilePS = "(不明)"; | |
| } | |
| var colorModePS = modeToString(doc.mode); | |
| var headInfo = | |
| "ファイル名: " + fileNamePS + "\n" + | |
| "フォルダ: " + filePathPS + "\n" + | |
| "カラーモード:" + colorModePS + "(" + colorProfilePS + ")\n"; | |
| var infoText = infoPanel.add("statictext", undefined, headInfo, { | |
| multiline: true | |
| }); | |
| infoText.minimumSize.width = 400; | |
| // 下段(ボールド): 配置サイズ・現在ppi・配置スケール・画像px | |
| var msgText = infoPanel.add("statictext", undefined, messageBase, { | |
| multiline: true | |
| }); | |
| msgText.minimumSize.width = 400; | |
| try { | |
| msgText.graphics.font = ScriptUI.newFont(msgText.graphics.font.name, 'Bold', msgText.graphics.font.size); | |
| } catch (e) {} | |
| // 指定解像度(タイトル付きパネル) | |
| var radioGrp = dlg.add("panel", undefined, "指定解像度"); | |
| radioGrp.orientation = "row"; | |
| radioGrp.alignChildren = ["left", "center"]; | |
| radioGrp.margins = [10, 12, 10, 12]; | |
| var radioButtons = []; | |
| for (var i = 0; i < targetPPIList.length; i++) { | |
| var rb = radioGrp.add("radiobutton", undefined, String(targetPPIList[i])); | |
| radioButtons.push(rb); | |
| } | |
| // 前回値の読み込み | |
| var coPrefsID = loadPrefsCO_ID(); | |
| // 既定(PPIは先頭、up: deepUpscale、down: bicubic) | |
| var initialRadioIndex = 0; | |
| var initialUpscale = "deepUpscale"; | |
| var initialDownscale = "bicubic"; | |
| if (coPrefsID.usePrev) { | |
| initialRadioIndex = (coPrefsID.radioIndex >= 0 && coPrefsID.radioIndex < targetPPIList.length) ? coPrefsID.radioIndex : 0; | |
| initialUpscale = coPrefsID.upscaleMethod || initialUpscale; | |
| initialDownscale = coPrefsID.downMethod || initialDownscale; | |
| } | |
| for (var rbi = 0; rbi < radioButtons.length; rbi++) radioButtons[rbi].value = (rbi === initialRadioIndex); | |
| // 拡大メソッド | |
| var methodPanel = dlg.add("panel", undefined, "拡大メソッド"); | |
| methodPanel.orientation = "row"; | |
| methodPanel.alignChildren = ["left", "center"]; | |
| methodPanel.margins = [10, 12, 10, 12]; | |
| var methodRadioButtons = []; | |
| var methodLabels = [ | |
| "ディテールを保持2.0(推奨)", | |
| "ディテールを保持(旧)", | |
| "ニアレストネイバー" | |
| ]; | |
| var methodValues = [ | |
| "deepUpscale", | |
| "preserveDetails", | |
| "nearestNeighbor" | |
| ]; | |
| var defaultMethodIndex = 0; | |
| for (var i = 0; i < methodLabels.length; i++) { | |
| var rb = methodPanel.add("radiobutton", undefined, methodLabels[i]); | |
| methodRadioButtons.push(rb); | |
| rb.value = (methodValues[i] === initialUpscale); | |
| } | |
| // 縮小メソッド | |
| var downMethodPanel = dlg.add("panel", undefined, "縮小メソッド"); | |
| downMethodPanel.orientation = "row"; | |
| downMethodPanel.alignChildren = ["left", "center"]; | |
| downMethodPanel.margins = [10, 12, 10, 12]; | |
| var downMethodRadioButtons = []; | |
| var downMethodLabels = [ | |
| "バイキュービック(滑らか)", | |
| "ニアレストネイバー" | |
| ]; | |
| var downMethodValues = [ | |
| "bicubic", | |
| "nearestNeighbor" | |
| ]; | |
| var defaultDownMethodIndex = 0; | |
| for (var i = 0; i < downMethodLabels.length; i++) { | |
| var rb = downMethodPanel.add("radiobutton", undefined, downMethodLabels[i]); | |
| downMethodRadioButtons.push(rb); | |
| rb.value = (downMethodValues[i] === initialDownscale); | |
| } | |
| // 拡縮率の現在値表示(ボールド) | |
| var scaleText = dlg.add("statictext", undefined, ""); | |
| scaleText.minimumSize.width = 400; | |
| try { | |
| scaleText.graphics.font = ScriptUI.newFont(scaleText.graphics.font.name, 'Bold', scaleText.graphics.font.size); | |
| } catch (e) {} | |
| // 警告エリア(背景は変更せず、テキストのみ表示) | |
| var warnText = dlg.add("statictext", undefined, " ", { | |
| multiline: true | |
| }); | |
| warnText.preferredSize = [400, 80]; | |
| warnText.minimumSize = [400, 80]; | |
| warnText.alignment = ["fill", "top"]; | |
| try { | |
| warnText.graphics.font = ScriptUI.newFont(warnText.graphics.font.name, 'Bold', warnText.graphics.font.size); | |
| } catch (e) {} | |
| // UI更新: 選択ppi→必要px→拡縮率→しきい値警告 | |
| function updateUI() { | |
| var selectedPPI = null; | |
| for (var i = 0; i < radioButtons.length; i++) { | |
| if (radioButtons[i].value) { | |
| selectedPPI = parseInt(radioButtons[i].text); | |
| break; | |
| } | |
| } | |
| var requiredPx = Math.round(placedLongMM * selectedPPI / 25.4); | |
| var scale = requiredPx / longPx; | |
| var scalePct = scale * 100; | |
| scaleText.text = "拡縮率: " + scalePct.toFixed(2) + " %"; | |
| // 注意: Bitmap(2値)専用警告(Bitmap時はスケール警告を抑止) + それ以外は無駄拡縮/過大拡大 | |
| var warnMsg = ""; | |
| var warnColor = null; | |
| var isBitmap = false; | |
| try { | |
| if (doc && doc.mode == DocumentMode.BITMAP) { | |
| isBitmap = true; | |
| } | |
| } catch (_) {} | |
| if (isBitmap) { | |
| // Bitmap(2値)時は専用警告のみ表示(スケール警告は出さない) | |
| warnMsg = "【注意:キャンセル推奨】\n2値画像のリサイズは、モアレが発生しやすいため推奨しません。\nリサンプルする場合は2,400ppi以下、600ppi以上を選択してください。"; | |
| warnColor = [1, 0, 0, 1]; | |
| } else { | |
| // 既存のしきい値警告(Bitmap以外) | |
| if (scale >= efScaleMin && scale <= efScaleMax) { | |
| warnMsg += (warnMsg ? "\n" : "") + "【注意:キャンセルを推奨】\n 拡大率が " + scalePct.toFixed(2) + "% です。無駄な拡縮で、余計な画像劣化の可能性があります。"; | |
| warnColor = [1, 0.4, 0, 1]; | |
| } | |
| if (scale > scaleMax) { | |
| warnMsg += (warnMsg ? "\n" : "") + "【警告:キャンセルを推奨】\n 拡大率が " + (scaleMax * 100).toFixed(0) + "% を超えています。\nPhotoshop以外の手段を検討してください。"; | |
| warnColor = [1, 0, 0, 1]; | |
| } | |
| } | |
| warnText.text = warnMsg; | |
| if (warnColor) warnText.graphics.foregroundColor = warnText.graphics.newPen(warnText.graphics.PenType.SOLID_COLOR, warnColor, 1); | |
| else warnText.graphics.foregroundColor = warnText.graphics.newPen(warnText.graphics.PenType.SOLID_COLOR, [0, 0, 0, 1], 1); | |
| try { | |
| warnText.window.update(); | |
| } catch (e) {} | |
| } | |
| updateUI(); | |
| for (var i = 0; i < radioButtons.length; i++) { | |
| radioButtons[i].onClick = updateUI; | |
| } | |
| // ボタン群と既定キー(Enter=OK / Esc=Cancel / I=InDesignで表示) | |
| var usePrevGrp = dlg.add('group'); | |
| usePrevGrp.alignment = ['left', 'center']; | |
| var cbUsePrev = usePrevGrp.add('checkbox', undefined, '前回設定値を使用'); | |
| cbUsePrev.value = coPrefsID.usePrev === true; | |
| function applyFromPrefs_ID(p) { | |
| var ri = (p.radioIndex >= 0 && p.radioIndex < radioButtons.length) ? p.radioIndex : 0; | |
| for (var i = 0; i < radioButtons.length; i++) radioButtons[i].value = (i === ri); | |
| for (var j = 0; j < methodRadioButtons.length; j++) methodRadioButtons[j].value = (methodValues[j] === p.upscaleMethod); | |
| for (var k = 0; k < downMethodRadioButtons.length; k++) downMethodRadioButtons[k].value = (downMethodValues[k] === p.downMethod); | |
| updateUI(); | |
| } | |
| function applyDefaultsNow_ID() { | |
| for (var i = 0; i < radioButtons.length; i++) radioButtons[i].value = (i === 0); // PPI先頭 | |
| for (var j = 0; j < methodRadioButtons.length; j++) methodRadioButtons[j].value = (methodValues[j] === 'deepUpscale'); | |
| for (var k = 0; k < downMethodRadioButtons.length; k++) downMethodRadioButtons[k].value = (downMethodValues[k] === 'bicubic'); | |
| updateUI(); | |
| } | |
| cbUsePrev.onClick = function() { | |
| if (cbUsePrev.value) applyFromPrefs_ID(coPrefsID); | |
| else applyDefaultsNow_ID(); | |
| }; | |
| var btnGrp = dlg.add("group"); | |
| btnGrp.alignment = "center"; | |
| btnGrp.margins = [0, 15, 0, 0]; | |
| var okBtn = btnGrp.add("button", undefined, "処理する (Enter)"); | |
| var cancelBtn = btnGrp.add("button", undefined, "キャンセル (Esc)"); | |
| var idBtn = btnGrp.add("button", undefined, "InDesignで表示 (I)"); | |
| // 既定キー設定(Enter=OK, Esc=Cancel) | |
| okBtn.properties = { | |
| name: 'ok' | |
| }; | |
| cancelBtn.properties = { | |
| name: 'cancel' | |
| }; | |
| okBtn.active = true; | |
| dlg.defaultElement = okBtn; | |
| dlg.cancelElement = cancelBtn; | |
| okBtn.onClick = function() { | |
| var selRadioIdx = 0; | |
| for (var i = 0; i < radioButtons.length; i++) { | |
| if (radioButtons[i].value) { | |
| selRadioIdx = i; | |
| break; | |
| } | |
| } | |
| var selUpscale = null; | |
| for (var j = 0; j < methodRadioButtons.length; j++) { | |
| if (methodRadioButtons[j].value) { | |
| selUpscale = methodValues[j]; | |
| break; | |
| } | |
| } | |
| var selDown = null; | |
| for (var k = 0; k < downMethodRadioButtons.length; k++) { | |
| if (downMethodRadioButtons[k].value) { | |
| selDown = downMethodValues[k]; | |
| break; | |
| } | |
| } | |
| try { | |
| savePrefsCO_ID({ | |
| usePrev: cbUsePrev.value === true, | |
| radioIndex: selRadioIdx, | |
| upscaleMethod: selUpscale || 'deepUpscale', | |
| downMethod: selDown || 'bicubic' | |
| }); | |
| } catch (_) {} | |
| dlg.close(1); | |
| }; | |
| cancelBtn.onClick = function() { | |
| try { | |
| saveUsePrevOnly_ID(cbUsePrev.value); | |
| } catch (_) {} | |
| dlg.close(0); | |
| }; | |
| // InDesign側で該当リンクを選択・表示(可能なら対象ページへ移動) | |
| idBtn.onClick = function() { | |
| var btShow = new BridgeTalk(); | |
| btShow.target = "indesign"; | |
| btShow.onError = function(e) { | |
| alert("InDesign通信エラー: " + e.body); | |
| }; | |
| // BridgeTalk本文テンプレ: 正規化関数はプレースホルダに注入 | |
| var _idShowSrc = (function(ip) { | |
| var decoded = decodeURI(ip); | |
| /*__INJECT_TO_NFC_JA__*/ | |
| decoded = toNFCJa(decoded); | |
| try { | |
| decoded = _normPath(decoded); | |
| } catch (_e) {} | |
| app.activate(); | |
| if (app.documents.length === 0) throw new Error('InDesign: ドキュメントが開かれていません'); | |
| var doc = app.activeDocument; | |
| var links = doc.links; | |
| for (var i = 0; i < links.length; i++) { | |
| var lk = links[i]; | |
| var lnkNorm = _normPath(lk.filePath); | |
| var matched = (lnkNorm === decoded); | |
| if (!matched) { | |
| try { | |
| var lf = new File(lk.filePath); | |
| var tf = new File(decoded); | |
| var nameEq = toNFCJa(lf.name) === toNFCJa(tf.name); | |
| var lenEq = (lf.length === tf.length && lf.length > 0); | |
| if (nameEq && lenEq) matched = true; | |
| } catch (__e) {} | |
| } | |
| if (matched) { | |
| var it = lk.parent; | |
| it.select(); | |
| if (it.parentPage) doc.layoutWindows[0].activePage = it.parentPage; | |
| // 中央表示(ユーザー操作時のみ) | |
| try { | |
| var win = doc.layoutWindows[0]; | |
| win.zoom(ZoomOptions.FIT_PAGE); | |
| win.zoom(ZoomOptions.SHOW_SELECTION); | |
| } catch (_z) {} | |
| return; | |
| } | |
| } | |
| throw new Error('InDesign: リンクが見つかりません: ' + decoded); | |
| }).toString().replace("/*__INJECT_TO_NFC_JA__*/", "var toNFCJa = " + NFC_HELPER_SRC + ";\nvar _normPath = " + NORM_HELPER_SRC + ";"); | |
| btShow.body = "(" + _idShowSrc + ")(" + (encodeURI(imgPath)).toSource() + ")"; | |
| btShow.send(); | |
| try { | |
| saveUsePrevOnly_ID(cbUsePrev.value); | |
| } catch (_) {} | |
| try { | |
| dlg.close(2); | |
| } catch (e) { | |
| dlg.close(); | |
| } // 終了(戻り値2=キャンセル扱い) | |
| }; | |
| // キーボードショートカットの割当 | |
| dlg.addEventListener('keydown', function(k) { | |
| try { | |
| var n = String(k.keyName || '').toUpperCase(); | |
| if (n === 'ENTER' || n === 'RETURN') { | |
| okBtn.notify('onClick'); | |
| k.preventDefault(); | |
| } else if (n === 'I') { | |
| idBtn.notify('onClick'); | |
| k.preventDefault(); | |
| } | |
| } catch (e) {} | |
| }); | |
| // ダイアログ表示し、OKなら選択値を返す。キャンセルやクローズでも usePrev を返す。 | |
| var ok = (dlg.show() === 1); | |
| var ret; | |
| if (ok) { | |
| var ppi = null, | |
| method = null, | |
| downMethod = null; | |
| for (var i = 0; i < radioButtons.length; i++) | |
| if (radioButtons[i].value) ppi = parseInt(radioButtons[i].text); | |
| for (var i = 0; i < methodRadioButtons.length; i++) | |
| if (methodRadioButtons[i].value) method = methodValues[i]; | |
| for (var i = 0; i < downMethodRadioButtons.length; i++) | |
| if (downMethodRadioButtons[i].value) downMethod = downMethodValues[i]; | |
| ret = { | |
| ppi: ppi, | |
| method: method, | |
| downMethod: downMethod, | |
| usePrev: !!cbUsePrev.value, | |
| cancelled: false | |
| }; | |
| } else { | |
| ret = { | |
| usePrev: !!cbUsePrev.value, | |
| cancelled: true | |
| }; | |
| } | |
| return ret; | |
| } | |
| // 実行開始 | |
| main(); |
InDesignに配置したサイズで、実効解像度にリサイズするスクリプトです。
① InDesignドキュメントを開く
② InDesignに配置したリンク画像をPhotoshopで開く
③ Photoshopでスクリプトを実行する