diff options
| author | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-06 14:47:31 +0900 |
|---|---|---|
| committer | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-06 14:47:31 +0900 |
| commit | 716060b099cb29170215ce8c03885d09a1d1446a (patch) | |
| tree | 1f2f2378f121892980e21ec4666ee238bb445a70 /public | |
| parent | aa30a9067a6a21782a32be2e0525374a222246e8 (diff) | |
bluebey-studio: 縦書きの括弧「」『』[]()などを90度回転に対応
縦書きでは開き括弧が下向き・閉じ括弧が上向きになるよう、括弧を90度回転(枠内は中央のまま)。canvasプレビューとSVG書き出しの両方。
Diffstat (limited to 'public')
| -rw-r--r-- | public/bluebey-studio/src/caption.js | 31 |
1 files changed, 23 insertions, 8 deletions
diff --git a/public/bluebey-studio/src/caption.js b/public/bluebey-studio/src/caption.js index df9c952..71759ea 100644 --- a/public/bluebey-studio/src/caption.js +++ b/public/bluebey-studio/src/caption.js @@ -101,21 +101,34 @@ const VERTICAL_OFFSETS = new Map(); for (const ch of SMALL_KANA) VERTICAL_OFFSETS.set(ch, { dx: 0.12, dy: -0.12 }); // 縦書きの句読点は、横書きの左下ではなく、字枠の右上に置く。 for (const ch of HANGING_PUNCTUATION) VERTICAL_OFFSETS.set(ch, { dx: 0.68, dy: -0.55 }); -for (const ch of '「『(〔') VERTICAL_OFFSETS.set(ch, { dx: 0.14, dy: -0.12 }); -for (const ch of '」』)〕') VERTICAL_OFFSETS.set(ch, { dx: -0.14, dy: 0.12 }); /** * Characters drawn turned a quarter turn when stacked, so they read the way - * 縦書き sets them: the prolonged sound mark `ー` becomes an upright bar. + * 縦書き sets them: the prolonged sound mark `ー` becomes an upright bar, and the + * brackets `「」『』[]()` turn so they open down the column. */ const VERTICAL_ROTATED = new Set(['ー', '~', '〜', '―']); +const VERTICAL_BRACKETS = new Set([ + '「', '」', '『', '』', '(', ')', '[', ']', '【', '】', '〔', '〕', '〈', '〉', '《', '》', +]); /** * How far a rotated glyph is nudged down inside its cell, in em. The rotated `ー` - * is centred otherwise, which leaves it hugging the character above it. + * is centred otherwise, which leaves it hugging the character above it; the + * brackets stay centred. */ const VERTICAL_ROTATED_DROP = 0.14; +/** + * The downward nudge (em) for a glyph that is turned a quarter turn in 縦書き, or + * `null` when the glyph is drawn the ordinary way. + */ +function verticalRotation(ch) { + if (VERTICAL_ROTATED.has(ch)) return VERTICAL_ROTATED_DROP; + if (VERTICAL_BRACKETS.has(ch)) return 0; + return null; +} + /** Offset used for the characters that need no special placement. */ const NO_OFFSET = Object.freeze({ dx: 0, dy: 0 }); @@ -426,9 +439,10 @@ function drawText(ctx, layout, state, font) { const x = left + (lines.length - 1 - i) * step; for (let j = 0; j < chars.length; j++) { const ch = chars[j]; - if (VERTICAL_ROTATED.has(ch)) { + const drop = verticalRotation(ch); + if (drop !== null) { ctx.save(); - ctx.translate(x + fontSize / 2, top + j * fontSize + fontSize / 2 + VERTICAL_ROTATED_DROP * fontSize); + ctx.translate(x + fontSize / 2, top + j * fontSize + fontSize / 2 + drop * fontSize); ctx.rotate(Math.PI / 2); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; @@ -492,9 +506,10 @@ function liveText(state, layout, font, fill, places) { const x = left + (lines.length - 1 - i) * step; for (let j = 0; j < chars.length; j++) { const ch = chars[j]; - if (VERTICAL_ROTATED.has(ch)) { + const drop = verticalRotation(ch); + if (drop !== null) { const cx = x + fontSize / 2; - const cy = top + j * fontSize + fontSize / 2 + VERTICAL_ROTATED_DROP * fontSize; + const cy = top + j * fontSize + fontSize / 2 + drop * fontSize; rotated.push(`<text ${base} text-anchor="middle" dy="0.36em"` + ` transform="rotate(90 ${formatNumber(cx, places)} ${formatNumber(cy, places)})"` + ` x="${formatNumber(cx, places)}" y="${formatNumber(cy, places)}">${escapeText(ch)}</text>`); |
