Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Android 15以上でPDFのテキスト検索を爆速開発!
Search
tonionagauzzi
July 24, 2025
Programming
400
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
390
Kotlin2.3明示的バッキングフィールド
tonionagauzzi
1
400
【Android】テキスト選択色の問題修正で心がけたこと
tonionagauzzi
0
280
Googleの新しいコーディングAIエージェントJulesを使ってみた
tonionagauzzi
0
850
Compose におけるパスワード自動入力とパスワード保存
tonionagauzzi
0
550
Androidテスト基礎講義
tonionagauzzi
0
410
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
190
Other Decks in Programming
See All in Programming
テストを司るデーモンに会いに行く 〜隔離した仮想マシンでテストを通すまで〜
h1d3mun3
1
180
[GoCon2026] When Goroutines Are Not Enough: Runtime Locality in High-Throughput Go
takehaya
6
2k
Go × SIMDで高速化するベクトル検索 ~ルーフラインモデルでSIMDが効く境界を探れ! ~
po3rin
1
240
技術的負債を組織課題として解く-増えすぎたマイクロサービスとの戦い-
reimaru
1
450
Building an Out-of-Order CPU
latte72
1
760
thread_parallel_with_free-threaded_Python_and_NumPy.pdf
riku_sakamoto
0
310
Security issues being discussed on Web Platforms
petamoriken
0
810
LoopHub - ローカルで動く GitHub で、AI と共同開発
jugyo
1
520
{ Android | Kotlin } Gradle Plugin in 2026
ryunen344
1
300
iOSDC2026登壇資料.pdf
riofujimon
0
120
個人開発基盤をまるごとCloudflareに引っ越して爆速で総合的体験を向上させた話
tinykitten
0
170
[DroidKaigi 2026] Bring your own phones to Gradle Managed Devices
f2lk
0
120
Featured
See All Featured
How to build a perfect <img>
jonoalderson
1
6k
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
410
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Docker and Python
trallard
47
4.2k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
10k
Chasing Engaging Ingredients in Design
codingconduct
0
310
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
680
Collaborative Software Design: How to facilitate domain modelling decisions
baasie
1
320
How to train your dragon (web standard)
notwaldorf
97
6.8k
Google's AI Overviews - The New Search
badams
0
1.6k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
Neural Spatial Audio Processing for Sound Field Analysis and Control
skoyamalab
0
490
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