import { applyEffect, getAvailableEffects } from './effects.js'; import { DummyPostProcess } from './postprocess/dummy.js'; import { InvertPostProcess } from './postprocess/invert.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 = { dummy: new DummyPostProcess(), invert: new InvertPostProcess() }; /** * 選択されているポストプロセスを取得 */ 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 = `