HapeLee 2 месяцев назад
Родитель
Сommit
bc0843cb94

+ 5 - 12
app/src/main/java/io/legado/app/feature/reader/core/layout/ReaderChapterBlockMeasurer.kt

@@ -75,7 +75,6 @@ data class ReaderChapterMeasureStyle(
     val bodyIndentText: String? = null,
     val bodyIndentText: String? = null,
     val imageLayoutMode: ReaderImageLayoutMode = ReaderImageLayoutMode.AUTO,
     val imageLayoutMode: ReaderImageLayoutMode = ReaderImageLayoutMode.AUTO,
     val imageAvailableWidthPx: Float? = null,
     val imageAvailableWidthPx: Float? = null,
-    val imageAvailableHeightPx: Float? = null,
 )
 )
 
 
 sealed interface ReaderChapterMeasureResult {
 sealed interface ReaderChapterMeasureResult {
@@ -260,17 +259,11 @@ class ReaderChapterBlockMeasurer(
                                 pageBreakAfter = mode == ReaderImageLayoutMode.SINGLE_PAGE,
                                 pageBreakAfter = mode == ReaderImageLayoutMode.SINGLE_PAGE,
                             )
                             )
                         } else {
                         } else {
-                            // The View reader replaces the inline placeholder with an image
-                            // fitted to the visible text area. Do the final fitting during
-                            // measurement so following content gets the same stable geometry.
-                            val availableWidth = style.imageAvailableWidthPx
-                                ?.takeIf { it > 0f } ?: size.widthPx
-                            val availableHeight = style.imageAvailableHeightPx
-                                ?.takeIf { it > 0f } ?: size.heightPx
-                            val scale = minOf(
-                                availableWidth / size.widthPx.coerceAtLeast(1f),
-                                availableHeight / size.heightPx.coerceAtLeast(1f),
-                            )
+                            // 文字嵌入(行内图):与 View 实现一致,图片作为段内占位参与行排版,
+                            // 只允许缩小到不超过当前行高(禁止放大到铺满整页文字区)。行高上限
+                            // 让紧随其后的内容保持与行内占位一致且稳定的几何。
+                            val maxHeight = lineHeight ?: baseStyle.fontSizePx
+                            val scale = minOf(1f, maxHeight / size.heightPx.coerceAtLeast(1f))
                             inline += ReaderMeasuredInlineItem.Image(
                             inline += ReaderMeasuredInlineItem.Image(
                                 source = item.source,
                                 source = item.source,
                                 widthPx = size.widthPx * scale,
                                 widthPx = size.widthPx * scale,

+ 1 - 7
app/src/main/java/io/legado/app/feature/reader/legacy/LegacyReaderChapterPaginator.kt

@@ -170,7 +170,7 @@ object LegacyReaderChapterPaginator {
                 bodyIndentText = ReadBookConfig.paragraphIndent,
                 bodyIndentText = ReadBookConfig.paragraphIndent,
                 bodyAlignment = if (ReadBookConfig.textFullJustify) ReaderTextAlignment.JUSTIFY else ReaderTextAlignment.START,
                 bodyAlignment = if (ReadBookConfig.textFullJustify) ReaderTextAlignment.JUSTIFY else ReaderTextAlignment.START,
                 titleAlignment = if (ReadBookConfig.isMiddleTitle || chapter.isVolume ||
                 titleAlignment = if (ReadBookConfig.isMiddleTitle || chapter.isVolume ||
-                    content.textList.isEmpty() || singleImage
+                    content.textList.isEmpty()
                 ) ReaderTextAlignment.CENTER else ReaderTextAlignment.START,
                 ) ReaderTextAlignment.CENTER else ReaderTextAlignment.START,
                 imagePageBreakBefore = singleImage,
                 imagePageBreakBefore = singleImage,
                 imagePageBreakAfter = singleImage,
                 imagePageBreakAfter = singleImage,
@@ -180,12 +180,6 @@ object LegacyReaderChapterPaginator {
                         paginationStyle.paddingLeftPx - paginationStyle.paddingRightPx -
                         paginationStyle.paddingLeftPx - paginationStyle.paddingRightPx -
                         contentPaddingLeftPx - contentPaddingRightPx
                         contentPaddingLeftPx - contentPaddingRightPx
                     ).coerceAtLeast(0).toFloat(),
                     ).coerceAtLeast(0).toFloat(),
-                imageAvailableHeightPx = (
-                        viewportHeightPx.toFloat() - paginationStyle.paddingTopPx - paginationStyle.paddingBottomPx -
-                                contentPaddingTopPx - contentPaddingBottomPx -
-                                LegacyReaderPageDecorationFactory.headerExtentPx() -
-                                LegacyReaderPageDecorationFactory.footerExtentPx()
-                        ).coerceAtLeast(0f),
                 bodyLineHeightPx = paginationStyle.bodyTextHeightPx,
                 bodyLineHeightPx = paginationStyle.bodyTextHeightPx,
                 bodyBaselineOffsetPx = paginationStyle.bodyBaselineOffsetPx,
                 bodyBaselineOffsetPx = paginationStyle.bodyBaselineOffsetPx,
                 titleLineHeightPx = paginationStyle.titleTextHeightPx,
                 titleLineHeightPx = paginationStyle.titleTextHeightPx,

+ 15 - 5
app/src/main/java/io/legado/app/feature/reader/legacy/LegacyReaderImageOptionsResolver.kt

@@ -3,6 +3,7 @@ package io.legado.app.feature.reader.legacy
 import io.legado.app.feature.reader.core.layout.ReaderImageLayoutMode
 import io.legado.app.feature.reader.core.layout.ReaderImageLayoutMode
 import io.legado.app.feature.reader.core.layout.ReaderImageOptions
 import io.legado.app.feature.reader.core.layout.ReaderImageOptions
 import io.legado.app.feature.reader.core.layout.ReaderImageOptionsResolver
 import io.legado.app.feature.reader.core.layout.ReaderImageOptionsResolver
+import io.legado.app.feature.reader.core.layout.ReaderTextAlignment
 import io.legado.app.model.analyzeRule.AnalyzeUrl.Companion.paramPattern
 import io.legado.app.model.analyzeRule.AnalyzeUrl.Companion.paramPattern
 import io.legado.app.utils.GSON
 import io.legado.app.utils.GSON
 import io.legado.app.utils.fromJsonObject
 import io.legado.app.utils.fromJsonObject
@@ -15,16 +16,25 @@ object LegacyReaderImageOptionsResolver : ReaderImageOptionsResolver {
             source.substring(separator.range.last + 1),
             source.substring(separator.range.last + 1),
         ).getOrNull() ?: return null
         ).getOrNull() ?: return null
         val style = values["style"]?.uppercase()
         val style = values["style"]?.uppercase()
-        val action = values["click"]?.takeIf(String::isNotBlank)
-        // The View reader only treats a source-level TEXT style as an override. FULL,
-        // SINGLE, alignment and width metadata do not replace the reader's selected image
-        // style; retaining that precedence keeps existing book-source rendering compatible.
+        val width = values["width"]
         return ReaderImageOptions(
         return ReaderImageOptions(
             layoutMode = when (style) {
             layoutMode = when (style) {
+                null -> null
                 "TEXT" -> ReaderImageLayoutMode.INLINE
                 "TEXT" -> ReaderImageLayoutMode.INLINE
+                "FULL" -> ReaderImageLayoutMode.FULL_WIDTH
+                "SINGLE" -> ReaderImageLayoutMode.SINGLE_PAGE
+                else -> ReaderImageLayoutMode.STANDALONE
+            },
+            requestedWidthPx = width?.takeUnless { it.endsWith('%') }?.toFloatOrNull()
+                ?.takeIf { it > 0f },
+            requestedWidthFraction = width?.takeIf { it.endsWith('%') }
+                ?.dropLast(1)?.toFloatOrNull()?.div(100f)?.takeIf { it > 0f },
+            horizontalAlignment = when (style) {
+                "LEFT" -> ReaderTextAlignment.START
+                "RIGHT" -> ReaderTextAlignment.END
                 else -> null
                 else -> null
             },
             },
-            action = action,
+            action = values["click"]?.takeIf(String::isNotBlank),
         )
         )
     }
     }
 }
 }

+ 25 - 8
app/src/test/java/io/legado/app/feature/reader/core/layout/ReaderImageOptionsTest.kt

@@ -38,30 +38,47 @@ class ReaderImageOptionsTest {
     }
     }
 
 
     @Test
     @Test
-    fun inlineImageFitsTheVisibleTextArea() = runBlocking {
+    fun inlineImageStaysWithinParagraphLineNotTextArea() = runBlocking {
         val result = measure(
         val result = measure(
             ReaderImageOptions(ReaderImageLayoutMode.INLINE, action = "run()"),
             ReaderImageOptions(ReaderImageLayoutMode.INLINE, action = "run()"),
             baseStyle.copy(
             baseStyle.copy(
                 imageLayoutMode = ReaderImageLayoutMode.INLINE,
                 imageLayoutMode = ReaderImageLayoutMode.INLINE,
                 imageAvailableWidthPx = 100f,
                 imageAvailableWidthPx = 100f,
-                imageAvailableHeightPx = 40f,
             ),
             ),
-            ReaderImageDimensions(20f, 10f),
+            ReaderImageDimensions(100f, 50f),
         )
         )
         val paragraph = result.blocks.single() as ReaderMeasuredBlock.InlineParagraph
         val paragraph = result.blocks.single() as ReaderMeasuredBlock.InlineParagraph
         val image = paragraph.items.single() as ReaderMeasuredInlineItem.Image
         val image = paragraph.items.single() as ReaderMeasuredInlineItem.Image
-        assertEquals(80f, image.widthPx, 0f)
-        assertEquals(40f, image.heightPx, 0f)
+        // 文字嵌入图只受当前行高约束(10px 字号),不允许放大到铺满可视文字区。
+        assertEquals(20f, image.widthPx, 0f)
+        assertEquals(10f, image.heightPx, 0f)
         assertEquals("run()", image.action)
         assertEquals("run()", image.action)
     }
     }
 
 
     @Test
     @Test
-    fun requestedWidthIsPreservedForInlineImageGeometry() = runBlocking {
+    fun requestedFractionAppliesWithinLineCap() = runBlocking {
         val result = measure(ReaderImageOptions(requestedWidthFraction = .5f))
         val result = measure(ReaderImageOptions(requestedWidthFraction = .5f))
         val paragraph = result.blocks.single() as ReaderMeasuredBlock.InlineParagraph
         val paragraph = result.blocks.single() as ReaderMeasuredBlock.InlineParagraph
         val image = paragraph.items.single() as ReaderMeasuredInlineItem.Image
         val image = paragraph.items.single() as ReaderMeasuredInlineItem.Image
-        assertEquals(50f, image.widthPx, 0f)
-        assertEquals(25f, image.heightPx, 0f)
+        assertEquals(20f, image.widthPx, 0f)
+        assertEquals(10f, image.heightPx, 0f)
+    }
+
+    @Test
+    fun perImageFullStyleOverridesGlobalInlineMode() = runBlocking {
+        val result = measure(
+            ReaderImageOptions(ReaderImageLayoutMode.FULL_WIDTH, action = "run()"),
+            baseStyle.copy(imageLayoutMode = ReaderImageLayoutMode.INLINE),
+            ReaderImageDimensions(20f, 10f),
+        )
+        val block = result.blocks.single() as ReaderMeasuredBlock.Image
+        assertEquals(ReaderImageScaleMode.FIT_WIDTH, block.scaleMode)
+        assertEquals("run()", block.action)
+        val image = ReaderPaginator.paginateBlocks(result.blocks, config)
+            .single().elements.single() as ReaderElement.Image
+        assertEquals(100f, image.bounds.width, 0f)
+        assertEquals(50f, image.bounds.height, 0f)
+        assertEquals("run()", image.action)
     }
     }
 
 
     @Test fun rightAlignmentAndSinglePageOverridesReachPaginator() = runBlocking {
     @Test fun rightAlignmentAndSinglePageOverridesReachPaginator() = runBlocking {

+ 9 - 8
app/src/test/java/io/legado/app/feature/reader/legacy/LegacyReaderImageOptionsResolverTest.kt

@@ -1,27 +1,28 @@
 package io.legado.app.feature.reader.legacy
 package io.legado.app.feature.reader.legacy
 
 
 import io.legado.app.feature.reader.core.layout.ReaderImageLayoutMode
 import io.legado.app.feature.reader.core.layout.ReaderImageLayoutMode
+import io.legado.app.feature.reader.core.layout.ReaderTextAlignment
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNull
 import org.junit.Assert.assertNull
 import org.junit.Test
 import org.junit.Test
 
 
 class LegacyReaderImageOptionsResolverTest {
 class LegacyReaderImageOptionsResolverTest {
     @Test
     @Test
-    fun sourceTextStyleIsTheOnlyLayoutOverride() {
+    fun parsesTypedImageUrlOptions() {
         val options = LegacyReaderImageOptionsResolver.resolve(
         val options = LegacyReaderImageOptionsResolver.resolve(
-            "https://example/image, {\"style\":\"text\",\"width\":\"37.5%\",\"click\":\"open(\\\"x\\\")\"}",
+            "https://example/image, {\"style\":\"right\",\"width\":\"37.5%\",\"click\":\"open(\\\"x\\\")\"}",
         )!!
         )!!
-        assertEquals(ReaderImageLayoutMode.INLINE, options.layoutMode)
-        assertNull(options.requestedWidthFraction)
-        assertNull(options.horizontalAlignment)
+        assertEquals(ReaderImageLayoutMode.STANDALONE, options.layoutMode)
+        assertEquals(ReaderTextAlignment.END, options.horizontalAlignment)
+        assertEquals(.375f, options.requestedWidthFraction!!, 0f)
         assertEquals("open(\"x\")", options.action)
         assertEquals("open(\"x\")", options.action)
     }
     }
 
 
     @Test
     @Test
-    fun nonTextSourceStyleDoesNotOverrideReaderImageStyle() {
+    fun parsesAbsoluteWidthAndKnownModesCaseInsensitively() {
         val options = LegacyReaderImageOptionsResolver.resolve("x,{\"style\":\"full\",\"width\":\"123\"}")!!
         val options = LegacyReaderImageOptionsResolver.resolve("x,{\"style\":\"full\",\"width\":\"123\"}")!!
-        assertNull(options.layoutMode)
-        assertNull(options.requestedWidthPx)
+        assertEquals(ReaderImageLayoutMode.FULL_WIDTH, options.layoutMode)
+        assertEquals(123f, options.requestedWidthPx!!, 0f)
     }
     }
 
 
     @Test fun malformedOrAbsentOptionsDoNotInventOverrides() {
     @Test fun malformedOrAbsentOptionsDoNotInventOverrides() {