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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
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
320
Kotlin2.3明示的バッキングフィールド
tonionagauzzi
1
380
【Android】テキスト選択色の問題修正で心がけたこと
tonionagauzzi
0
270
Googleの新しいコーディングAIエージェントJulesを使ってみた
tonionagauzzi
0
840
Compose におけるパスワード自動入力とパスワード保存
tonionagauzzi
0
530
Androidテスト基礎講義
tonionagauzzi
0
400
Android Composeでの自動入力(作成:GPT-4o)
tonionagauzzi
0
170
Jetpack Composeで自動入力(Autofill)を実装しよう(作成:claude-3.7-sonnet)
tonionagauzzi
0
170
Jetpack Composeにおける自動入力の実装と注意点(作成者:Gemini 2.5 Pro Exp 03-25)
tonionagauzzi
0
180
Other Decks in Programming
See All in Programming
Building a Meta Ray-Ban display app
akkeylab
0
180
typoなんかねぇよ
raspython3
0
490
「つくるAI」だけではバグは見つからない ~テストに必要な「見つけるAI」を分離させる戦略~
mfunaki
0
160
【デモ】Kiroで体験する仕様駆動開発|設計からコーディングまでAIと進める開発フロー
cmkudo
0
360
片田舎のおっさん、 Swift Buildのダイアモンド問題解決の不具合修正PRを出すが、解決方法がキャッシュをしないようにすることであり、ビルド時間が伸びると言われてマージされないので高速化もする/swiftbuild
yimajo
0
270
PostgreSQL 18で考えるUUID主キー
kazuhiro1982
0
500
「人を評価する AI」の設計と実装
ryoyanara
0
230
Flow は今どうなっているか
mizdra
PRO
0
690
DynamoDBの基礎を振り返りながらベクトル検索機能を理解する
musan
2
140
ドリフトを絶対に許さない(?)CDK運用 / CDK Ops with Zero Tolerance for Drifts (?)
akihisaikeda
1
220
異なる設計思想のフレームワークを経験して得た学び
amekuhideki
1
330
そこに3びきプロダクトがいるじゃろう——生成AI時代における“価値が届かない理由”の構造
kosuket
0
570
Featured
See All Featured
The B2B funnel & how to create a winning content strategy
katarinadahlin
PRO
1
480
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
580
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.6k
Unsuck your backbone
ammeep
672
58k
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
380
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
73
41k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
4.4k
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
440
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
11k
Build The Right Thing And Hit Your Dates
maggiecrowley
39
3.4k
Technical Leadership for Architectural Decision Making
baasie
3
530
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