* Add performance and stylus debugging logs * Refactor and decouple UI models from `MainViewModel` * Refactor library state management and projection logic * Implement desktop shell using Compose Multiplatform * Implement desktop shell using Compose Multiplatform * Implement desktop shell using Compose Multiplatform * Introduce ReaderEngine and enhance EPUB reader features in windows app * Move core paginated reader logic to a Kotlin Multiplatform `shared` module and introduce experimental desktop support. * Implement PDF rendering and text extraction for desktop using Pdfium * Add `NonReaderScreens.kt` and UI dependencies * Refactor and centralize library state management and models to improve cross-platform consistency * Implement JSON persistence for desktop library and enhance library management features including shelf CRUD, tagging, and metadata editing * Implement PDF annotation system and enhanced zoom controls for the desktop viewer * Implement WebView-based EPUB rendering for desktop using CEF and embedded resources * Optimize UI state projection, navigation state handling, and main screen pager performance * Implement Bring Your Own Key (BYOK) support for AI features in OSS version * Support Gemini-based Cloud TTS with BYOK support for OSS builds * Refactor table cell image sizing in `PaginatedReader` and improve `MobiParser` native library loading and error handling. * crash fixes * Enhance navigation stability with lifecycle-aware safety checks and update `navigation-compose` to 2.9.6 * Implement dynamic bottom padding for the page info bar to account for device rounded corners * Implement bidirectional jump history navigation and replace the jump-back pill with a dedicated `PdfJumpHistoryBar` * Optimize PDF tiling performance and refine pan-and-fling gesture handling * Implement customizable toolbars with drag-and-drop reordering and placement for PDF and EPUB readers * Updated UI for customize toolbar * Refine drag-and-drop reordering and section assignment for PDF and EPUB reader controls * restructure PDF viewer UI component hierarchy to fix verifier crash * Implement separate text dimming factors for light and dark themes * Synchronize Pdfium access and improve resource lifecycle safety across Kotlin and native layers * Enhance image alignment in paginated and EPUB readers through anchor detection and style-based positioning * Centralize file type resolution logic and implement HTML sanitization during import * Introduce vertical margin customization and configurable progress bar positioning * texture support in epub reader * Enhance TTS session management, progress tracking, and diagnostic logging * Optimize library state projection and folder synchronization performance by refactoring collection lookups and refining metadata extraction logic. * Refine TTS page mapping for PDF and overhaul TTS control UI * Implement natural session completion logic in `TtsPlaybackManager` for cloud tts * Replace Snackbar with `CustomTopBanner` for notifications in `PdfViewerScreen` * Refine TTS playback continuity across PDF pages and improve state management for session transitions * Implement global texture transparency and enhance textured theme support across PDF and EPUB readers. * Update reader themes and improve texture rendering in page animations, EPUB UI, and immersive mode * Add Support Project screen * Optimize library performance via projection caching, batch database updates, and scoped folder synchronization. * Enhance folder synchronization with fallback query mechanisms and refactor annotation sidecar importing logic * Bump version to 1.0.47 (51)
412 lines
No EOL
20 KiB
Kotlin
412 lines
No EOL
20 KiB
Kotlin
/*
|
|
* Episteme Reader - A native Android document reader.
|
|
* Copyright (C) 2026 Episteme
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* mail: epistemereader@gmail.com
|
|
*/
|
|
@file:OptIn(ExperimentalSerializationApi::class)
|
|
package com.aryan.reader.paginatedreader
|
|
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.graphics.isSpecified
|
|
import androidx.compose.ui.text.AnnotatedString
|
|
import androidx.compose.ui.text.ParagraphStyle
|
|
import androidx.compose.ui.text.SpanStyle
|
|
import androidx.compose.ui.text.font.FontStyle
|
|
import androidx.compose.ui.text.font.FontWeight
|
|
import androidx.compose.ui.text.style.TextAlign
|
|
import androidx.compose.ui.unit.Dp
|
|
import androidx.compose.ui.unit.TextUnit
|
|
import androidx.compose.ui.unit.dp
|
|
import androidx.compose.ui.unit.isSpecified
|
|
import com.aryan.reader.paginatedreader.serialization.AnnotatedStringSerializer
|
|
import com.aryan.reader.paginatedreader.serialization.ColorSerializer
|
|
import com.aryan.reader.paginatedreader.serialization.DpSerializer
|
|
import com.aryan.reader.paginatedreader.serialization.ParagraphStyleSerializer
|
|
import com.aryan.reader.paginatedreader.serialization.SpanStyleSerializer
|
|
import com.aryan.reader.paginatedreader.serialization.TextAlignSerializer
|
|
import com.aryan.reader.paginatedreader.serialization.TextUnitSerializer
|
|
import kotlinx.serialization.ExperimentalSerializationApi
|
|
import kotlinx.serialization.Serializable
|
|
import kotlinx.serialization.protobuf.ProtoNumber
|
|
|
|
@Serializable
|
|
data class BlockStyle(
|
|
@ProtoNumber(1) val margin: BoxBorders = BoxBorders(),
|
|
@ProtoNumber(2) val padding: BoxBorders = BoxBorders(),
|
|
@ProtoNumber(3) @Serializable(with = DpSerializer::class) val width: Dp = Dp.Unspecified,
|
|
@ProtoNumber(4) @Serializable(with = DpSerializer::class) val maxWidth: Dp = Dp.Unspecified,
|
|
@ProtoNumber(5) @Serializable(with = DpSerializer::class) val height: Dp = Dp.Unspecified,
|
|
@ProtoNumber(6) @Serializable(with = ColorSerializer::class) val backgroundColor: Color = Color.Unspecified,
|
|
@ProtoNumber(7) val borderTop: BorderStyle? = null,
|
|
@ProtoNumber(8) val borderRight: BorderStyle? = null,
|
|
@ProtoNumber(9) val borderBottom: BorderStyle? = null,
|
|
@ProtoNumber(10) val borderLeft: BorderStyle? = null,
|
|
@ProtoNumber(11) val listStyleType: String? = null,
|
|
@ProtoNumber(12) val listStyleImage: String? = null,
|
|
@ProtoNumber(13) val pageBreakInsideAvoid: Boolean = false,
|
|
@ProtoNumber(14) val pageBreakAfterAvoid: Boolean = false,
|
|
@ProtoNumber(15) val boxSizing: String? = null,
|
|
@ProtoNumber(16) val float: String? = null,
|
|
@ProtoNumber(17) val clear: String? = null,
|
|
@ProtoNumber(18) val position: String? = null,
|
|
@ProtoNumber(19) @Serializable(with = DpSerializer::class) val top: Dp = Dp.Unspecified,
|
|
@ProtoNumber(20) @Serializable(with = DpSerializer::class) val right: Dp = Dp.Unspecified,
|
|
@ProtoNumber(21) @Serializable(with = DpSerializer::class) val bottom: Dp = Dp.Unspecified,
|
|
@ProtoNumber(22) @Serializable(with = DpSerializer::class) val left: Dp = Dp.Unspecified,
|
|
@ProtoNumber(23) val display: String? = null,
|
|
@ProtoNumber(24) val flexDirection: String? = null,
|
|
@ProtoNumber(25) val justifyContent: String? = null,
|
|
@ProtoNumber(26) val alignItems: String? = null,
|
|
@ProtoNumber(27) val horizontalAlign: String? = null,
|
|
@ProtoNumber(28) val filter: String? = null,
|
|
@ProtoNumber(29) val borderCollapse: String? = null,
|
|
@ProtoNumber(30) @Serializable(with = DpSerializer::class) val borderTopLeftRadius: Dp = 0.dp,
|
|
@ProtoNumber(31) @Serializable(with = DpSerializer::class) val borderTopRightRadius: Dp = 0.dp,
|
|
@ProtoNumber(32) @Serializable(with = DpSerializer::class) val borderBottomRightRadius: Dp = 0.dp,
|
|
@ProtoNumber(33) @Serializable(with = DpSerializer::class) val borderBottomLeftRadius: Dp = 0.dp,
|
|
@ProtoNumber(34) @Serializable(with = DpSerializer::class) val borderSpacing: Dp = 0.dp
|
|
) {
|
|
fun merge(other: BlockStyle): BlockStyle {
|
|
return BlockStyle(
|
|
margin = BoxBorders(
|
|
top = if (other.margin.top != 0.dp) other.margin.top else this.margin.top,
|
|
bottom = if (other.margin.bottom != 0.dp) other.margin.bottom else this.margin.bottom,
|
|
left = if (other.margin.left != 0.dp) other.margin.left else this.margin.left,
|
|
right = if (other.margin.right != 0.dp) other.margin.right else this.margin.right
|
|
),
|
|
padding = BoxBorders(
|
|
top = if (other.padding.top != 0.dp) other.padding.top else this.padding.top,
|
|
bottom = if (other.padding.bottom != 0.dp) other.padding.bottom else this.padding.bottom,
|
|
left = if (other.padding.left != 0.dp) other.padding.left else this.padding.left,
|
|
right = if (other.padding.right != 0.dp) other.padding.right else this.padding.right
|
|
),
|
|
width = if (other.width != Dp.Unspecified) other.width else this.width,
|
|
maxWidth = if (other.maxWidth != Dp.Unspecified) other.maxWidth else this.maxWidth,
|
|
height = if (other.height != Dp.Unspecified) other.height else this.height,
|
|
backgroundColor = if (other.backgroundColor.isSpecified) other.backgroundColor else this.backgroundColor,
|
|
borderTop = other.borderTop ?: this.borderTop,
|
|
borderRight = other.borderRight ?: this.borderRight,
|
|
borderBottom = other.borderBottom ?: this.borderBottom,
|
|
borderLeft = other.borderLeft ?: this.borderLeft,
|
|
borderTopLeftRadius = if (other.borderTopLeftRadius != 0.dp) other.borderTopLeftRadius else this.borderTopLeftRadius,
|
|
borderTopRightRadius = if (other.borderTopRightRadius != 0.dp) other.borderTopRightRadius else this.borderTopRightRadius,
|
|
borderBottomRightRadius = if (other.borderBottomRightRadius != 0.dp) other.borderBottomRightRadius else this.borderBottomRightRadius,
|
|
borderBottomLeftRadius = if (other.borderBottomLeftRadius != 0.dp) other.borderBottomLeftRadius else this.borderBottomLeftRadius,
|
|
listStyleType = other.listStyleType ?: this.listStyleType,
|
|
listStyleImage = other.listStyleImage ?: this.listStyleImage,
|
|
pageBreakInsideAvoid = this.pageBreakInsideAvoid || other.pageBreakInsideAvoid,
|
|
pageBreakAfterAvoid = this.pageBreakAfterAvoid || other.pageBreakAfterAvoid,
|
|
boxSizing = other.boxSizing ?: this.boxSizing,
|
|
float = other.float ?: this.float,
|
|
clear = other.clear ?: this.clear,
|
|
position = other.position ?: this.position,
|
|
top = if (other.top.isSpecified) other.top else this.top,
|
|
right = if (other.right.isSpecified) other.right else this.right,
|
|
bottom = if (other.bottom.isSpecified) other.bottom else this.bottom,
|
|
left = if (other.left.isSpecified) other.left else this.left,
|
|
display = other.display ?: this.display,
|
|
flexDirection = other.flexDirection ?: this.flexDirection,
|
|
justifyContent = other.justifyContent ?: this.justifyContent,
|
|
alignItems = other.alignItems ?: this.alignItems,
|
|
horizontalAlign = other.horizontalAlign ?: this.horizontalAlign,
|
|
filter = other.filter ?: this.filter,
|
|
borderCollapse = other.borderCollapse ?: this.borderCollapse,
|
|
borderSpacing = if (other.borderSpacing != 0.dp) other.borderSpacing else this.borderSpacing
|
|
)
|
|
}
|
|
}
|
|
|
|
@Serializable
|
|
data class BoxBorders(
|
|
@ProtoNumber(1) @Serializable(with = DpSerializer::class) val top: Dp = 0.dp,
|
|
@ProtoNumber(2) @Serializable(with = DpSerializer::class) val right: Dp = 0.dp,
|
|
@ProtoNumber(3) @Serializable(with = DpSerializer::class) val bottom: Dp = 0.dp,
|
|
@ProtoNumber(4) @Serializable(with = DpSerializer::class) val left: Dp = 0.dp
|
|
)
|
|
|
|
@Serializable
|
|
data class BorderStyle(
|
|
@ProtoNumber(1) @Serializable(with = DpSerializer::class) val width: Dp = 0.dp,
|
|
@ProtoNumber(2) @Serializable(with = ColorSerializer::class) val color: Color = Color.Transparent,
|
|
@ProtoNumber(3) val style: String = "solid"
|
|
)
|
|
|
|
@Serializable
|
|
sealed interface ContentBlock {
|
|
val style: BlockStyle
|
|
val elementId: String?
|
|
val cfi: String?
|
|
val blockIndex: Int
|
|
val expectedHeight: Int
|
|
}
|
|
|
|
sealed interface TextContentBlock : ContentBlock {
|
|
val content: AnnotatedString
|
|
val startCharOffsetInSource: Int
|
|
val endCharOffsetInSource: Int
|
|
}
|
|
|
|
@Serializable
|
|
data class ParagraphBlock(
|
|
@ProtoNumber(1) @Serializable(with = AnnotatedStringSerializer::class) override val content: AnnotatedString,
|
|
@ProtoNumber(2) @Serializable(with = TextAlignSerializer::class) val textAlign: TextAlign? = null,
|
|
@ProtoNumber(3) override val style: BlockStyle = BlockStyle(),
|
|
@ProtoNumber(4) override val elementId: String? = null,
|
|
@ProtoNumber(5) override val cfi: String? = null,
|
|
@ProtoNumber(6) override val startCharOffsetInSource: Int = 0,
|
|
@ProtoNumber(7) override val endCharOffsetInSource: Int = -1,
|
|
@ProtoNumber(8) override val blockIndex: Int,
|
|
@ProtoNumber(9) override val expectedHeight: Int = 0
|
|
) : TextContentBlock
|
|
|
|
@Serializable
|
|
data class ImageBlock(
|
|
@ProtoNumber(1) val path: String,
|
|
@ProtoNumber(2) val altText: String?,
|
|
@ProtoNumber(3) val intrinsicWidth: Float? = null,
|
|
@ProtoNumber(4) val intrinsicHeight: Float? = null,
|
|
@ProtoNumber(5) override val style: BlockStyle = BlockStyle(),
|
|
@ProtoNumber(6) override val elementId: String? = null,
|
|
@ProtoNumber(7) override val cfi: String? = null,
|
|
@ProtoNumber(8) val invertOnDarkTheme: Boolean = false,
|
|
@ProtoNumber(9) override val blockIndex: Int,
|
|
@ProtoNumber(10) override val expectedHeight: Int = 0
|
|
) : ContentBlock
|
|
|
|
@Serializable
|
|
data class HeaderBlock(
|
|
@ProtoNumber(1) val level: Int,
|
|
@ProtoNumber(2) @Serializable(with = AnnotatedStringSerializer::class) override val content: AnnotatedString,
|
|
@ProtoNumber(3) @Serializable(with = TextAlignSerializer::class) val textAlign: TextAlign? = null,
|
|
@ProtoNumber(4) override val style: BlockStyle = BlockStyle(),
|
|
@ProtoNumber(5) override val elementId: String? = null,
|
|
@ProtoNumber(6) override val cfi: String? = null,
|
|
@ProtoNumber(7) override val startCharOffsetInSource: Int = 0,
|
|
@ProtoNumber(8) override val endCharOffsetInSource: Int = -1,
|
|
@ProtoNumber(9) override val blockIndex: Int,
|
|
@ProtoNumber(10) override val expectedHeight: Int = 0
|
|
) : TextContentBlock
|
|
|
|
@Serializable
|
|
data class SpacerBlock(
|
|
@ProtoNumber(1) @Serializable(with = DpSerializer::class) val height: Dp = 8.dp,
|
|
@ProtoNumber(2) override val style: BlockStyle = BlockStyle(),
|
|
@ProtoNumber(3) override val elementId: String? = null,
|
|
@ProtoNumber(4) override val cfi: String? = null,
|
|
@ProtoNumber(5) override val blockIndex: Int,
|
|
@ProtoNumber(6) override val expectedHeight: Int = 0
|
|
) : ContentBlock
|
|
|
|
@Serializable
|
|
data class QuoteBlock(
|
|
@ProtoNumber(1) @Serializable(with = AnnotatedStringSerializer::class) override val content: AnnotatedString,
|
|
@ProtoNumber(2) @Serializable(with = TextAlignSerializer::class) val textAlign: TextAlign? = null,
|
|
@ProtoNumber(3) override val style: BlockStyle = BlockStyle(),
|
|
@ProtoNumber(4) override val elementId: String? = null,
|
|
@ProtoNumber(5) override val cfi: String? = null,
|
|
@ProtoNumber(6) override val startCharOffsetInSource: Int = 0,
|
|
@ProtoNumber(7) override val endCharOffsetInSource: Int = -1,
|
|
@ProtoNumber(8) override val blockIndex: Int,
|
|
@ProtoNumber(9) override val expectedHeight: Int = 0
|
|
) : TextContentBlock
|
|
|
|
@Serializable
|
|
data class ListItemBlock(
|
|
@ProtoNumber(1) @Serializable(with = AnnotatedStringSerializer::class) override val content: AnnotatedString,
|
|
@ProtoNumber(2) val itemMarker: String?,
|
|
@ProtoNumber(3) val itemMarkerImage: String? = null,
|
|
@ProtoNumber(4) override val style: BlockStyle,
|
|
@ProtoNumber(5) override val elementId: String? = null,
|
|
@ProtoNumber(6) override val cfi: String? = null,
|
|
@ProtoNumber(7) override val startCharOffsetInSource: Int = 0,
|
|
@ProtoNumber(8) override val endCharOffsetInSource: Int = -1,
|
|
@ProtoNumber(9) override val blockIndex: Int,
|
|
@ProtoNumber(10) override val expectedHeight: Int = 0
|
|
) : TextContentBlock
|
|
|
|
@Serializable
|
|
data class TableCell(
|
|
@ProtoNumber(1) val content: List<ContentBlock>,
|
|
@ProtoNumber(2) val isHeader: Boolean = false,
|
|
@ProtoNumber(3) val style: CssStyle = CssStyle(),
|
|
@ProtoNumber(4) val colspan: Int = 1
|
|
)
|
|
|
|
@Serializable
|
|
data class TableBlock(
|
|
@ProtoNumber(1) val rows: List<List<TableCell>>,
|
|
@ProtoNumber(2) override val style: BlockStyle = BlockStyle(),
|
|
@ProtoNumber(3) override val elementId: String? = null,
|
|
@ProtoNumber(4) override val cfi: String? = null,
|
|
@ProtoNumber(5) override val blockIndex: Int,
|
|
@ProtoNumber(6) override val expectedHeight: Int = 0
|
|
) : ContentBlock
|
|
|
|
@Serializable
|
|
data class MathBlock(
|
|
@ProtoNumber(1) val svgContent: String?,
|
|
@ProtoNumber(2) val altText: String?,
|
|
@ProtoNumber(3) override val style: BlockStyle,
|
|
@ProtoNumber(4) override val elementId: String?,
|
|
@ProtoNumber(5) override val cfi: String?,
|
|
@ProtoNumber(6) val svgWidth: String? = null,
|
|
@ProtoNumber(7) val svgHeight: String? = null,
|
|
@ProtoNumber(8) val svgViewBox: String? = null,
|
|
@ProtoNumber(9) val isFromMathJax: Boolean = false,
|
|
@ProtoNumber(10) override val blockIndex: Int,
|
|
@ProtoNumber(11) override val expectedHeight: Int = 0
|
|
) : ContentBlock
|
|
|
|
@Serializable
|
|
data class WrappingContentBlock(
|
|
@ProtoNumber(1) val floatedImage: ImageBlock,
|
|
@ProtoNumber(2) val paragraphsToWrap: List<ParagraphBlock>,
|
|
@ProtoNumber(3) override val style: BlockStyle = BlockStyle(),
|
|
@ProtoNumber(4) override val elementId: String? = null,
|
|
@ProtoNumber(5) override val cfi: String? = null,
|
|
@ProtoNumber(6) override val blockIndex: Int,
|
|
@ProtoNumber(7) override val expectedHeight: Int = 0
|
|
) : ContentBlock
|
|
|
|
@Serializable
|
|
data class TextEmphasis(
|
|
@ProtoNumber(1) val style: String? = null,
|
|
@ProtoNumber(2) val fill: String? = null,
|
|
@ProtoNumber(3) @Serializable(with = ColorSerializer::class) val color: Color = Color.Unspecified,
|
|
@ProtoNumber(4) val position: String? = null
|
|
)
|
|
|
|
@Serializable
|
|
data class CssStyle(
|
|
@ProtoNumber(1) @Serializable(with = SpanStyleSerializer::class) val spanStyle: SpanStyle = SpanStyle(),
|
|
@ProtoNumber(2) @Serializable(with = ParagraphStyleSerializer::class) val paragraphStyle: ParagraphStyle = ParagraphStyle(),
|
|
@ProtoNumber(3) val blockStyle: BlockStyle = BlockStyle(),
|
|
@ProtoNumber(4) val fontFamilies: List<String> = emptyList(),
|
|
@ProtoNumber(5) val display: String? = null,
|
|
@ProtoNumber(6) @Serializable(with = TextUnitSerializer::class) val fontSize: TextUnit = TextUnit.Unspecified,
|
|
@ProtoNumber(7) val textTransform: String? = null,
|
|
@ProtoNumber(8) val boxSizing: String? = null,
|
|
@ProtoNumber(9) val content: String? = null,
|
|
@ProtoNumber(10) val hyphens: String? = null,
|
|
@ProtoNumber(11) val fontVariantNumeric: String? = null,
|
|
@ProtoNumber(12) val textEmphasis: TextEmphasis? = null,
|
|
@ProtoNumber(13) @Serializable(with = TextUnitSerializer::class) val wordSpacing: TextUnit = TextUnit.Unspecified,
|
|
@ProtoNumber(14) val textDecorationStyle: String? = null,
|
|
@ProtoNumber(15) @Serializable(with = ColorSerializer::class) val textDecorationColor: Color = Color.Unspecified,
|
|
@ProtoNumber(16) @Serializable(with = DpSerializer::class) val textUnderlineOffset: Dp = Dp.Unspecified
|
|
) {
|
|
fun merge(other: CssStyle): CssStyle {
|
|
return CssStyle(
|
|
spanStyle = this.spanStyle.merge(other.spanStyle),
|
|
paragraphStyle = this.paragraphStyle.merge(other.paragraphStyle),
|
|
blockStyle = this.blockStyle.merge(other.blockStyle),
|
|
fontFamilies = other.fontFamilies.takeIf { it.isNotEmpty() } ?: this.fontFamilies,
|
|
display = other.display ?: this.display,
|
|
fontSize = if (other.fontSize.isSpecified) other.fontSize else this.fontSize,
|
|
textTransform = other.textTransform ?: this.textTransform,
|
|
boxSizing = other.boxSizing ?: this.boxSizing,
|
|
content = other.content ?: this.content,
|
|
hyphens = other.hyphens ?: this.hyphens,
|
|
fontVariantNumeric = other.fontVariantNumeric ?: this.fontVariantNumeric,
|
|
textEmphasis = other.textEmphasis ?: this.textEmphasis,
|
|
wordSpacing = if (other.wordSpacing.isSpecified) other.wordSpacing else this.wordSpacing,
|
|
textDecorationStyle = other.textDecorationStyle ?: this.textDecorationStyle,
|
|
textDecorationColor = if (other.textDecorationColor.isSpecified) other.textDecorationColor else this.textDecorationColor,
|
|
textUnderlineOffset = if (other.textUnderlineOffset.isSpecified) other.textUnderlineOffset else this.textUnderlineOffset
|
|
)
|
|
}
|
|
}
|
|
|
|
@Serializable
|
|
data class CssSelector(
|
|
@ProtoNumber(1) val selector: String,
|
|
@ProtoNumber(2) val specificity: Int
|
|
)
|
|
|
|
@Serializable
|
|
data class CssRule(
|
|
@ProtoNumber(1) val selector: CssSelector,
|
|
@ProtoNumber(2) val style: CssStyle
|
|
)
|
|
|
|
@Serializable
|
|
data class FontFaceInfo(
|
|
@ProtoNumber(1) val fontFamily: String,
|
|
@ProtoNumber(2) val src: String,
|
|
@ProtoNumber(3) @Serializable(with = com.aryan.reader.paginatedreader.serialization.FontWeightSerializer::class) val fontWeight: FontWeight?,
|
|
@ProtoNumber(4) @Serializable(with = com.aryan.reader.paginatedreader.serialization.FontStyleSerializer::class) val fontStyle: FontStyle?
|
|
)
|
|
|
|
@Serializable
|
|
data class Page(
|
|
@ProtoNumber(1) val content: List<ContentBlock>
|
|
)
|
|
|
|
@Serializable
|
|
data class FlexContainerBlock(
|
|
@ProtoNumber(1) val children: List<ContentBlock>,
|
|
@ProtoNumber(2) override val style: BlockStyle = BlockStyle(),
|
|
@ProtoNumber(3) override val elementId: String? = null,
|
|
@ProtoNumber(4) override val cfi: String? = null,
|
|
@ProtoNumber(5) override val blockIndex: Int,
|
|
@ProtoNumber(6) override val expectedHeight: Int = 0
|
|
) : ContentBlock
|
|
|
|
@Serializable
|
|
data class OptimizedCssRules(
|
|
@ProtoNumber(1) val byTag: Map<String, List<CssRule>> = emptyMap(),
|
|
@ProtoNumber(2) val byClass: Map<String, List<CssRule>> = emptyMap(),
|
|
@ProtoNumber(3) val byId: Map<String, List<CssRule>> = emptyMap(),
|
|
@ProtoNumber(4) val otherComplex: List<CssRule> = emptyList()
|
|
) {
|
|
fun merge(other: OptimizedCssRules): OptimizedCssRules {
|
|
fun mergeMap(
|
|
m1: Map<String, List<CssRule>>,
|
|
m2: Map<String, List<CssRule>>
|
|
): Map<String, List<CssRule>> {
|
|
if (m1.isEmpty()) return m2
|
|
if (m2.isEmpty()) return m1
|
|
|
|
val result = LinkedHashMap(m1)
|
|
for ((key, value) in m2) {
|
|
val existing = result[key]
|
|
if (existing != null) {
|
|
result[key] = existing + value
|
|
} else {
|
|
result[key] = value
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
return OptimizedCssRules(
|
|
byTag = mergeMap(this.byTag, other.byTag),
|
|
byClass = mergeMap(this.byClass, other.byClass),
|
|
byId = mergeMap(this.byId, other.byId),
|
|
otherComplex = this.otherComplex + other.otherComplex
|
|
)
|
|
}
|
|
|
|
fun toFlatList(): List<CssRule> {
|
|
return byTag.values.flatten() + byClass.values.flatten() + byId.values.flatten() + otherComplex
|
|
}
|
|
}
|
|
|
|
data class OptimizedCssParseResult(
|
|
val rules: OptimizedCssRules,
|
|
val fontFaces: List<FontFaceInfo>
|
|
) |