Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Android 16 × Jetpack Composeで縦書きテキストエディタを作ろう ...

Avatar for cc4966 cc4966
September 12, 2025

Android 16 × Jetpack Composeで縦書きテキストエディタを作ろう / Vertical Text Editor with Compose on Android 16

DroidKaigi 2025で発表したものです。https://2025.droidkaigi.jp/timetable/946512/

Android 16でPaintに追加されるVERTICAL_TEXT_FLAGにより、ついに標準で縦書きテキストの描画が可能になります。
本セッションではその新機能とJetpack Composeを組み合わせた簡単な縦書きテキストエディタの実装方法を具体的なコードを交えて紹介します。

Androidで縦書きを簡単に使うにはWebViewが必要でした。一方iOSでは以前より縦書きのテキスト描画がサポートされていたため、縦書きのできるテキストエディタアプリの数はAndroidとiOSで天と地ほどの差がある現状です。
日本語文化の重要なピースである縦書きをAndroidにも普及させるべく、本セッションにより誰もが「Androidで縦書きエディタを作れる」ようになることを目指します。

本セッションではAndroid 16の縦書きのテキスト描画APIのことだけではなく、Jetpack Composeで独自のテキストエディタを作る方法も説明します。
またJetpack Composeにはテキストの表示からIMEによるテキスト入力を分離して抽象的に扱うことのできる先進的な設計が存在しており、その先進性についてAndroid従来のView含めた他のプラットフォームの事情と比較して解説します。

#発表のアウトライン
## 縦書きテキスト
・PaintへのVERTICAL_TEXT_FLAGの追加
・縦書きテキストの描画
## Jetpack Composeのテキスト入力とテキスト描画
・PlatformTextInputModifierNode とは
・AndroidのInputConnectionとの関係について
・androidx.compose.text.input からの変遷
## Jetpack Composeと他プラットフォームのテキスト入力周りのAPI設計
・iOS, macOS, Windowsとの比較
## 具体的な縦書きエディタの実装コード

Avatar for cc4966

cc4966

September 12, 2025
Tweet

More Decks by cc4966

Other Decks in Programming

Transcript

  1. 自己紹介 • rokuroku / @496_ on X • 趣味プロダクト ◦

    TATEditor - 縦書きエディタ ▪ Android / iOS / macOS / Windows ▪ https://tateditor.app
  2. Androidと縦書き(2021年) API Level 31からCanvasでフォン トのグリフIDを直接指定して文字を 描画できるようになった canvas.nativeCanvas.drawGlyphs(intArrayOf (1424, 65154), 0,

    floatArrayOf(100f, 100f, 100f + paint.textSize, 100f), 0, 2, result.getFont(0), paint) https://developer.android.com/reference/android/graphics/Canvas#drawGlyphs(int%5B%5D,%20int,%20flo at%5B%5D,%20int,%20int,%20android.graphics.fonts.Font,%20android.graphics.Paint)
  3. Webと縦書き • writing-mode: vertical-rl; • Internet Explorer 5.5 (2000年) の

    writing-mode: tb-rl; が始まり • AndroidアプリでもWebViewを使えば縦 書きのテキスト表示は可能
  4. iOSと縦書き • macOS同様、2019年にPagesが縦書き 対応された • 2013年 (iOS 7) から文字描画で縦書き が可能で、縦書きテキストエディタのアプ

    リも多数リリースされている https://developer.apple.com/documentation/foundation/nsattributedstring/key/verticalglyphform
  5. 縦書きCanvas @Composable fun VerticalCanvasSample( modifier: Modifier = Modifier.fillMaxSize(), text: String,

    ) { Canvas(modifier = modifier) { val verticalPaint = TextPaint().apply { textSize = 40.sp.toPx() flags = flags or VERTICAL_TEXT_FLAG } drawIntoCanvas { canvas -> canvas.nativeCanvas.drawText(text, 0, text.length, 100f, 100f, verticalPaint) } } }
  6. ベースラインと調整 drawIntoCanvas { canvas -> canvas.nativeCanvas.drawText(text, 0, text.length, 100f, 100f,

    verticalPaint) canvas.nativeCanvas.drawText(text, 0, text.length, 100f, 100f, paint) canvas.drawRect(0f, 0f, 100f, 100f, Paint()) canvas.drawLine(Offset(100f, 100f), Offset(9999f, 100f), Paint()) canvas.drawLine(Offset(100f, 100f), Offset(100f, 9999f), Paint()) }
  7. ダウンロード可能フォント val HinaMinFont = GoogleFont(name = "Hina Mincho") val HinaMinFontFamily

    = FontFamily( Font(googleFont = HinaMinFont, fontProvider = provider), ) val basePaint = TextPaint().apply { val typeface = createFontFamilyResolver(context).resolve(HinaMinFontFamily) if (typeface is Typeface) { this.typeface = typeface } } https://developer.android.com/develop/ui/compose/text/fonts?hl=ja#use-downloadable-fonts https://developer.android.com/reference/kotlin/androidx/compose/ui/text/font/package-summary#createFontFamilyResolver(android.co ntent.Context)
  8. 2025/8/27に新ライブラリ登場🎉 androidx.text:text-vertical:1.0.0-alpha01 A new vertical text library to support vertical

    text layout mainly for Japanese, by leveraging the new VERTICAL_TEXT_FLAG flag added in Android 16. https://developer.android.com/jetpack/androidx/releases/text https://developer.android.com/reference/kotlin/androidx/text/vertical/VerticalTextLayout VerticalTextLayout
  9. VerticalTextLayoutの使用例 val paint = TextPaint().apply { textSize = 36.sp.toPx() }

    val text = "とある科学の超電磁砲 " val layout = VerticalTextLayout.Builder( text, 0, text.length, paint, 9999f ).build() layout.draw( canvas.nativeCanvas, canvas.nativeCanvas.width.toFloat(), 0f )
  10. ルビ、 ルビとは: ふりがなのこと val text = SpannableStringBuilder() .append("とある科学の ") .append("超電磁砲",

    RubySpan.Builder( "レールガン " ).build(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) .toSpannable() https://developer.android.com/reference/kotlin/androidx/text/vertical/RubySpan.Builder
  11. 傍点とは: 1文字1文字に 強調点を打つこと val text = SpannableStringBuilder() .append("とある科学の ") .append("超電磁砲",

    EmphasisSpan(STYLE_SESAME), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) .toSpannable() https://developer.android.com/reference/kotlin/androidx/text/vertical/EmphasisSpan ルビ、傍点、
  12. 縦中横とは: 文章の一部を横 並びで表示 val text = SpannableStringBuilder() .append("今日、") .append("2025", TextOrientationSpan.TextCombineUpright(),

    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) .append("年") .append("9", TextOrientationSpan.TextCombineUpright(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) .append("月") .append("12", TextOrientationSpan.TextCombineUpright(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) .append("日。") .toSpannable() https://developer.android.com/reference/kotlin/androidx/text/vertical/TextOrientationSpan.TextCombineUpright ルビ・傍点・縦中横
  13. EditCommand Known direct subclasses BackspaceCommand, CommitTextCommand, DeleteAllCommand, DeleteSurroundingTextCommand, DeleteSurroundingTextInCodePointsCommand, FinishComposingTextCommand,

    MoveCursorCommand, SetComposingRegionCommand, SetComposingTextCommand, SetSelectionCommand https://developer.android.com/reference/kotlin/androidx/compose/ui/text/input/EditCommand
  14. TextInputSession fun updateTextLayoutResult( textFieldValue: TextFieldValue, offsetMapping: OffsetMapping, textLayoutResult: TextLayoutResult, textFieldToRootTransform:

    (Matrix) -> Unit, innerTextFieldBounds: Rect, decorationBoxBounds: Rect, ) https://developer.android.com/reference/kotlin/androidx/compose/ui/text/input/TextInputSession#updateTextLayoutResult(androidx.co mpose.ui.text.input.TextFieldValue,androidx.compose.ui.text.input.OffsetMapping,androidx.compose.ui.text.TextLayoutResult,kotlin.Fu nction1,androidx.compose.ui.geometry.Rect,androidx.compose.ui.geometry.Rect)