Skip to content

Commit 77a5a08

Browse files
committed
Implement PDF text search
1 parent d82e731 commit 77a5a08

12 files changed

Lines changed: 450 additions & 44 deletions

File tree

.idea/deploymentTargetSelector.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package io.bashpsk.emptylibs.pdfviewer.extension
2+
3+
import androidx.compose.ui.geometry.CornerRadius
4+
import androidx.compose.ui.geometry.Offset
5+
import androidx.compose.ui.geometry.Rect
6+
import androidx.compose.ui.geometry.Size
7+
import androidx.compose.ui.graphics.Color
8+
import androidx.compose.ui.graphics.drawscope.DrawScope
9+
import androidx.compose.ui.unit.dp
10+
import io.bashpsk.emptylibs.pdfviewer.page.PdfPageData
11+
import io.bashpsk.emptylibs.pdfviewer.pdf.PdfLazyColumnProperties
12+
import kotlinx.collections.immutable.ImmutableList
13+
14+
/**
15+
* Draws the list of search result rectangles on the current [DrawScope].
16+
*
17+
* This function scales the search result coordinates from the original PDF page size
18+
* to the current canvas dimensions and renders them using the search box color
19+
* defined in [PdfLazyColumnProperties].
20+
*
21+
* @param pageData The data of the PDF page, containing the dimensions and the list of search result
22+
* rectangles.
23+
* @param properties The configuration properties for the PDF viewer, including the color used for
24+
* search highlights.
25+
*/
26+
internal fun DrawScope.drawSearchRectList(
27+
pageData: PdfPageData,
28+
properties: PdfLazyColumnProperties
29+
) {
30+
31+
drawRectList(
32+
pageWidth = pageData.width,
33+
pageHeight = pageData.height,
34+
color = properties.searchBoxColor,
35+
rectList = pageData.searchRectList
36+
)
37+
}
38+
39+
/**
40+
* Draws the list of selection rectangles onto the [DrawScope].
41+
*
42+
* This function scales the selection rectangles from the PDF page's coordinate system
43+
* to the current canvas size and renders them using the selection color defined in [properties].
44+
*
45+
* @param pageData The data containing the original dimensions and the list of selection rectangles
46+
* for the page.
47+
* @param properties The configuration properties containing the color used for drawing selection
48+
* highlights.
49+
*/
50+
internal fun DrawScope.drawSelectRectList(
51+
pageData: PdfPageData,
52+
properties: PdfLazyColumnProperties
53+
) {
54+
55+
drawRectList(
56+
pageWidth = pageData.width,
57+
pageHeight = pageData.height,
58+
color = properties.selectBoxColor,
59+
rectList = pageData.selectRectList
60+
)
61+
}
62+
63+
/**
64+
* Draws a list of rounded rectangles onto the [DrawScope], scaling them from the original page
65+
* coordinates to the current canvas dimensions.
66+
*
67+
* @param pageWidth The original width of the PDF page.
68+
* @param pageHeight The original height of the PDF page.
69+
* @param color The color to use for drawing the rectangles.
70+
* @param rectList A nested list of [Rect] objects representing the areas to be drawn
71+
* (e.g., search results or selections).
72+
*/
73+
private fun DrawScope.drawRectList(
74+
pageWidth: Int,
75+
pageHeight: Int,
76+
color: Color,
77+
rectList: ImmutableList<ImmutableList<Rect>>
78+
) {
79+
80+
if (rectList.isEmpty()) return
81+
82+
val scaleFactorX = size.width / pageWidth
83+
val scaleFactorY = size.height / pageHeight
84+
85+
rectList.forEach { match ->
86+
87+
match.forEach { rect ->
88+
89+
val scaledRect = Rect(
90+
left = rect.left * scaleFactorX,
91+
top = rect.top * scaleFactorY,
92+
right = rect.right * scaleFactorX,
93+
bottom = rect.bottom * scaleFactorY
94+
)
95+
96+
drawRoundRect(
97+
topLeft = Offset(x = scaledRect.left, y = scaledRect.top),
98+
size = Size(width = scaledRect.width, height = scaledRect.height),
99+
cornerRadius = CornerRadius(x = 0.4.dp.toPx()),
100+
color = color
101+
)
102+
}
103+
}
104+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.bashpsk.emptylibs.pdfviewer.page
2+
3+
import androidx.compose.runtime.Immutable
4+
import androidx.compose.ui.geometry.Rect
5+
import androidx.compose.ui.graphics.ImageBitmap
6+
import kotlinx.collections.immutable.ImmutableList
7+
import kotlinx.collections.immutable.persistentListOf
8+
9+
/**
10+
* Represents the data for a single page in a PDF document.
11+
*
12+
* @property page The page number.
13+
* @property width The width of the page in pixels.
14+
* @property height The height of the page in pixels.
15+
* @property bitmap A low-quality bitmap representation of the page, used for previews.
16+
* @property searchRectList A list of rectangles representing search matches in the page.
17+
* @property selectRectList A list of rectangles representing selected regions in the page.
18+
*/
19+
@Immutable
20+
internal data class PdfPageData(
21+
val page: Int = 0,
22+
val width: Int = 0,
23+
val height: Int = 0,
24+
val bitmap: ImageBitmap? = null,
25+
val searchRectList: ImmutableList<ImmutableList<Rect>> = persistentListOf(),
26+
val selectRectList: ImmutableList<ImmutableList<Rect>> = persistentListOf()
27+
)

pdf-viewer/src/main/kotlin/io/bashpsk/emptylibs/pdfviewer/pdf/PdfPageView.kt renamed to pdf-viewer/src/main/kotlin/io/bashpsk/emptylibs/pdfviewer/page/PdfPageView.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.bashpsk.emptylibs.pdfviewer.pdf
1+
package io.bashpsk.emptylibs.pdfviewer.page
22

33
import androidx.compose.foundation.Image
44
import androidx.compose.foundation.background
@@ -19,6 +19,7 @@ import androidx.compose.runtime.setValue
1919
import androidx.compose.runtime.snapshotFlow
2020
import androidx.compose.ui.Alignment
2121
import androidx.compose.ui.Modifier
22+
import androidx.compose.ui.draw.drawWithContent
2223
import androidx.compose.ui.graphics.Color
2324
import androidx.compose.ui.graphics.ColorFilter
2425
import androidx.compose.ui.graphics.ImageBitmap
@@ -27,6 +28,9 @@ import androidx.compose.ui.layout.onFirstVisible
2728
import androidx.compose.ui.unit.round
2829
import io.bashpsk.emptylibs.formatter.format.EmptyFormat
2930
import io.bashpsk.emptylibs.jetpackui.layout.ZoomableLayout
31+
import io.bashpsk.emptylibs.pdfviewer.pdf.PdfLazyColumnDefaults
32+
import io.bashpsk.emptylibs.pdfviewer.pdf.PdfLazyColumnProperties
33+
import io.bashpsk.emptylibs.pdfviewer.pdf.PdfLazyColumnState
3034
import kotlinx.coroutines.Dispatchers
3135
import kotlinx.coroutines.FlowPreview
3236
import kotlinx.coroutines.flow.collectLatest
@@ -44,6 +48,7 @@ import kotlin.time.Duration.Companion.milliseconds
4448
* @param isScrolling Whether the user is currently scrolling.
4549
* @param placeholder The color to use for the placeholder.
4650
* @param colorFilter The color filter to apply to the image.
51+
* @param properties The properties for the PDF viewer.
4752
*/
4853
@OptIn(FlowPreview::class)
4954
@Composable
@@ -53,7 +58,8 @@ internal fun PdfPageView(
5358
pageData: PdfPageData = PdfPageData(),
5459
isScrolling: Boolean = false,
5560
placeholder: Color = MaterialTheme.colorScheme.surfaceVariant,
56-
colorFilter: ColorFilter? = null
61+
colorFilter: ColorFilter? = null,
62+
properties: PdfLazyColumnProperties = PdfLazyColumnDefaults.properties()
5763
) {
5864

5965
var scaledBitmap by retain { mutableStateOf<ImageBitmap?>(null) }
@@ -122,7 +128,13 @@ internal fun PdfPageView(
122128
Image(
123129
modifier = Modifier
124130
.fillMaxWidth()
125-
.aspectRatio(ratio = aspectRatio),
131+
.aspectRatio(ratio = aspectRatio)
132+
.drawWithContent {
133+
134+
drawContent()
135+
// drawSearchRectList(pageData = pageData, properties = properties)
136+
// drawSelectRectList(pageData = pageData, properties = properties)
137+
},
126138
bitmap = bitmap,
127139
contentScale = ContentScale.Fit,
128140
colorFilter = colorFilter,

pdf-viewer/src/main/kotlin/io/bashpsk/emptylibs/pdfviewer/pdf/PdfQualityPageData.kt renamed to pdf-viewer/src/main/kotlin/io/bashpsk/emptylibs/pdfviewer/page/PdfScaledPageData.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.bashpsk.emptylibs.pdfviewer.pdf
1+
package io.bashpsk.emptylibs.pdfviewer.page
22

33
import androidx.compose.runtime.Immutable
44
import androidx.compose.ui.graphics.ImageBitmap
@@ -11,7 +11,7 @@ import androidx.compose.ui.graphics.ImageBitmap
1111
* @property bitmap A high-quality bitmap representation of the page.
1212
*/
1313
@Immutable
14-
internal data class PdfQualityPageData(
14+
internal data class PdfScaledPageData(
1515
val page: Int = 0,
1616
val quality: Float = 1.0F,
1717
val bitmap: ImageBitmap? = null

pdf-viewer/src/main/kotlin/io/bashpsk/emptylibs/pdfviewer/pdf/PdfLazyColumn.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import androidx.compose.ui.unit.Dp
2727
import androidx.compose.ui.unit.dp
2828
import io.bashpsk.emptylibs.gestureui.transform.transformableGestures
2929
import io.bashpsk.emptylibs.jetpackui.scrollbar.LazyListScrollBar
30+
import io.bashpsk.emptylibs.pdfviewer.page.PdfPageView
31+
import io.bashpsk.emptylibs.pdfviewer.utils.setDebug
3032
import kotlinx.collections.immutable.toImmutableList
3133

3234
/**
@@ -36,6 +38,9 @@ import kotlinx.collections.immutable.toImmutableList
3638
* @param state The state of the PDF viewer.
3739
* @param pageSpace The space between pages.
3840
* @param scrollBarAlignment The alignment of the scrollbar.
41+
* @param colorFilter The color filter to apply to the image.
42+
* @param placeholder The color to use for the placeholder.
43+
* @param properties The properties for the PDF viewer.
3944
* @param onClick A callback that is invoked when the user clicks on the PDF.
4045
* @param content A slot for composable content to be displayed on top of the PDF.
4146
*/
@@ -47,6 +52,7 @@ fun PdfLazyColumn(
4752
scrollBarAlignment: Alignment = Alignment.TopEnd,
4853
colorFilter: ColorFilter? = null,
4954
placeholder: Color = MaterialTheme.colorScheme.surface,
55+
properties: PdfLazyColumnProperties = PdfLazyColumnDefaults.properties(),
5056
onClick: (offset: Offset) -> Unit = {},
5157
content: @Composable @UiComposable BoxWithConstraintsScope.() -> Unit = {},
5258
) {
@@ -67,6 +73,11 @@ fun PdfLazyColumn(
6773
derivedStateOf { state.transformable.touchCount == 1 }
6874
}
6975

76+
LaunchedEffect(pageDataList) {
77+
78+
"${pageDataList.firstOrNull()}".setDebug()
79+
}
80+
7081
BoxWithConstraints(
7182
modifier = modifier
7283
.clipToBounds()
@@ -106,7 +117,8 @@ fun PdfLazyColumn(
106117
pageData = pageData,
107118
isScrolling = isScrolling,
108119
placeholder = placeholder,
109-
colorFilter = colorFilter
120+
colorFilter = colorFilter,
121+
properties = properties
110122
)
111123
}
112124
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.bashpsk.emptylibs.pdfviewer.pdf
2+
3+
import androidx.compose.material3.MaterialTheme
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.ui.graphics.Color
6+
7+
/**
8+
* Contains the default values used by a PDF lazy column.
9+
*/
10+
object PdfLazyColumnDefaults {
11+
12+
/**
13+
* Creates a [PdfLazyColumnProperties] instance with configurable colors for search and
14+
* selection highlights.
15+
*
16+
* @param searchBoxColor The color applied to the highlight boxes when searching for text within
17+
* the PDF.
18+
* @param selectBoxColor The color applied to the highlight boxes when selecting text within the
19+
* PDF.
20+
* @return A [PdfLazyColumnProperties] object containing the specified color configurations.
21+
*/
22+
@Composable
23+
fun properties(
24+
searchBoxColor: Color = MaterialTheme.colorScheme.primary.copy(alpha = 0.5F),
25+
selectBoxColor: Color = MaterialTheme.colorScheme.primaryFixed.copy(alpha = 0.5F)
26+
): PdfLazyColumnProperties {
27+
28+
return PdfLazyColumnProperties(
29+
searchBoxColor = searchBoxColor,
30+
selectBoxColor = selectBoxColor
31+
)
32+
}
33+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.bashpsk.emptylibs.pdfviewer.pdf
2+
3+
import androidx.compose.runtime.Stable
4+
import androidx.compose.ui.graphics.Color
5+
6+
/**
7+
* Configuration properties for styling the lazy column within a PDF viewer.
8+
*
9+
* @property searchBoxColor The [Color] used to highlight text matches during a search operation.
10+
* @property selectBoxColor The [Color] used to highlight the background of selected text.
11+
*/
12+
@Stable
13+
data class PdfLazyColumnProperties(
14+
val searchBoxColor: Color = Color.Unspecified,
15+
val selectBoxColor: Color = Color.Unspecified
16+
)

0 commit comments

Comments
 (0)