イラストACにてイラストも公開中。まずはここのブログでチェック!!欲しいイラストがあれば無料でダウンロード出来ますのでDownloadへGo!

【2026年最新版対応】InDesignが落ちる・戻れない・起動しない時の救急箱Indesign_Doctor! 1クリック解決ツールをPythonで開発

【2026年最新版対応】InDesignが落ちる・戻れない・起動しない時の救急箱Indesign_Doctor! 1クリック解決ツールをPythonで開発

こんにちは、DTPの現場で戦う皆さん。2026/07/02現在バージョンアップしました。

「締め切り前なのにInDesignが落ちた!」
「再起動しても、特定のファイルを開くとまた落ちる(無限ループ)」
「なぜか『取り消し(Ctrl+Z)』が3〜4回しかできない…(これは2026/01/31現在9割の確率で下記に記した事が原因)」

こんな絶望的な経験、ありませんか?
InDesignは高機能ゆえに、長く使っているとどうしても「見えないゴミ」が溜まり、動作が不安定になります。

ネットで検索すると「環境設定をリセットせよ」と出てきますが、ショートカットキー(Ctrl+Alt+Shift)の同時押しはタイミングが難しいし、せっかく作ったワークスペースショートカット設定まで消えてしまうのが怖くて、なかなか踏み切れないですよね。私なんて、会社内では、やたらと起動中に落ちる。と言われたり、パネルが真っ白になった。等いろいろな不具合が出てます。

そこで今回、InDesignの不調を安全かつ確実に直すための救急箱ツール をPythonで自作しました。

今回は、そのツールの機能解説と、誰でも使えるように全ソースコードを公開します。(コードの更新は気まぐれです)
特に最近のバグが原因じゃない人で「軽いデータなのに数回しかUndo(取り消し)できない」という方は必見です。

2026年現在の環境では、ローカルの不具合よりもクラウド同期のタイムアウトが原因で「起動すらできない」ケースが増えているようです。
フォントをたくさんいれている方は解決法2をご覧ください。

↓↓↓↓ EXEのダウンロードは ↓↓↓↓
V2.3 Download
(ちなみにアイコンはめんどくさくて作ってませんのでご了承を)


1. なぜInDesignは不安定になるのか?

PCのスペックは足りているはずなのに、なぜInDesignは重くなったり落ちたりするのでしょうか?
主な原因は以下の3つに集約されます。

A. 「SavedData」の肥大化・破損

InDesignは、操作の履歴(Undo情報)やウィンドウ位置などをInDesign SavedDataという一時ファイルに記録しています。
これが長年の使用で壊れると、「メモリはあるのに履歴を書き込めない」という状態になります。
「軽いデータなのに5回しか戻れない」という症状は、十中八九これが犯人です。

B. フォントの破損・競合

フリーフォントや古いフォントの中には、データ構造が微妙に壊れているものがあります。InDesignはフォントの描画にシビアなので、そういった「爆弾フォント」を読み込んだ瞬間にクラッシュします。

C. 復元データの破損

作業中に落ちた際、InDesignは「復元データ」を作ります。しかし、落ちた原因そのものが復元データに含まれている場合、次回起動時にそれを読み込んでまた落ちる…という「無限クラッシュループ」が発生します。

これらを手動でメンテナンスするのは非常に面倒です。そこで、これらを自動で解決するツールを作りました。


2. 自作ツール「InDesign救急箱」の機能

今回作成したツールは、GUI(画面)で操作できるWindows/Mac両対応のアプリです。Macで試していませんが、Wineが必要かも?
主な機能は以下の4つです。っと。リセットや検査をする前に必ずindesignは閉じてくださいね。

画像のバージョンは古いものです

① 壊れたフォントの精密検査&隔離

指定したフォントフォルダをスキャンし、以下の2段階でチェックします。

  1. 構造チェック: ファイルとして壊れていないか?

  2. 描画負荷テスト: 実際にメモリ上で「Testあ亜!?」のような文字を描画できるか?

特に2番目が重要です。「ファイルとしては開けるけど、日本語データが壊れている」といった隠れ危険フォントも見つけ出します。
さらに、TTC(TrueType Collection)形式の全フォントチェックにも対応。
検知された危険なフォントは、ボタン一つで「ゴミ箱」へ移動できます(完全削除ではないので安心です)。

② キャッシュ削除(表示バグ対策)

PC内のAdobe関連フォルダから、トラブルの元凶となりやすいAdobeFnt*.lst(フォントリストキャッシュ)を一括検索して削除します。
「文字が化ける」「フォントメニューがおかしい」といった軽微なトラブルはこれで直ります。

③-A 環境設定リセット(設定保護機能付き!)

これが今回の目玉機能です。
InDesignの挙動がおかしい時、公式推奨の対処法は「環境設定のリセット」ですが、これをやるとワークスペースやショートカットが初期化されてしまいます。

このツールでは、リセットを行う前に**「Workspaces」と「Shortcut Sets」フォルダを自動でバックアップ**します。
その後、不具合の原因であるInDesign DefaultsInDesign SavedDataだけをリネーム(.old化)して無効化します。

InDesign Defaultsの保存場所
C:\Users\(ユーザー名)\AppData\Roaming\Adobe\InDesign\Version 20.0-J\ja_JP

InDesign SavedDataの保存場所
C:\Users\(ユーザー名)\AppData\Local\Adobe\InDesign\Version 20.0-J\ja_JP\Caches

Version 20.0-Jの部分は、お使いのInDesignのバージョンによって数字が異なります(例:19.0、21.0など)。

つまり、「不具合は直るけど、俺の使いやすい設定は守られる(またはすぐに戻せる)」 という、いいとこ取りのリセットが可能です。
もちろん、インストールされている全バージョンのInDesignを自動検知して、特定のバージョンだけをリセットできます。

-B バージョン

製品名(InDesign 2024など)表示はわかりやすくしております。日本語環境(-Jサフィックス)の自動検出にも対応しています。

③-C 復元データの強制削除(クラッシュループ脱出)

InDesignが起動すらしなくなった時のための「最終奥義」です。
InDesign Recoveryフォルダの中身を強制的に空にします。
これにより、壊れた復元データを読み込もうとして落ちるループから脱出できます。

③-D バックアップからの「復元機能」

バックアップからの復元をクリックし、復元ポイントを選択して実行すれば復元も可能です。
対象の設定フォルダを開けばどこにあるのかもわかります。

3. 【コード公開】Pythonで作るInDesign救急箱

では、実際のPythonコードを公開します。作ってみたい方はある程度知識があると思いますので、詳しい説明は省きます。
まずはGUIライブラリのtkinterを使い、ファイル操作やフォント解析を行っています。

必要なライブラリ:
事前に以下のコマンドでライブラリをインストールしてください。

Bash

pip install fonttools pillow send2trash

ソースコード (indesign_doctor.py):

Python

import os
import threading
import tkinter as tk
import platform
import fnmatch
import datetime
import shutil
import subprocess
from tkinter import ttk, filedialog, scrolledtext, messagebox

# ゴミ箱ライブラリ
try:
    from send2trash import send2trash
except ImportError:
    def send2trash(path):
        os.remove(path)

from fontTools.ttLib import TTFont, TTCollection
from PIL import Image, ImageFont, ImageDraw

class FontCheckerApp:
    # バージョンと製品名の対応テーブル
    VERSION_MAP = {
        "Version 21.0": "InDesign 2026",
        "Version 20.0": "InDesign 2025",
        "Version 19.0": "InDesign 2024",
        "Version 18.0": "InDesign 2023",
        "Version 17.0": "InDesign 2022",
        "Version 16.0": "InDesign 2021",
        "Version 15.0": "InDesign 2020",
        "Version 14.0": "InDesign CC 2019",
        "Version 13.0": "InDesign CC 2018",
        "Version 12.0": "InDesign CC 2017",
    }

    def __init__(self, root):
        self.root = root
        self.root.title("InDesign救急箱 (Ultimate V2.3)")
        self.root.geometry("520x680")

        style = ttk.Style()
        style.configure("Bold.TButton", font=("Meiryo UI", 9, "bold"))
        style.configure("Danger.TButton", foreground="red", font=("Meiryo UI", 9, "bold"))
        style.configure("Blue.TButton", foreground="blue", font=("Meiryo UI", 9, "bold"))

        # =========================================
        #  ① フォント検査エリア
        # =========================================
        frame_check = ttk.LabelFrame(root, text="① フォントファイルの健康診断", padding=10)
        frame_check.pack(fill=tk.X, padx=10, pady=5)
        
        frame_path = ttk.Frame(frame_check)
        frame_path.pack(fill=tk.X)
        ttk.Label(frame_path, text="対象フォルダ:").pack(side=tk.LEFT)
        self.entry_path = ttk.Entry(frame_path)
        self.entry_path.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
        self.entry_path.insert(0, self.get_system_font_folder())
        ttk.Button(frame_path, text="参照...", command=self.select_folder).pack(side=tk.LEFT)

        self.btn_start = ttk.Button(frame_check, text="フォント検査を実行", command=self.start_check_thread, style="Bold.TButton")
        self.btn_start.pack(fill=tk.X, pady=5)
        
        self.btn_delete = ttk.Button(frame_check, text="検知されたファイルをゴミ箱へ移動", command=self.confirm_trash_files, style="Danger.TButton", state='disabled')
        self.btn_delete.pack(fill=tk.X, pady=5)
        
        self.progress = ttk.Progressbar(frame_check, orient=tk.HORIZONTAL, mode='determinate')
        self.progress.pack(fill=tk.X)
        self.lbl_status = ttk.Label(frame_check, text="待機中...")
        self.lbl_status.pack()

        # =========================================
        #  ② キャッシュ削除エリア
        # =========================================
        frame_cache = ttk.LabelFrame(root, text="② おかしくなったらまずコレ (キャッシュ削除)", padding=10)
        frame_cache.pack(fill=tk.X, padx=10, pady=5)
        
        ttk.Label(frame_cache, text="※「AdobeFnt*.lst」を削除します。表示がおかしい時に有効。", foreground="#555").pack(anchor=tk.W)
        self.btn_cache = ttk.Button(frame_cache, text="フォントキャッシュを削除", command=self.confirm_delete_cache)
        self.btn_cache.pack(fill=tk.X, pady=2)

        # =========================================
        #  ③ 環境設定 & 復元データ (バージョン指定)
        # =========================================
        frame_reset = ttk.LabelFrame(root, text="③ 起動しない・すぐ落ちる時はコレ", padding=10)
        frame_reset.pack(fill=tk.X, padx=10, pady=5)
        
        # バージョン選択
        frame_ver = ttk.Frame(frame_reset)
        frame_ver.pack(fill=tk.X, pady=5)
        ttk.Label(frame_ver, text="対象バージョン:").pack(side=tk.LEFT)
        self.combo_ver = ttk.Combobox(frame_ver, state="readonly", width=38)
        self.combo_ver.pack(side=tk.LEFT, padx=5)
        
        versions = self.scan_indesign_versions()
        self.combo_ver['values'] = versions
        if versions: self.combo_ver.current(0)
        else: self.combo_ver.set("InDesignが見つかりません")

        # ----------------------------------------------------
        # A. 環境設定リセット (保護機能付き)
        # ----------------------------------------------------
        ttk.Label(frame_reset, text="A. 環境設定をリセット (バックアップ付)", foreground="#333", font=("Meiryo UI", 9, "bold")).pack(anchor=tk.W, pady=(10,0))
        
        self.var_keep_settings = tk.BooleanVar(value=True)
        self.chk_keep = ttk.Checkbutton(frame_reset, text="ワークスペースとキーボードショートカットを保護(バックアップ)する", variable=self.var_keep_settings)
        self.chk_keep.pack(anchor=tk.W, padx=10)
        
        frame_reset_btns = ttk.Frame(frame_reset)
        frame_reset_btns.pack(fill=tk.X, pady=2)
        
        self.btn_reset = ttk.Button(frame_reset_btns, text="環境設定をリセットする", command=self.confirm_reset_prefs, style="Blue.TButton")
        self.btn_reset.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2)
        
        self.btn_restore = ttk.Button(frame_reset_btns, text="バックアップから復元...", command=self.show_restore_dialog)
        self.btn_restore.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=2)

        self.btn_open_folder = ttk.Button(frame_reset, text="対象の設定フォルダを開く", command=self.open_target_folder)
        self.btn_open_folder.pack(fill=tk.X, pady=2)

        # ----------------------------------------------------
        # B. 復元データ削除
        # ----------------------------------------------------
        ttk.Label(frame_reset, text="B. クラッシュループ脱出 (復元データを削除)", foreground="#333", font=("Meiryo UI", 9, "bold")).pack(anchor=tk.W, pady=(15,0))
        self.btn_recovery = ttk.Button(frame_reset, text="復元データ(Recovery Data)を空にする", command=self.confirm_delete_recovery)
        self.btn_recovery.pack(fill=tk.X, pady=2)

        # =========================================
        #  ログエリア
        # =========================================
        frame_log = ttk.Frame(root, padding=10)
        frame_log.pack(fill=tk.BOTH, expand=True)
        
        ttk.Label(frame_log, text="実行ログ:").pack(anchor=tk.W)
        self.txt_log = scrolledtext.ScrolledText(frame_log, state='disabled', height=8)
        self.txt_log.pack(fill=tk.BOTH, expand=True)

        self.txt_log.tag_config("ERROR", foreground="red")
        self.txt_log.tag_config("WARNING", foreground="#D35400")
        self.txt_log.tag_config("SAFE", foreground="green")
        self.txt_log.tag_config("INFO", foreground="black")
        self.txt_log.tag_config("CACHE", foreground="blue")
        self.txt_log.tag_config("TRASH", foreground="purple")
        self.txt_log.tag_config("RESET", foreground="#008080")
        self.txt_log.tag_config("BACKUP", foreground="#800080")

        self.is_running = False
        self.detected_files = []

    def is_indesign_running(self):
        """InDesignが起動中かチェックする"""
        system = platform.system()
        try:
            if system == "Windows":
                output = subprocess.check_output(['tasklist'], universal_newlines=True, creationflags=0x08000000)
                return 'indesign.exe' in output.lower()
            elif system == "Darwin":
                output = subprocess.check_output(['ps', '-ax'], universal_newlines=True)
                return 'adobe indesign' in output.lower()
        except Exception as e:
            self.log(f"プロセスの確認に失敗しました。({e})", "WARNING")
            return False
        return False

    def get_system_font_folder(self):
        system = platform.system()
        if system == "Windows": return r"C:\Windows\Fonts"
        elif system == "Darwin": return os.path.expanduser("~/Library/Fonts")
        return ""

    def get_preferences_base_path(self):
        """設定が保存されているルートパスを返します"""
        user_home = os.path.expanduser("~")
        system = platform.system()
        if system == "Windows":
            return os.path.join(user_home, "AppData", "Roaming", "Adobe", "InDesign")
        elif system == "Darwin":
            return os.path.join(user_home, "Library", "Preferences", "Adobe InDesign")
        return None

    def scan_indesign_versions(self):
        """検出したフォルダ名(末尾の-Jなども考慮)を製品名に変換して取得"""
        found_versions = ["全バージョン一括 (All)"]
        search_path = self.get_preferences_base_path()
        
        if search_path and os.path.exists(search_path):
            try:
                dirs = [d for d in os.listdir(search_path) if os.path.isdir(os.path.join(search_path, d)) and "Version" in d]
                dirs.sort(reverse=True)
                
                for d in dirs:
                    mapped_name = None
                    # 部分一致による製品名マッピング (例: "Version 20.0-J" 内に "Version 20.0" があるか判定)
                    for key, val in self.VERSION_MAP.items():
                        if key in d:
                            mapped_name = val
                            break
                    
                    if not mapped_name:
                        mapped_name = "InDesign (非対応バージョン)"
                        
                    found_versions.append(f"{mapped_name} ({d})")
            except Exception:
                pass
        return found_versions

    def get_raw_version(self, display_text):
        """UI表示用の文字列から本来のフォルダ名 (例: Version 20.0-J) を正しく取り出す"""
        if "All" in display_text or "一括" in display_text:
            return "All"
        if "(" in display_text and ")" in display_text:
            # 最後のカッコ内を取り出すことで、サフィックス付きのフォルダ名(Version 20.0-Jなど)を取得
            parts = display_text.split("(")
            if len(parts) >= 2:
                raw_ver = parts[-1].replace(")", "").strip()
                return raw_ver
        return display_text

    def select_folder(self):
        folder = filedialog.askdirectory(initialdir=self.entry_path.get())
        if folder:
            self.entry_path.delete(0, tk.END)
            self.entry_path.insert(0, folder)

    def log(self, message, tag="INFO"):
        self.txt_log.configure(state='normal')
        self.txt_log.insert(tk.END, message + "\n", tag)
        self.txt_log.see(tk.END)
        self.txt_log.configure(state='disabled')

    def set_buttons_state(self, state):
        self.btn_start.config(state=state)
        self.btn_cache.config(state=state)
        self.btn_reset.config(state=state)
        self.btn_restore.config(state=state)
        self.btn_open_folder.config(state=state)
        self.btn_recovery.config(state=state)
        if state == 'normal' and not self.detected_files:
            self.btn_delete.config(state='disabled')
        elif state == 'disabled':
            self.btn_delete.config(state='disabled')

    # =========================================
    #  設定フォルダを開く
    # =========================================
    def open_target_folder(self):
        display_ver = self.combo_ver.get()
        if "InDesignが見つかりません" in display_ver:
            return

        base_path = self.get_preferences_base_path()
        if not base_path or not os.path.exists(base_path):
            messagebox.showwarning("警告", "設定フォルダが見つかりません。")
            return

        target_ver = self.get_raw_version(display_ver)
        target_path = base_path
        if "All" not in target_ver:
            target_path = os.path.join(base_path, target_ver)

        if not os.path.exists(target_path):
            messagebox.showwarning("警告", f"指定されたフォルダが存在しません:\n{target_path}")
            return

        try:
            system = platform.system()
            if system == "Windows":
                os.startfile(target_path)
            elif system == "Darwin":
                subprocess.Popen(["open", target_path])
            self.log(f"フォルダを開きました: {target_path}", "INFO")
        except Exception as e:
            messagebox.showerror("エラー", f"フォルダを開けませんでした。\n{e}")

    # =========================================
    #  バックアップ復元ダイアログ
    # =========================================
    def show_restore_dialog(self):
        if self.is_running: return
        
        if self.is_indesign_running():
            messagebox.showwarning("確認", "InDesignが起動しています。\nアプリケーションを完全に終了してから再度実行してください。")
            return

        display_ver = self.combo_ver.get()
        if "InDesignが見つかりません" in display_ver:
            return

        target_ver = self.get_raw_version(display_ver)

        # バックアップフォルダを走査
        backups = self.scan_backups(target_ver)
        if not backups:
            messagebox.showinfo("情報", "復元可能なバックアップファイル(フォルダ)が見つかりませんでした。")
            return

        dialog = tk.Toplevel(self.root)
        dialog.title("バックアップから復元")
        dialog.geometry("450x300")
        dialog.transient(self.root)
        dialog.grab_set()

        ttk.Label(dialog, text="復元したいバックアップを選択してください:", font=("Meiryo UI", 9, "bold")).pack(anchor=tk.W, padx=10, pady=10)

        frame_list = ttk.Frame(dialog)
        frame_list.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        scrollbar = ttk.Scrollbar(frame_list, orient=tk.VERTICAL)
        listbox = tk.Listbox(frame_list, yscrollcommand=scrollbar.set, selectmode=tk.SINGLE, font=("Courier", 9))
        scrollbar.config(command=listbox.yview)
        
        listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        for idx, item in enumerate(backups):
            # 表示名のマッピング判定(バックアップ元フォルダ名から製品名を割り出す)
            mapped_name = None
            for key, val in self.VERSION_MAP.items():
                if key in item['version']:
                    mapped_name = val
                    break
            if not mapped_name:
                mapped_name = item['version']

            display_name = f"[{mapped_name}] {item['type']} ({item['date']})"
            listbox.insert(tk.END, display_name)

        def execute_restore():
            selection = listbox.curselection()
            if not selection:
                messagebox.showwarning("警告", "バックアップが選択されていません。")
                return

            selected_item = backups[selection[0]]
            confirm = messagebox.askyesno(
                "確認", 
                f"選択したバックアップを復元しますか?\n\n"
                f"対象: {selected_item['type']}\n"
                f"日付: {selected_item['date']}\n\n"
                f"※現在の設定は上書きされます。",
                parent=dialog
            )
            
            if confirm:
                dialog.destroy()
                self.start_restore_thread(selected_item)

        btn_frame = ttk.Frame(dialog)
        btn_frame.pack(fill=tk.X, padx=10, pady=10)
        ttk.Button(btn_frame, text="キャンセル", command=dialog.destroy).pack(side=tk.RIGHT, padx=5)
        ttk.Button(btn_frame, text="復元を実行", command=execute_restore, style="Blue.TButton").pack(side=tk.RIGHT, padx=5)

    def scan_backups(self, target_ver):
        """現在作成されているバックアップフォルダを検索してリストを返します"""
        base_path = self.get_preferences_base_path()
        if not base_path or not os.path.exists(base_path):
            return []

        backups = []
        is_all = "All" in target_ver

        for dirpath, dirnames, filenames in os.walk(base_path):
            if is_all or (target_ver in dirpath):
                for d in dirnames:
                    if "_Backup_" in d:
                        full_path = os.path.join(dirpath, d)
                        parts = d.split("_Backup_")
                        if len(parts) == 2:
                            orig_name = parts[0]
                            timestamp = parts[1]
                            
                            try:
                                dt = datetime.datetime.strptime(timestamp, "%Y%m%d_%H%M%S")
                                date_str = dt.strftime("%Y/%m/%d %H:%M:%S")
                            except ValueError:
                                date_str = timestamp
                            
                            rel_path = os.path.relpath(dirpath, base_path)
                            version_dir = rel_path.split(os.sep)[0]

                            backups.append({
                                "version": version_dir,
                                "type": orig_name,
                                "date": date_str,
                                "folder_name": d,
                                "full_path": full_path,
                                "orig_name": orig_name,
                                "parent_dir": dirpath
                            })
        backups.sort(key=lambda x: x['date'], reverse=True)
        return backups

    def start_restore_thread(self, backup_item):
        self.is_running = True
        self.set_buttons_state('disabled')
        self.txt_log.configure(state='normal')
        self.txt_log.delete(1.0, tk.END)
        self.txt_log.configure(state='disabled')
        thread = threading.Thread(target=self.run_restore, args=(backup_item,), daemon=True)
        thread.start()

    def run_restore(self, backup_item):
        self.log(f"--- バックアップ復元開始 ---", "RESET")
        self.log(f"対象: {backup_item['type']} ({backup_item['date']})", "INFO")
        
        src_path = backup_item['full_path']
        dst_path = os.path.join(backup_item['parent_dir'], backup_item['orig_name'])

        try:
            if os.path.exists(dst_path):
                try:
                    shutil.rmtree(dst_path)
                    self.log(f"現在の既存フォルダを削除しました: {backup_item['orig_name']}", "INFO")
                except Exception as e:
                    self.log(f"既存フォルダの削除に失敗しました: {e}", "WARNING")
                    raise e
            
            shutil.copytree(src_path, dst_path)
            self.log(f"復元完了: {backup_item['orig_name']} を元に戻しました。", "SAFE")
            messagebox.showinfo("完了", f"「{backup_item['orig_name']}」の復元が完了しました。\nInDesignを起動して確認してください。")
        except Exception as e:
            self.log(f"復元中にエラーが発生しました: {e}", "ERROR")
            messagebox.showerror("エラー", f"復元に失敗しました。\n{e}")

        self.finish_task_common()

    # =========================================
    #  ③ 環境設定リセット機能 (保護機能付き)
    # =========================================
    def confirm_reset_prefs(self):
        if self.is_running: return
        
        if self.is_indesign_running():
            messagebox.showwarning("確認", "InDesignが起動しています。\nアプリケーションを完全に終了してから再度実行してください。")
            return

        display_ver = self.combo_ver.get()
        if "InDesignが見つかりません" in display_ver: return

        target_ver = self.get_raw_version(display_ver)
        keep_msg = "\n★ワークスペースとショートカットは保護されます" if self.var_keep_settings.get() else ""
        
        res = messagebox.askyesno(
            "環境設定リセット", 
            f"【{display_ver}】の環境設定をリセットしますか?\n\n"
            "「InDesign Defaults」と「SavedData」を初期化します。\n"
            "(戻る回数制限バグなどに有効です)\n"
            f"{keep_msg}\n\n"
            "※必ずInDesignを終了してから実行してください。"
        )
        if res: self.start_reset_thread(target_ver)

    def start_reset_thread(self, target_ver):
        self.is_running = True
        self.set_buttons_state('disabled')
        self.txt_log.configure(state='normal')
        self.txt_log.delete(1.0, tk.END)
        self.txt_log.configure(state='disabled')
        thread = threading.Thread(target=self.run_reset_prefs, args=(target_ver,), daemon=True)
        thread.start()

    def run_reset_prefs(self, target_ver):
        self.log(f"--- 環境設定リセット: {target_ver} ---", "RESET")
        
        target_files = ["InDesign Defaults", "InDesign SavedData"]
        protected_folders = ["Workspaces", "InDesign Shortcut Sets"]

        user_home = os.path.expanduser("~")
        system = platform.system()
        paths_to_check = []

        if system == "Windows":
            paths_to_check.append(os.path.join(user_home, "AppData", "Roaming", "Adobe", "InDesign"))
            paths_to_check.append(os.path.join(user_home, "AppData", "Local", "Adobe", "InDesign"))
        elif system == "Darwin":
            paths_to_check.append(os.path.join(user_home, "Library", "Preferences", "Adobe InDesign"))
            paths_to_check.append(os.path.join(user_home, "Library", "Caches", "Adobe InDesign"))

        reset_count = 0
        backup_count = 0
        timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        is_all = "All" in target_ver
        do_keep = self.var_keep_settings.get()

        for root_base in paths_to_check:
            if not os.path.exists(root_base): continue
            
            for dirpath, dirnames, filenames in os.walk(root_base):
                if is_all or (target_ver in dirpath):
                    
                    # ----------------------------------------
                    # ★ ワークスペース等のバックアップ処理
                    # ----------------------------------------
                    if do_keep and ("Roaming" in dirpath or "Preferences" in dirpath):
                        for p_folder in protected_folders:
                            if p_folder in dirnames:
                                src_path = os.path.join(dirpath, p_folder)
                                backup_folder_name = f"{p_folder}_Backup_{timestamp}"
                                dst_path = os.path.join(dirpath, backup_folder_name)
                                
                                try:
                                    if os.path.exists(dst_path):
                                        shutil.rmtree(dst_path)
                                    shutil.copytree(src_path, dst_path)
                                    self.log(f"保護(Backup): {p_folder} -> {backup_folder_name}", "BACKUP")
                                    backup_count += 1
                                except Exception as e:
                                    self.log(f"保護失敗: {p_folder} ({e})", "WARNING")

                    # ----------------------------------------
                    # ★ 設定ファイルのリセット
                    # ----------------------------------------
                    for target in target_files:
                        if target in filenames:
                            file_path = os.path.join(dirpath, target)
                            backup_name = f"{target}.old_{timestamp}"
                            try:
                                os.rename(file_path, os.path.join(dirpath, backup_name))
                                reset_count += 1
                                self.log(f"リセット: {target}", "RESET")
                            except Exception as e:
                                self.log(f"リセット失敗: {target} ({e})", "WARNING")

        self.log("-" * 30)
        
        result_msg = ""
        if reset_count > 0:
            result_msg += f"・設定ファイル {reset_count} 個をリセットしました。\n"
        if backup_count > 0:
            result_msg += f"・ワークスペース/ショートカットを {backup_count} 箇所バックアップしました。\n"
            
        if result_msg:
            self.log("完了しました。InDesignを起動してください。", "SAFE")
            if do_keep:
                self.log("※もし設定が消えていても、「バックアップから復元...」ボタンから\n簡単に元の状態へ戻すことが可能です。", "INFO")
            messagebox.showinfo("完了", f"処理が完了しました。\n\n{result_msg}")
        else:
            self.log("対象の設定ファイルが見つかりませんでした。", "WARNING")
            messagebox.showwarning("結果", "リセット対象が見つかりませんでした。")
            
        self.finish_task_common()

    # =========================================
    #  ④ 復元データ削除
    # =========================================
    def confirm_delete_recovery(self):
        if self.is_running: return

        if self.is_indesign_running():
            messagebox.showwarning("確認", "InDesignが起動しています。\nアプリケーションを完全に終了してから再度実行してください。")
            return

        display_ver = self.combo_ver.get()
        if "InDesignが見つかりません" in display_ver: return

        target_ver = self.get_raw_version(display_ver)
        res = messagebox.askyesno("確認", f"【{display_ver}】の自動復元データを削除しますか?\n保存されていない作業データは消えます。")
        if res: self.start_recovery_thread(target_ver)

    def start_recovery_thread(self, target_ver):
        self.is_running = True
        self.set_buttons_state('disabled')
        self.txt_log.configure(state='normal')
        self.txt_log.delete(1.0, tk.END)
        self.txt_log.configure(state='disabled')
        thread = threading.Thread(target=self.run_delete_recovery, args=(target_ver,), daemon=True)
        thread.start()

    def run_delete_recovery(self, target_ver):
        self.log(f"--- 復元データ削除: {target_ver} ---", "RESET")
        target_folder_name = "InDesign Recovery"
        user_home = os.path.expanduser("~")
        system = platform.system()
        paths_to_check = []
        if system == "Windows":
            paths_to_check.append(os.path.join(user_home, "AppData", "Local", "Adobe", "InDesign"))
        elif system == "Darwin":
            paths_to_check.append(os.path.join(user_home, "Library", "Caches", "Adobe InDesign"))

        deleted_count = 0
        is_all = "All" in target_ver

        for root_base in paths_to_check:
            if not os.path.exists(root_base): continue
            for dirpath, dirnames, filenames in os.walk(root_base):
                if is_all or (target_ver in dirpath):
                    if target_folder_name in dirnames:
                        recovery_path = os.path.join(dirpath, target_folder_name)
                        try:
                            files = os.listdir(recovery_path)
                            if not files: continue
                            for f in files:
                                f_path = os.path.join(recovery_path, f)
                                if os.path.isfile(f_path): os.remove(f_path)
                                elif os.path.isdir(f_path): shutil.rmtree(f_path)
                                deleted_count += 1
                            self.log(f"削除: Recoveryフォルダ内", "RESET")
                        except Exception as e:
                            self.log(f"失敗: {e}", "WARNING")

        self.log("-" * 30)
        if deleted_count > 0:
            self.log(f"完了: {deleted_count} 個削除しました。", "SAFE")
            messagebox.showinfo("完了", "復元データを削除しました。")
        else:
            self.log("復元データは空でした。", "INFO")
            messagebox.showinfo("結果", "復元データは空でした。")
        self.finish_task_common()

    # =========================================
    #  ② キャッシュ削除
    # =========================================
    def confirm_delete_cache(self):
        if self.is_running: return
        
        if self.is_indesign_running():
            messagebox.showwarning("確認", "InDesignが起動しています。\nアプリケーションを完全に終了してから再度実行してください。")
            return
        
        res = messagebox.askyesno("確認", "フォントキャッシュ(AdobeFnt*.lst)を削除しますか?")
        if res: self.start_cache_thread()

    def start_cache_thread(self):
        self.is_running = True
        self.set_buttons_state('disabled')
        self.txt_log.configure(state='normal')
        self.txt_log.delete(1.0, tk.END)
        self.txt_log.configure(state='disabled')
        thread = threading.Thread(target=self.run_delete_cache, daemon=True)
        thread.start()

    def run_delete_cache(self):
        self.log("--- キャッシュ削除開始 ---", "CACHE")
        user_home = os.path.expanduser("~")
        roots = []
        if platform.system() == "Windows":
            roots = [os.path.join(user_home, "AppData", "Local", "Adobe"),
                     os.path.join(user_home, "AppData", "Roaming", "Adobe")]
        elif platform.system() == "Darwin":
            roots = [os.path.join(user_home, "Library", "Caches", "Adobe InDesign"),
                     os.path.join(user_home, "Library", "Application Support", "Adobe")]
        
        count = 0
        for r in roots:
            if os.path.exists(r):
                for dp, dn, fn in os.walk(r):
                    for f in fnmatch.filter(fn, 'AdobeFnt*.lst'):
                        try:
                            os.remove(os.path.join(dp, f))
                            count += 1
                        except Exception:
                            pass
        self.log(f"完了: {count} 個削除しました。", "SAFE")
        self.finish_task_common()

    # =========================================
    #  ① フォント検査
    # =========================================
    def confirm_trash_files(self):
        if not self.detected_files: return
        res = messagebox.askyesno("確認", f"{len(self.detected_files)} 個のファイルをゴミ箱へ移動しますか?")
        if res: self.run_trash_files()

    def run_trash_files(self):
        self.log("--- 削除処理開始 ---", "TRASH")
        success = 0
        targets = list(set(self.detected_files))
        for path in targets:
            try:
                send2trash(path)
                self.log(f"ゴミ箱へ: {os.path.basename(path)}", "TRASH")
                success += 1
            except Exception:
                pass
        self.detected_files = []
        self.btn_delete.config(state='disabled')
        messagebox.showinfo("完了", f"{success} 個移動しました。")

    def start_check_thread(self):
        path = self.entry_path.get()
        if not path or not os.path.exists(path): return
        if self.is_running: return
        self.is_running = True
        self.detected_files = []
        self.set_buttons_state('disabled')
        self.txt_log.configure(state='normal')
        self.txt_log.delete(1.0, tk.END)
        self.txt_log.configure(state='disabled')
        thread = threading.Thread(target=self.run_check, args=(path,), daemon=True)
        thread.start()

    def run_check(self, folder_path):
        self.log("--- チェック開始 ---", "INFO")
        valid_exts = ('.ttf', '.otf', '.ttc')
        file_list = []
        for root, dirs, files in os.walk(folder_path):
            for filename in files:
                if filename.lower().endswith(valid_exts):
                    file_list.append(os.path.join(root, filename))

        total = len(file_list)
        if total == 0:
            self.log("ファイルなし", "WARNING")
            self.finish_task_common()
            return

        self.root.after(0, lambda: self.progress.configure(maximum=total, value=0))
        corrupt, warning = 0, 0

        for idx, file_path in enumerate(file_list):
            filename = os.path.basename(file_path)
            self.root.after(0, lambda v=idx+1, t=f"確認中: {filename}": self.update_ui(v, t))

            if os.path.getsize(file_path) == 0:
                self.log(f"[× 破損] {filename}", "ERROR")
                corrupt += 1
                self.detected_files.append(file_path)
                continue

            font_indices = range(len(TTCollection(file_path))) if filename.lower().endswith('.ttc') else [0]
            is_bad = False
            for i in font_indices:
                if is_bad: break
                try:
                    font = TTFont(file_path, fontNumber=i)
                    for tbl in ['name', 'cmap']: _ = font[tbl]
                    font.close()
                except Exception:
                    self.log(f"[× 破損] {filename}", "ERROR")
                    corrupt += 1
                    is_bad = True
                    continue
                try:
                    img_font = ImageFont.truetype(file_path, 40, index=i)
                    ImageDraw.Draw(Image.new("RGB",(1,1))).text((0,0), "Testあ亜", font=img_font)
                except Exception:
                    self.log(f"[! 危険] {filename}", "WARNING")
                    warning += 1
                    is_bad = True
            if is_bad: self.detected_files.append(file_path)

        self.log("-" * 30)
        if corrupt == 0 and warning == 0:
            self.log("★ 問題なし", "SAFE")
            messagebox.showinfo("完了", "正常でした。")
        else:
            self.log(f"破損: {corrupt} / 危険: {warning}", "ERROR")
            messagebox.showwarning("完了", "問題が見つかりました。")
            self.btn_delete.config(state='normal')
        self.finish_task_common()

    def update_ui(self, value, text):
        self.progress['value'] = value
        self.lbl_status.config(text=text)

    def finish_task_common(self):
        self.root.after(0, self._reset_ui)

    def _reset_ui(self):
        self.progress['value'] = 0
        self.lbl_status.config(text="待機中")
        self.set_buttons_state('normal')
        self.is_running = False

if __name__ == "__main__":
    root = tk.Tk()
    app = FontCheckerApp(root)
    root.mainloop()

 

4. 使い方とexe化の手順

このコードを自分のPCで動かす、または同僚に配布するための.exeファイルを作る手順です。

手順①:Pythonのインストール

Python公式サイトからPythonをダウンロードしてインストールします。インストーラーの「Add Python to PATH」にチェックを入れるのを忘れずに。

手順②:ライブラリのインストール

コマンドプロンプト(またはターミナル)を開き、以下のコマンドを入力します。

Bash

pip install pyinstaller fonttools pillow send2trash

手順③:exeファイルの作成

上記のPythonコードをコピーして、indesign_doctor.py という名前で保存します。
保存したフォルダでコマンドプロンプトを開き、以下を実行します。

Bash

pyinstaller --onefile --noconsole --clean indesign_doctor.py

少し待つと、同じフォルダ内にdistというフォルダができ、その中にindesign_doctor.exeが完成しています!
これを実行するだけで、誰でもInDesignのメンテナンスが可能になります。


5. まとめ

InDesignのトラブルの9割は、このツールで解決できます。

  • 戻る回数が少ない? → 環境設定リセット(SavedDataの初期化)で10%程度の確率で直ります。

  • 特定のファイルで落ちる? → フォント検査で「危険」なフォントを探して捨ててください。

  • 起動しない? → 復元データ削除でループから脱出できます。

「原因不明の不調」に怯えながら作業するのは精神衛生上よくありません。あ、それと再起動はリセット等した場合、なるべく再起動を念の為しておいた方が良いでしょう。
ぜひこのツールをUSBメモリに入れて、「InDesign救急箱」として現場に常備してみてください。あなたの、そしてチームの貴重な時間を守れるはずです。
勿論、このソフトで何があっても自己責任でお願いします。

↓↓↓↓ EXEのダウンロードは ↓↓↓↓
V2.3 Download
(ちなみにアイコンはめんどくさくて作ってませんのでご了承を)

6. 他にも試した解決法1(2026/01/31)

このソフトをつかって複数台のパソコンで試してみた。ある程度速度が改善されたり、indesignが復活したパソコンもあったが、どれをやっても駄目なパソコンも残念ながらありました。
その場合は、素直にアドビクリエイティブクラウドからアンインストールして再インストールすれば治りますので、そうした方が手っ取り早いかもしれません。それが嫌な方は解決法2

7. 他にも試した解決法22026/03/26)

このツールを使用しても治らないパソコンがあった。ちなみにバージョンは2025(20.5.2)だ。サービスディレクトリーの実行中から進行しなかったり、起動後にフリーズしたり、フリーズしなかったと思ったら知らぬ間にフリーズしたり。どうやっても復活しないので、とりあえずAdobe Creative Cloudの設定を変更する事にした。
Adobe Fontsをオフにするメリット: InDesign起動時、同期対象から外れることで、破損したキャッシュの参照をスキップし、正常に起動できる回路を通るようになるらしい。

  1. Adobe Creative Cloudを開く。
  2. 右上の自分のアイコンをクリック。
  3. 環境設定を開く。
  4. 左タブ内のサービスをクリック
  5. 右側にあるAdobe Fontsを無効にする。
画像は有効になってますが、ここを一旦オフにする

そしてInDesignを起動をすると起動するようになった。だが、じゃあこのフォント内のどれかが原因なのか?
と問い詰められると、実際は違う場合もあるだろうし、フォントだとしても莫大なフォント(1000個程)がAdobeFonts内にそのパソコンにはあった。ローカルフォント(Windows上のフォントフォルダ内)では2800個だったが。結論フォントが多すぎてどこかしらに不具合が発生、もしくは多すぎて重くなり単純に読み込みエラーを裏で吐いたものと思われる。

フォントの場所 快適な環境 Font整理が必要な環境
Local Fonts(Windowsフォント内の数) 500個以下 2,000個以上(OS自体が重くなる)
Adobe Fonts (同期中) 50個〜100個 500個以上(InDesignが起動不全に)

起動した場合の今後の対策:安定して使うためにやったこと。(AdobeFontsをオンにするには)

以下の手順で「環境のリセット」を試した。

  1. フォントキャッシュの削除:(アプリで削除可能)
    Windows: C:\Users\[ユーザー名]\AppData\Local\Adobe\InDesign\Version [バージョン]\ja_JP\Caches\InDesign Recovery 内のキャッシュをクリア。
    Mac: ~/Library/Caches/Adobe InDesign/Version [バージョン]/InDesign Recovery をクリア。
  2. Creative Cloud デスクトップアプリの再ログイン:
    一度ログアウトし、PCを再起動してから再度ログインすることで、認証トークンとサービスディレクトリの紐付けがリフレッシュされます。
  3. AdobeFontsをオンにし、特定のフォントの削除してみる:
    最近追加したAdobe Fontsがあれば、Web上のAdobe Fontsサイトから一度「アクティベート解除」、個別に原因を特定します。

もしこれらを試しても再発する場合は、環境設定ファイルの再構築
(アプリで実行可能)もさらに試して起動してみてください。

フォントの入れすぎには注意ですね。これで治ったそこのあなた!
これを機に不要なフォントは削除しておきましょう。
消したらFontエラーでまたいちいちダウンロードする方がめんどくさい?
いやいや、InDesignが起動しなくなって、あたふたする方がめんどくさいでしょ?
整理してくださいよ

端的に言うならば、ローカルFontのみ基本は使用。Creatibe CloudのFontはどうしても要るFontのみを使用。Fontが大量にある方はこれで復活するはずです。


『取り消し(Ctrl+Z)』バグの可能性は、2026/01/31現在、最新バージョンや2025では十分にあり得る現象です。
これはIndesignが新機能としてヒストリーを追加したことによるバグの可能性が高いようです。

  • 2026/07/02 V2.31 バージョン表記の修正に伴い、2026verの表記が非対応になっていたのを修正
  • 2026/06/24 V2.3 機能追加。バックアップからの「復元機能」、「設定フォルダを開く機能」、バージョン選択時の「製品名(InDesign 2024など)表示」を追加。日本語環境(-Jサフィックス)の自動検出にも対応。
  • 2026/01/31 V2.0 コードの変更。リセット時、indesignが落ちてるか確認処理を追加 旧バージョン
  • 2026/01/30 V1.0 公開

PR
Adobe Creative Cloudで「アプリケーションを管理する権限がありません」「アドビのサーバーに接続できません。ファイアーウォール設定を確認して、コンピュータの時刻設定が正しいことを確認してから再試行してください。」の解決方法

https://blog.rino-illustrator.info/2020/03/06/post-1084/