Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Android 15以上でPDFのテキスト検索を爆速開発!
Search
tonionagauzzi
July 24, 2025
Programming
390
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Android 15以上でPDFのテキスト検索を爆速開発!
tonionagauzzi
July 24, 2025
More Decks by tonionagauzzi
See All by tonionagauzzi
Androidエンジニアが大規模プロダクトのWebフロントエンドとバックエンドに越境して感じたギャップと習得したマインド
tonionagauzzi
0
330
Kotlin2.3明示的バッキングフィールド
tonionagauzzi
1
380
【Android】テキスト選択色の問題修正で心がけたこと
tonionagauzzi
0
270
Googleの新しいコーディングAIエージェントJulesを使ってみた
tonionagauzzi
0
850
Compose におけるパスワード自動入力とパスワード保存
tonionagauzzi
0
530
Androidテスト基礎講義
tonionagauzzi
0
400
Android Composeでの自動入力(作成:GPT-4o)
tonionagauzzi
0
170
Jetpack Composeで自動入力(Autofill)を実装しよう(作成:claude-3.7-sonnet)
tonionagauzzi
0
180
Jetpack Composeにおける自動入力の実装と注意点(作成者:Gemini 2.5 Pro Exp 03-25)
tonionagauzzi
0
180
Other Decks in Programming
See All in Programming
バグを直したら useEffect が消えた
colorful12
3
700
思考垂れ流し開発 ~音声入力 × AIエージェント × 開発ハーネスによる試行錯誤~
npostring
0
190
GDG Korea Android: 2026 I/O Extended ~ What's new in Android development tools
pluu
0
240
「人を評価する AI」の設計と実装
ryoyanara
0
240
Flow は今どうなっているか
mizdra
PRO
0
720
【デモ】Kiroで体験する仕様駆動開発|設計からコーディングまでAIと進める開発フロー
cmkudo
0
390
[PyCon KR 2026] More Variants, More Diversity for AI Accelerators
achimnol
0
110
Loosening the Reins: Go Generics Get More Flexible
kuro_kurorrr
0
330
Discordを用いたラボオートメーション関連情報収集の自動化
noguhiro2002
0
440
まずはプロンプトガイドを読もう、話はそれからだ
kiakiraki
1
210
生成AIで帳票OCRが「簡単に」作れる時代になった?
kon_shou
0
1.2k
Detecting Compromised CI with eBPF and Cilium Tetragon
lizrice
0
260
Featured
See All Featured
VelocityConf: Rendering Performance Case Studies
addyosmani
331
25k
HDC tutorial
michielstock
2
820
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
710
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
360
Un-Boring Meetings
codingconduct
0
390
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
10k
Discover your Explorer Soul
emna__ayadi
2
1.3k
Utilizing Notion as your number one productivity tool
mfonobong
4
550
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
800
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
310
Accessibility Awareness
sabderemane
1
180
Transcript
Android 15以上でPDFのテキスト検索を爆速開発! Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 1
登壇者情報 トニオ(@tonionagauzzi) Androidエンジニア 趣味はブログ https://dribit.hatenablog.com/ Mobile勉強会 #21 ウォンテッドリー × チームラボ
× Sansan 2
アジェンダ 1. PdfRendererの紹介 2. PDFテキスト検索の実装 3. ハマったポイントと解決策 全ページを検索できない問題 スクロール位置の問題 折り返し文字列の検索問題
4. まとめ Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 3
1. PdfRendererの紹介 Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 4
AndroidでPDFを扱うなら"PdfRenderer" Android 5.0以上で使用可能 Android 15では大幅に強化 注釈、パスワード付きPDFへの対応、テキスト検索など 詳細はWebで! https://developer.android.com/about/versions/15/features?hl=ja#pdf Mobile勉強会 #21
ウォンテッドリー × チームラボ × Sansan 5
2. PDFテキスト検索の実装 Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 6
検索対応前の処理 // 1ページを開いて画像を返す private suspend fun PdfRenderer.open(pageNumber: Int): ImageBitmap {
return withContext(Dispatchers.IO) { openPage(pageNumber).use { page -> val bitmap = createBitmap(page.width, page.height) page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY) bitmap.asImageBitmap().apply { prepareToDraw() } } } } Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 7
検索対応後の処理 // 1ページを開いて画像を返す。テキストハイライト付き private suspend fun PdfRenderer.open(pageNumber: Int, searchText: String
= ""): OpenedPage { return withContext(Dispatchers.IO) { openPage(pageNumber).use { page -> val scale = 2f val bitmap = createBitmap(page.width * scale.toInt(), page.height * scale.toInt()) val matrix = Matrix().apply { postScale(scale, scale) } page.render(bitmap, null, matrix, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY) val matchedList = if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && searchText.isNotEmpty() ) { val tmpMatchedList = page.searchText(searchText) tmpMatchedList.map { matched -> matched.bounds.map { bound -> bound.applyScale(scale) val canvas = Canvas(bitmap.asImageBitmap()) val paint = Paint().apply { color = Color(255, 255, 0, 127) } canvas.drawRect(bound.left, bound.top, bound.right, bound.bottom, paint) } } tmpMatchedList } else { emptyList() } OpenedPage( imageBitmap = bitmap.asImageBitmap().apply { prepareToDraw() }, matchedCount = matchedList.size ) } } } private data class OpenedPage( val imageBitmap: ImageBitmap, val matchedCount: Int, ) Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 8
ざっくり言うと、 open を LazyColumn で1ページずつ描画してます 検索バーの文字列が searchText に入ります 最後に renderer.close()
を忘れないように! Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 9
拡大してもずれない! Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 10
ハマったポイント Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 11
1. 最初に全ページを検索できない! 最初に"1/8"と表示されてても、スクロールしていくと"8/16"になる… どうやら最初のほうのページしか検索できてないようだ。 Mobile勉強会 #21 ウォンテッドリー × チームラボ ×
Sansan 12
原因:LazyColumnで遅延読み込みしているから Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 13
解決策:検索バー表示時にPDFを全部読み込む! Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 14
// 検索時に検出数を正確に知るため、全ページを一気に読み込む LaunchedEffect(searchText) { if (searchText.isNotEmpty()) { isSearching = true
matchedPages = emptyList() val allMatchedPages = (0 until pdf.pageCount).map { pageNumber -> async { val openedPage = mutex.withLock(pdf to pageNumber) { pdf.open(pageNumber, searchText) } if (openedPage.matchedCount > 0) { MatchedPage( pageNumber = pageNumber, matchedCount = openedPage.matchedCount ) } else { null } } }.mapNotNull { it.await() } matchedPages = allMatchedPages isSearching = false } else { matchedPages = emptyList() } } Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 15
最初に全ページ開くので、メモリ不足の不安はある… より良いロジックは考えていきたい Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 16
2. 検索ヒット箇所に移動する際、移動先が正しくないことがある Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 17
原因:animateScrollToItemをうまく使えていなかった Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 18
val density = LocalDensity.current val scrollIndex = targetMatchedIndex + 1
with(density) { lazyListState.animateScrollToItem( index = scrollIndex, scrollOffset = -searchBarHeight.roundToPx() ) } scrollOffset と with(density) の組み合わせで、拡大率に合わせたスクロール先を 計算可能! Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 19
3. 折り返している文字列がpdf検索でヒットしない Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 20
Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 21
これは許容しました… 文字列検索は認識している矩形範囲で行われるので、そこから切れてしまう文字列は 検索対象にならない。 これはPDFの構造に依存するので、直接的な解決方法はない(自分調べ) 。 Mobile勉強会 #21 ウォンテッドリー × チームラボ
× Sansan 22
まとめ Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 23
Android 15以上ではPDFテキスト検索を爆速開発できる! ハマりどころはいくつかあるのとクローズ忘れに注意しよう! Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan
24
ご清聴ありがとうございました! Mobile勉強会 #21 ウォンテッドリー × チームラボ × Sansan 25