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
380
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
Kotlin2.3明示的バッキングフィールド
tonionagauzzi
1
360
【Android】テキスト選択色の問題修正で心がけたこと
tonionagauzzi
0
250
Googleの新しいコーディングAIエージェントJulesを使ってみた
tonionagauzzi
0
800
Compose におけるパスワード自動入力とパスワード保存
tonionagauzzi
0
500
Androidテスト基礎講義
tonionagauzzi
0
390
Android Composeでの自動入力(作成:GPT-4o)
tonionagauzzi
0
150
Jetpack Composeで自動入力(Autofill)を実装しよう(作成:claude-3.7-sonnet)
tonionagauzzi
0
160
Jetpack Composeにおける自動入力の実装と注意点(作成者:Gemini 2.5 Pro Exp 03-25)
tonionagauzzi
0
170
Jetpack Composeで自動入力を完全攻略(作成:o3)
tonionagauzzi
0
160
Other Decks in Programming
See All in Programming
SREは、MCPとSRE Agentをこう使え!
kazumax55
0
130
ADKを使って簡単にAIエージェントを作ってみよう
k1mu21
0
290
PHPで使える日時の表現と、その知り方 #frontend_phpcon_do
o0h
PRO
0
280
Hunting Vulnerabilities in Symfony with LLMs
vinceamstoutz
0
570
AIを活用したE2Eテスト実装効率化のあゆみ / ebisu-mobile-14-kotetu
kotetuco
0
150
OS アップデート対応の取り組み方がもっと共有されてほしい
andpad
0
110
自作OSでスライド発表する
uyuki234
1
3.6k
AI時代のUIはどこへ行く?その2!
yusukebe
22
7.7k
Spring Security 実践 ─ GraphQL APIで実務に役立つ 認証・認可 を学ぶ
wagyu
0
270
TAKTでAI駆動開発の品質を設計する
j5ik2o
7
1.6k
これからAgentCoreを触る方へトレンドはGatewayです
har1101
6
460
ECSアプリログをFireLensでコスト削減しようとしたけど諦めた話 in Fargate×Node.js
akihisaikeda
2
4.2k
Featured
See All Featured
Paper Plane (Part 1)
katiecoart
PRO
0
9.5k
How to make the Groovebox
asonas
2
2.3k
Reality Check: Gamification 10 Years Later
codingconduct
0
2.2k
Stewardship and Sustainability of Urban and Community Forests
pwiseman
0
240
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.3k
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
450
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
1
6k
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
250
How to Think Like a Performance Engineer
csswizardry
28
2.7k
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
1
3.7k
Heart Work Chapter 1 - Part 1
lfama
PRO
8
36k
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
600
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