File size: 26,926 Bytes
92a523d
7d03026
8f9ad04
92a523d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8f9ad04
 
92a523d
 
d21aed0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8f9ad04
 
 
 
 
 
 
 
 
d21aed0
 
92a523d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7d03026
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92a523d
7d03026
92a523d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d21aed0
 
 
92a523d
 
 
 
 
 
 
 
 
 
 
 
 
 
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
import { applyEffect, getAvailableEffects } from './effects.js';
import { InvertPostProcess } from './postprocess/invert.js';
import { HuePostProcess } from './postprocess/hue.js';
import { BasePostProcess } from './postprocess/base.js';

const tagDisplayNames = {
    japanese: "日本語",
    english: "英語",
    kanji: "漢字対応",
    business: "ビジネス",
    fancy: "装飾的",
    playful: "遊び心",
    display: "ディスプレイ",
    handwritten: "手書き",
    retro: "レトロ",
    calm: "落ち着いた",
    cute: "かわいい",
    script: "筆記体",
    bold: "太字",
    horror: "ホラー",
    comic: "コミック"
};

const fontTags = [
    // 日本語フォント
    { name: "Aoboshi One", tags: ["japanese"] },
    { name: "BIZ UDGothic", tags: ["japanese", "kanji", "business"] },
    { name: "BIZ UDMincho", tags: ["japanese", "kanji", "business"] },
    { name: "BIZ UDPGothic", tags: ["japanese", "kanji", "business"] },
    { name: "BIZ UDPMincho", tags: ["japanese", "kanji", "business"] },
    { name: "Cherry Bomb One", tags: ["japanese", "cute"] },
    { name: "Chokokutai", tags: ["japanese", "fancy"] },
    { name: "Darumadrop One", tags: ["japanese", "playful"] },
    { name: "Dela Gothic One", tags: ["japanese", "kanji", "display"] },
    { name: "DotGothic16", tags: ["japanese", "kanji", "retro"] },
    { name: "Hachi Maru Pop", tags: ["japanese", "kanji", "cute"] },
    { name: "Hina Mincho", tags: ["japanese", "kanji", "fancy"] },
    { name: "IBM Plex Sans JP", tags: ["japanese", "kanji", "business"] },
    { name: "Kaisei Decol", tags: ["japanese", "kanji", "fancy"] },
    { name: "Kaisei HarunoUmi", tags: ["japanese", "kanji", "fancy"] },
    { name: "Kaisei Opti", tags: ["japanese", "kanji", "business"] },
    { name: "Kaisei Tokumin", tags: ["japanese", "kanji", "business"] },
    { name: "Kiwi Maru", tags: ["japanese", "kanji", "cute"] },
    { name: "Klee One", tags: ["japanese", "kanji", "handwritten"] },
    { name: "Kosugi", tags: ["japanese", "kanji", "business"] },
    { name: "Kosugi Maru", tags: ["japanese", "kanji", "calm"] },
    { name: "M PLUS 1", tags: ["japanese", "kanji", "business"] },
    { name: "M PLUS 1 Code", tags: ["japanese", "kanji", "display"] },
    { name: "M PLUS 1p", tags: ["japanese", "kanji", "business"] },
    { name: "M PLUS 2", tags: ["japanese", "kanji", "business"] },
    { name: "M PLUS Rounded 1c", tags: ["japanese", "kanji", "calm"] },
    { name: "Mochiy Pop One", tags: ["japanese", "kanji", "playful"] },
    { name: "Mochiy Pop P One", tags: ["japanese", "kanji", "playful"] },
    { name: "Monomaniac One", tags: ["japanese", "display"] },
    { name: "Murecho", tags: ["japanese", "business"] },
    { name: "New Tegomin", tags: ["japanese", "kanji", "fancy"] },
    { name: "Noto Sans JP", tags: ["japanese", "kanji", "business"] },
    { name: "Noto Serif JP", tags: ["japanese", "kanji", "business"] },
    { name: "Palette Mosaic", tags: ["japanese", "display"] },
    { name: "Potta One", tags: ["japanese", "kanji", "playful"] },
    { name: "Rampart One", tags: ["japanese", "kanji", "display"] },
    { name: "Reggae One", tags: ["japanese", "kanji", "display"] },
    { name: "Rock 3D", tags: ["japanese", "display"] },
    { name: "RocknRoll One", tags: ["japanese", "kanji", "playful"] },
    { name: "Sawarabi Gothic", tags: ["japanese", "kanji", "business"] },
    { name: "Sawarabi Mincho", tags: ["japanese", "kanji", "business"] },
    { name: "Shippori Antique", tags: ["japanese", "kanji", "retro"] },
    { name: "Shippori Antique B1", tags: ["japanese", "kanji", "retro"] },
    { name: "Shippori Mincho", tags: ["japanese", "kanji", "business"] },
    { name: "Shippori Mincho B1", tags: ["japanese", "kanji", "business"] },
    { name: "Shizuru", tags: ["japanese", "display"] },
    { name: "Slackside One", tags: ["japanese", "handwritten"] },
    { name: "Stick", tags: ["japanese", "kanji", "display"] },
    { name: "Train One", tags: ["japanese", "kanji", "display"] },
    { name: "Tsukimi Rounded", tags: ["japanese", "calm"] },
    { name: "Yomogi", tags: ["japanese", "kanji", "handwritten"] },
    { name: "Yuji Boku", tags: ["japanese", "kanji", "fancy"] },
    { name: "Yuji Hentaigana Akari", tags: ["japanese", "fancy"] },
    { name: "Yuji Hentaigana Akebono", tags: ["japanese", "fancy"] },
    { name: "Yuji Mai", tags: ["japanese", "kanji", "fancy"] },
    { name: "Yuji Syuku", tags: ["japanese", "kanji", "fancy"] },
    { name: "Yusei Magic", tags: ["japanese", "kanji", "playful"] },
    { name: "Zen Antique", tags: ["japanese", "kanji", "retro"] },
    { name: "Zen Antique Soft", tags: ["japanese", "kanji", "retro"] },
    { name: "Zen Kaku Gothic Antique", tags: ["japanese", "kanji", "business"] },
    { name: "Zen Kaku Gothic New", tags: ["japanese", "kanji", "business"] },
    { name: "Zen Kurenaido", tags: ["japanese", "calm"] },
    { name: "Zen Maru Gothic", tags: ["japanese", "calm"] },
    { name: "Zen Old Mincho", tags: ["japanese", "kanji", "retro"] },

    // 英語フォント - ビジネス/フォーマル
    { name: "Montserrat", tags: ["english", "business"] },
    { name: "Playfair Display", tags: ["english", "business", "fancy"] },
    { name: "Roboto", tags: ["english", "business"] },
    { name: "Lato", tags: ["english", "business"] },
    { name: "Poppins", tags: ["english", "business", "calm"] },
    { name: "Quicksand", tags: ["english", "calm"] },
    { name: "Raleway", tags: ["english", "calm"] },

    // デコラティブ/ファンシー
    { name: "Pacifico", tags: ["english", "fancy", "script"] },
    { name: "Great Vibes", tags: ["english", "fancy", "script"] },
    { name: "Lobster", tags: ["english", "fancy"] },
    { name: "Dancing Script", tags: ["english", "fancy", "script"] },
    { name: "Satisfy", tags: ["english", "fancy", "script"] },
    { name: "Courgette", tags: ["english", "fancy", "script"] },
    { name: "Kaushan Script", tags: ["english", "fancy", "script"] },
    { name: "Sacramento", tags: ["english", "fancy", "script", "handwritten"] },

    // かわいい/プレイフル
    { name: "Bubblegum Sans", tags: ["english", "display", "cute", "playful"] },
    { name: "Comic Neue", tags: ["english", "comic", "cute", "handwritten"] },
    { name: "Sniglet", tags: ["english", "display", "cute", "playful"] },
    { name: "Patrick Hand", tags: ["english", "handwritten", "playful"] },
    { name: "Indie Flower", tags: ["english", "handwritten", "playful"] },

    // 手書き/筆記体
    { name: "Caveat", tags: ["english", "handwritten", "script"] },
    { name: "Shadows Into Light", tags: ["english", "handwritten"] },
    { name: "Architects Daughter", tags: ["english", "handwritten"] },
    { name: "Covered By Your Grace", tags: ["english", "handwritten"] },
    { name: "Just Another Hand", tags: ["english", "handwritten"] },

    // 太字/ディスプレイ
    { name: "Righteous", tags: ["english", "display"] },
    { name: "Permanent Marker", tags: ["english", "display", "handwritten"] },
    { name: "Press Start 2P", tags: ["english", "display", "retro"] },
    { name: "Fredoka One", tags: ["english", "display", "playful"] },
    { name: "Creepster", tags: ["english", "display", "horror"] },
    { name: "Bangers", tags: ["english", "display", "comic"] },
    { name: "Rubik Mono One", tags: ["english", "display", "bold"] },
    { name: "Bungee", tags: ["english", "display", "bold"] },
    { name: "Bungee Shade", tags: ["english", "display", "fancy"] },
    { name: "Monoton", tags: ["english", "display", "retro"] },
    { name: "Anton", tags: ["english", "display", "bold"] },
    { name: "Bebas Neue", tags: ["english", "display", "bold"] },
    { name: "Black Ops One", tags: ["english", "display", "bold"] },
    { name: "Bowlby One SC", tags: ["english", "display", "bold"] }
];


// フォントの読み込みを管理する関数
async function loadGoogleFont(fontFamily) {
    // フォントファミリー名を正しく整形
    const formattedFamily = fontFamily.replace(/ /g, '+');

    // Google Fonts APIのURLを構築
    const url = `https://fonts.googleapis.com/css2?family=${formattedFamily}&display=swap`;

    // 既存のリンクタグがあれば削除
    const existingLink = document.querySelector(`link[href*="${formattedFamily}"]`);
    if (existingLink) {
        existingLink.remove();
    }

    // 新しいリンクタグを追加
    const link = document.createElement('link');
    link.href = url;
    link.rel = 'stylesheet';
    document.head.appendChild(link);

    // フォントの読み込みを待つ
    await new Promise((resolve, reject) => {
        link.onload = async () => {
            try {
                // フォントの読み込みを確認
                await document.fonts.load(`16px "${fontFamily}"`);
                // 少し待機して確実にフォントを利用可能にする
                setTimeout(resolve, 100);
            } catch (error) {
                reject(error);
            }
        };
        link.onerror = reject;
    });
}

// テキストを画像に変換する関数を更新
async function textToImage(text, fontFamily, fontSize = '48px', effectType = 'simple') {
    console.debug(`テキスト描画開始: ${effectType}`, { text, fontFamily, fontSize });
    try {
        await document.fonts.load(`${fontSize} "${fontFamily}"`);
        const fontSizeNum = parseInt(fontSize);
        const verticalText = document.getElementById('verticalText').checked;
        const verticalSpacing = document.getElementById('verticalSpacing').value;

        // エフェクトを適用してcanvasを取得
        const canvas = await applyEffect(effectType, text, {
            font: fontFamily,
            fontSize: fontSizeNum,
            vertical: verticalText,
            verticalSpacing: verticalSpacing
        });

        // ポストプロセスを適用
        const processedCanvas = await applyPostProcessors(canvas);

        // PNG化して返す
        return BasePostProcess.toPng(processedCanvas);
    } catch (error) {
        console.error('フォント描画エラー:', error);
        throw error;
    }
}

// デバウンス関数の実装
let renderTimeout = null;
let isRendering = false;

function debounceRender(callback, delay = 200) {
    if (renderTimeout) {
        clearTimeout(renderTimeout);
    }

    if (isRendering) {
        return;
    }

    renderTimeout = setTimeout(async () => {
        isRendering = true;
        try {
            await callback();
        } finally {
            isRendering = false;
        }
    }, delay);
}

// ポストプロセス処理のインスタンスを作成
const postProcessors = {
    invert: new InvertPostProcess(),
    hue: new HuePostProcess()
};

/**
 * ポストプロセスのカードを生成
 */
function createPostProcessCard(processor) {
    const div = document.createElement('div');
    div.className = 'col-md-6 col-lg-4';
    div.innerHTML = `
        <div class="card h-100">
            <div class="card-header d-flex align-items-center">
                <div class="form-check mb-0">
                    <input type="checkbox" class="form-check-input" name="postProcess" id="postProcess${processor.name}" value="${processor.name}">
                    <label class="form-check-label" for="postProcess${processor.name}">
                        ${processor.label}
                    </label>
                </div>
            </div>
            <div class="card-body">
                ${processor.ui.template}
            </div>
        </div>
    `;
    return div;
}

/**
 * ポストプロセスのUIを初期化
 */
function initializePostProcessUI() {
    const container = document.getElementById('postProcessContainer');
    container.innerHTML = '';

    // 各プロセッサのカードを生成
    Object.values(postProcessors).forEach(processor => {
        const card = createPostProcessCard(processor);
        container.appendChild(card);
    });

    // スライダーの値変更時のイベントハンドラを設定
    const hueRotate = document.getElementById('hueRotate');
    if (hueRotate) {
        hueRotate.addEventListener('input', (e) => {
            document.getElementById('hueRotateValue').textContent = e.target.value;
            debounceRender(renderAllPresets);
        });
    }
}

/**
 * 選択されているポストプロセスを取得
 */
function getSelectedPostProcessors() {
    const container = document.getElementById('postProcessContainer');
    const checkboxes = container.querySelectorAll('input[type="checkbox"]:checked');
    return Array.from(checkboxes).map(cb => postProcessors[cb.value]).filter(Boolean);
}

/**
 * ポストプロセスを適用
 */
async function applyPostProcessors(canvas) {
    let currentCanvas = canvas;
    const processors = getSelectedPostProcessors();
    
    for (const processor of processors) {
        currentCanvas = await processor.apply(currentCanvas);
    }
    
    return currentCanvas;
}

/**
 * プレビューを更新
 */
async function updatePreview(effectType) {
    const text = document.getElementById('textInput').value;
    const font = document.getElementById('googleFontInput').value;
    const fontSize = parseInt(document.getElementById('fontSize').value);
    const vertical = document.getElementById('verticalText').checked;
    const verticalSpacing = document.getElementById('verticalSpacing').value;

    try {
        // エフェクトタイプが指定されていない場合は更新しない
        if (!effectType) return;

        // エフェクトを適用してcanvasを取得
        const canvas = await applyEffect(effectType, text, {
            font,
            fontSize,
            vertical,
            verticalSpacing
        });

        // ポストプロセスを適用
        const processedCanvas = await applyPostProcessors(canvas);

        // PNG化してプレビューに表示
        const dataUrl = BasePostProcess.toPng(processedCanvas);
        const previewImage = document.querySelector(`.effect-item[data-effect="${effectType}"] img`);
        if (previewImage) {
            previewImage.src = dataUrl;
        }
    } catch (error) {
        console.error('プレビューの更新に失敗しました:', error);
    }
}

// すべてのプリセットを描画する関数
async function renderAllPresets() {
    const effectGrid = document.querySelector('.effect-grid');
    const textInput = document.getElementById('textInput');
    const fontSelect = document.getElementById('googleFontInput');
    const fontSizeInput = document.getElementById('fontSize');

    effectGrid.innerHTML = '';
    const text = textInput.value || 'プレビュー';
    const fontFamily = fontSelect.value;
    const fontSize = fontSizeInput.value + 'px';

    const effects = getAvailableEffects();
    for (const effect of effects) {
        try {
            const imageUrl = await textToImage(text, fontFamily, fontSize, effect.name);

            const presetCard = document.createElement('div');
            presetCard.className = 'effect-item';
            presetCard.dataset.effect = effect.name;
            presetCard.innerHTML = `
                <div class="effect-name">${effect.name}</div>
                <div class="preview-container">
                    <img src="${imageUrl}" alt="${effect.name}">
                </div>
            `;

            effectGrid.appendChild(presetCard);
        } catch (error) {
            console.error(`プリセット ${effect.name} の描画エラー:`, error);

            const errorCard = document.createElement('div');
            errorCard.className = 'effect-item error';
            errorCard.dataset.effect = effect.name;
            errorCard.innerHTML = `
                <div class="effect-name text-danger">${effect.name}</div>
                <div class="preview-container">
                    <div class="text-danger">
                        <small>エラー: ${error.message}</small>
                    </div>
                </div>
            `;
            effectGrid.appendChild(errorCard);
        }
    }
}

// イベントリスナーを追加(ポストプロセスの変更時にすべてのプレビューを更新)
document.getElementById('postProcessContainer').addEventListener('change', () => {
    renderAllPresets();
});

// イベントリスナーの設定を更新
document.addEventListener('DOMContentLoaded', async () => {
    const fontSelect = document.getElementById('googleFontInput');
    const fontTagFilter = document.getElementById('fontTagFilter');
    const textInput = document.getElementById('textInput');
    const fontSizeInput = document.getElementById('fontSize');
    const verticalTextInput = document.getElementById('verticalText');
    const effectGrid = document.querySelector('.effect-grid');
    const noFontsMessage = document.getElementById('noFontsMessage');

    // 利用可能なタグを収集し、使用頻度をカウント
    function getTagsWithCount() {
        const tagCount = new Map();
        fontTags.forEach(font => {
            font.tags.forEach(tag => {
                tagCount.set(tag, (tagCount.get(tag) || 0) + 1);
            });
        });
        return tagCount;
    }

    // 言語関連のタグかどうかを判定
    function isLanguageTag(tag) {
        return ['japanese', 'english', 'kanji'].includes(tag);
    }

    // フィルターボタンを動的に生成
    function createFilterButtons() {
        const tagCount = getTagsWithCount();
        fontTagFilter.innerHTML = '';

        // タグを言語関連とその他に分類し、個数でソート
        const languageTags = [...tagCount.entries()]
            .filter(([tag]) => isLanguageTag(tag))
            .sort((a, b) => b[1] - a[1]);

        const otherTags = [...tagCount.entries()]
            .filter(([tag]) => !isLanguageTag(tag))
            .sort((a, b) => b[1] - a[1]);

        // 言語関連のタグを追加
        if (languageTags.length > 0) {
            const langGroup = document.createElement('div');
            langGroup.className = 'filter-group mb-2';
            langGroup.innerHTML = '<div class="filter-group-label mb-1">言語</div>';
            
            const langButtonGroup = document.createElement('div');
            langButtonGroup.className = 'btn-group-wrapper';
            
            languageTags.forEach(([tag, count]) => {
                const displayName = tagDisplayNames[tag] || tag;
                const button = document.createElement('div');
                button.className = 'btn-check-wrapper';
                button.innerHTML = `
                    <input type="checkbox" class="btn-check" name="fontFilter" id="filter${tag}" value="${tag}">
                    <label class="btn btn-outline-primary" for="filter${tag}">
                        ${displayName} <span class="tag-count" data-tag="${tag}">(${count})</span>
                    </label>
                `;
                langButtonGroup.appendChild(button);
            });
            
            langGroup.appendChild(langButtonGroup);
            fontTagFilter.appendChild(langGroup);
        }

        // その他のタグを追加
        if (otherTags.length > 0) {
            const otherGroup = document.createElement('div');
            otherGroup.className = 'filter-group';
            otherGroup.innerHTML = '<div class="filter-group-label mb-1">スタイル</div>';
            
            const otherButtonGroup = document.createElement('div');
            otherButtonGroup.className = 'btn-group-wrapper';
            
            otherTags.forEach(([tag, count]) => {
                const displayName = tagDisplayNames[tag] || tag;
                const button = document.createElement('div');
                button.className = 'btn-check-wrapper';
                button.innerHTML = `
                    <input type="checkbox" class="btn-check" name="fontFilter" id="filter${tag}" value="${tag}">
                    <label class="btn btn-outline-primary" for="filter${tag}">
                        ${displayName} <span class="tag-count" data-tag="${tag}">(${count})</span>
                    </label>    
                `;
                otherButtonGroup.appendChild(button);
            });
            
            otherGroup.appendChild(otherButtonGroup);
            fontTagFilter.appendChild(otherGroup);
        }
    }

    // 選択されたフィルターに基づいてタグカウントを更新
    function updateTagCounts() {
        const selectedFilters = Array.from(fontTagFilter.querySelectorAll('input[type="checkbox"]:checked'))
            .map(checkbox => checkbox.value);

        // 選択されたフィルターがない場合は、全体の数を表示し、すべてのボタンを表示
        if (selectedFilters.length === 0) {
            const totalCounts = getTagsWithCount();
            totalCounts.forEach((count, tag) => {
                const wrapper = document.querySelector(`#filter${tag}`).closest('.btn-check-wrapper');
                wrapper.style.display = 'inline-block';
                const countElement = document.querySelector(`.tag-count[data-tag="${tag}"]`);
                if (countElement) {
                    countElement.textContent = `(${count})`;
                }
            });
            return;
        }

        // 各タグについて、現在の選択と組み合わせた場合の数を計算
        const allTags = [...new Set(fontTags.flatMap(font => font.tags))];
        allTags.forEach(tag => {
            const countElement = document.querySelector(`.tag-count[data-tag="${tag}"]`);
            const wrapper = document.querySelector(`#filter${tag}`).closest('.btn-check-wrapper');
            
            if (countElement && wrapper) {
                // このタグが既に選択されている場合は、他の選択されたタグとの組み合わせ数を表示
                const filtersToCheck = selectedFilters.includes(tag) 
                    ? selectedFilters 
                    : [...selectedFilters, tag];
                
                const count = fontTags.filter(font => 
                    filtersToCheck.every(filter => font.tags.includes(filter))
                ).length;

                countElement.textContent = `(${count})`;

                // カウントが0の場合、かつ現在選択されていないタグの場合は非表示
                if (count === 0 && !selectedFilters.includes(tag)) {
                    wrapper.style.display = 'none';
                } else {
                    wrapper.style.display = 'inline-block';
                }
            }
        });
    }

    // フォントオプションの初期化と絞り込み機能の実装
    function initializeFontOptions() {
        // 現在選択されているフォントを保持
        const currentFont = fontSelect.value;
        
        // 選択されているフィルターを取得
        const selectedFilters = Array.from(fontTagFilter.querySelectorAll('input[type="checkbox"]:checked'))
            .map(checkbox => checkbox.value);
        
        // 既存のオプションをクリア
        fontSelect.innerHTML = '';
        
        // フィルタリングされたフォントのリストを作成
        const filteredFonts = selectedFilters.length === 0 
            ? fontTags 
            : fontTags.filter(font => 
                selectedFilters.every(filter => font.tags.includes(filter))
            );

        // フォルター結果が0件の場合
        if (filteredFonts.length === 0) {
            noFontsMessage.style.display = 'block';
            fontSelect.disabled = true;
            return Promise.resolve(); // フォントの読み込みは不要
        } else {
            noFontsMessage.style.display = 'none';
            fontSelect.disabled = false;
        }

        // フォントオプションを追加
        filteredFonts.forEach((font, index) => {
            const option = document.createElement('option');
            option.value = font.name;
            option.textContent = font.name;
            // 以前選択されていたフォントがある場合はそれを選択、なければ最初のフォントを選択
            if (font.name === currentFont || (index === 0 && !currentFont)) {
                option.selected = true;
            }
            fontSelect.appendChild(option);
        });

        // タグカウントを更新
        updateTagCounts();

        // 選択されたフォントを読み込む
        return loadGoogleFont(fontSelect.value);
    }

    // タグフィルターの変更イベントを設定
    fontTagFilter.addEventListener('change', async (e) => {
        if (e.target.type === 'checkbox') {
            await initializeFontOptions();
            if (!fontSelect.disabled) { // フォントが選択可能な場合のみプレビューを更新
                await renderAllPresets();
            }
        }
    });

    // 初期化
    createFilterButtons();
    await initializeFontOptions();
    await loadGoogleFont(fontSelect.value);

    // 縦書きモードの状態をグリッドに反映
    verticalTextInput.addEventListener('change', (e) => {
        effectGrid.dataset.vertical = e.target.checked;
        renderAllPresets();
    });

    // フォント変更時の処理
    fontSelect.addEventListener('change', async (e) => {
        try {
            const fontFamily = e.target.value;
            await loadGoogleFont(fontFamily);
            await renderAllPresets();
        } catch (error) {
            console.error('フォント読み込みエラー:', error);
        }
    });

    // テキストとフォントサイズの変更時にすべてのプリセットを再描画
    [textInput, fontSizeInput, verticalTextInput, verticalSpacing].forEach(element => {
        element.addEventListener('input', () => {
            debounceRender(renderAllPresets);
        });
    });

    // 初期描画
    await renderAllPresets();

    // ポストプロセスのUI初期化
    initializePostProcessUI();
});

// 縦書きモードの切り替え時の処理
document.getElementById('verticalText').addEventListener('change', function (e) {
    const spacingContainer = document.getElementById('verticalSpacingContainer');
    spacingContainer.style.display = e.target.checked ? 'block' : 'none';
    // ロゴの再生成処理を呼び出す
});

// 文字間隔の変更時の処理
document.getElementById('verticalSpacing').addEventListener('input', function (e) {
    document.getElementById('verticalSpacingValue').textContent = e.target.value;
    // ロゴの再生成処理を呼び出す
});