ReadViewSDK/.planning/phases/08-pagination-quality-cache-performance/08-RESEARCH.md
shen 26afb9f7dc docs(08): research image display fix for pagination quality
Research Phase 8 plan 08-02: image display fix in the native text
rendering path. Documents the unified max-size approach (1080x1920),
footnote width-only sizing, and three entry points that need changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 18:27:08 +08:00

28 KiB

Phase 8: 图片显示修复 - Research

Researched: 2026-05-23 Domain: DTCoreText image attachment sizing, WXRead alignment Confidence: HIGH

Summary

Phase 8 plan 08-02 focuses on fixing image display in the native text rendering path (DTCoreText). Three image types have sizing issues: (1) qrbodyPic images can overflow page height because no max size constraint is applied, (2) cover images use screen-size baselines but lack a unified ceiling, (3) footnote images have inconsistent height calculations across three separate code paths.

The core fix is to add a unified maximum size constraint (CGSize(width: 1080, height: 1920)) to ALL image attachments in prepareHTMLElementForReaderRendering, applied BEFORE any type-specific handling. This directly mirrors WXRead's _WRPostProcessElementTree approach. The fix is surgical: one new code block at the top of the existing function, plus removal of redundant height logic in two other locations.

Primary recommendation: Add a unified max-size block at the top of prepareHTMLElementForReaderRendering that constrains all images to 1080x1920 via aspect-ratio-preserving scaling. Then simplify footnote handling to width-only (no explicit height), matching WXRead's replace.css.

<user_constraints>

User Constraints (from CONTEXT.md)

Locked Decisions

  • D-01: Unified max image size CGSizeMake(1080, 1920) for all image attachments
  • D-02: Scale proportionally when exceeding max width (matching WXRead _WRPostProcessElementTree)
  • D-03: Implementation location: prepareHTMLElementForReaderRendering, BEFORE existing footnote/cover special handling
  • D-04: Cover images keep screen-size base (UIScreen.main.bounds.insetBy(dx: 20, dy: 28)), constrained by unified max
  • D-05: Keep configureCoverIfNeeded UIImageView path unchanged
  • D-06: Cover images still processed via cover branch in prepareHTMLElementForReaderRendering, but must not exceed unified max
  • D-07: Footnote images: only set width:1em, no height (align with WXRead replace.css)
  • D-08: Remove footnote height calculation in prepareHTMLElementForReaderRendering (pointSize * 0.54)
  • D-09: Remove height override in normalizeInlineAttachments (pointSize * 0.14)
  • D-10: Keep CSS width:1em in normalizeAttachmentHTMLMarkers, remove height:1em
  • D-11: No special handling for qrbodyPic — unified max size implicitly fixes overflow
  • D-12: Keep existing CSS rules for qrbodyPic unchanged

Claude's Discretion

  • Whether to auto-add bodyPic class to all images (like WXRead does)
  • Whether wr-vertical-center-style semantic marker is needed (Phase 7 already handles this)
  • Specific implementation details of the unified max-size block

Deferred Ideas (OUT OF SCOPE)

  • Image caching mechanism (belongs to plan 08-01)
  • wr-vertical-center-style full implementation (Phase 7 already completed)
  • Image tap-to-zoom feature (new capability, not in scope) </user_constraints>

<phase_requirements>

Phase Requirements

ID Description Research Support
QUAL-01 Cache layout frame / pagination results Not in scope for 08-02 (belongs to 08-01)
QUAL-02 Improve pagination quality for complex illustrated chapters — reduce bad page breaks, orphan/widow lines, abnormal whitespace around images Image sizing fix directly addresses image-related pagination quality
QUAL-03 Image/attachment sizing strategy, dark mode adaptation, page background — must have verifiable processing rules, not rely on DTCoreText defaults Unified max size + explicit CSS rules provide verifiable rules
QUAL-04 Pagination improvements must not significantly degrade first-screen time, re-pagination time, or interaction smoothness Image sizing is a lightweight operation in willFlushCallback, negligible performance impact
</phase_requirements>

Architectural Responsibility Map

Capability Primary Tier Secondary Tier Rationale
Image max-size constraint Swift willFlushCallback (renderer) CSS replaceCSS Swift handles absolute pixel ceiling; CSS handles responsive width
Footnote image sizing CSS (width:1em in HTML preprocessing) Swift (vertical alignment) CSS controls width; Swift only sets alignment
Cover image sizing Swift (prepareHTMLElementForReaderRendering) configureCoverIfNeeded (view layer) Swift constrains in attributed string; view layer only extracts for UIImageView display
Page-break control around images Layouter (pagination) CSS (page-break-inside: avoid) Layouter decides actual breaks; CSS provides hints

Standard Stack

Core

Library Version Purpose Why Standard
DTCoreText (CocoaPod, project-local) HTML/CSS to NSAttributedString Existing project dependency; provides DTTextAttachment, willFlushCallback
UIKit iOS SDK UIScreen.main.bounds for cover sizing Platform standard

Supporting

Library Version Purpose When to Use
Foundation iOS SDK NSRegularExpression for HTML preprocessing Already used throughout RDEPUBTextRendererSupport

Alternatives Considered

Instead of Could Use Tradeoff
Swift-side max size in willFlushCallback CSS max-height in replaceCSS CSS approach is simpler but DTCoreText CSS parser may not support max-height reliably for attachments; Swift is authoritative
Removing DTMaxImageSize from builder options Keeping both layers Keeping both is safer — DTMaxImageSize is the first line of defense at attachment creation time; willFlushCallback is the second pass

Package Legitimacy Audit

No new packages are installed in this phase. All changes are to existing Swift source files.

Package Registry Age Downloads Source Repo slopcheck Disposition
(none) No new packages

Architecture Patterns

Current Image Processing Pipeline

HTML input
  |
  v
normalizeAttachmentHTMLMarkers()     <-- HTML regex: adds inline styles to <img> tags
  |                                      (footnote: width:1em;height:1em)
  |                                      (cover: adds rd-front-cover-image class)
  v
DTHTMLAttributedStringBuilder        <-- Creates DTTextAttachment with DTMaxImageSize
  |                                      (currently screenBounds.size, ~353x757)
  |                                      DTCoreText sets displaySize = min(original, max)
  v
willFlushCallback                    <-- prepareHTMLElementForReaderRendering()
  |                                      Currently: footnote → height calc, cover → screen scale
  |                                      MISSING: unified max-size for ALL images
  v
normalizeReadingAttributes()         <-- normalizeAttachmentDisplayIfNeeded()
  |                                      Currently: footnote → height override (0.14*pt)
  |                                      cover → maxWidth override
  v
configureCoverIfNeeded()             <-- View layer: extracts cover image for UIImageView
  |
  v
normalizeInlineAttachments()         <-- View layer: footnote → height override (0.14*pt)
HTML input
  |
  v
normalizeAttachmentHTMLMarkers()     <-- CHANGED: footnote <img> only gets width:1em (no height)
  v
DTHTMLAttributedStringBuilder        <-- CHANGED: DTMaxImageSize → CGSizeMake(1080, 1920)
  v
willFlushCallback                    <-- prepareHTMLElementForReaderRendering()
  |                                      NEW: unified max-size block for ALL images first
  |                                      THEN: footnote → only verticalAlignment + inline
  |                                      THEN: cover → screen-size scale (capped by max)
  v
normalizeReadingAttributes()         <-- normalizeAttachmentDisplayIfNeeded()
  |                                      CHANGED: footnote path removed (no height override)
  |                                      cover path can remain as fallback
  v
configureCoverIfNeeded()             <-- UNCHANGED
  v
normalizeInlineAttachments()         <-- CHANGED: footnote height override removed

Pattern 1: Unified Max-Size Block in prepareHTMLElementForReaderRendering

What: A new code block at the top of the function, before any type-specific handling, that constrains ALL image attachments to 1080x1920.

When to use: Every image attachment that enters willFlushCallback.

Example:

// Source: WXRead WREpubTypesetter.m §672-701 (_WRPostProcessElementTree)
// Placed BEFORE existing footnote/cover handling in prepareHTMLElementForReaderRendering

static func prepareHTMLElementForReaderRendering(
    _ element: DTHTMLElement,
    style: RDEPUBTextRenderStyle
) {
    guard let attachment = element.textAttachment else { return }

    // --- NEW: Unified max image size (aligns with WXRead _WRPostProcessElementTree) ---
    let maxImageSize = CGSize(width: 1080, height: 1920)
    let originalSize = attachment.originalSize
    if originalSize.width > maxImageSize.width || originalSize.height > maxImageSize.height {
        let scale = min(maxImageSize.width / max(originalSize.width, 1),
                        maxImageSize.height / max(originalSize.height, 1))
        attachment.displaySize = CGSize(
            width: round(originalSize.width * scale),
            height: round(originalSize.height * scale)
        )
    }

    // ... existing footnote/cover handling follows ...
}

Pattern 2: Footnote Width-Only Sizing

What: Footnote images get width:1em in HTML preprocessing, with no explicit height. DTCoreText calculates height from aspect ratio.

When to use: In normalizeAttachmentHTMLMarkers for qqreader-footnote images.

Example:

// Source: WXRead replace.css §100-103 (.qqreader-footnote { width: 1em; })
// Changed: removed "height:1em" from styleFragments

normalized = replaceMatches(
    using: footnoteRegex,
    in: normalized
) { tag in
    mergeHTMLAttributes(
        into: tag,
        requiredClass: nil,
        styleFragments: [
            "width:1em",
            // "height:1em"  <-- REMOVED per D-10
            "vertical-align:middle",
            "display:inline-block"
        ]
    )
}

Anti-Patterns to Avoid

  • Separate max-size blocks for each image type: WXRead applies one unified block to ALL images. Do not duplicate the scaling logic for qrbodyPic, cover, and footnote separately.
  • Removing DTMaxImageSize from builder options: The DTCoreText-level max size is the first line of defense (applied during attachment creation). The willFlushCallback max size is the second pass. Keep both.
  • Setting explicit height on footnote images: WXRead only sets width:1em on .qqreader-footnote. DTCoreText's setDisplaySize:withMaxDisplaySize: handles width-only cases by calculating height from aspect ratio (line 231-236 of DTTextAttachment.m).

Don't Hand-Roll

Problem Don't Build Use Instead Why
Aspect ratio calculation Custom scaling math DTCoreText's DTCGSizeThatFitsKeepingAspectRatio or the existing min(w/maxW, h/maxH) pattern Consistent with DTCoreText internals
Image type detection New classification system Existing CSS class + filename detection (lowercasedClasses, lowercasedPath) Already works, battle-tested in Phase 7

Common Pitfalls

Pitfall 1: DTMaxImageSize vs willFlushCallback Ordering

What goes wrong: If the max-size constraint is only in willFlushCallback, DTCoreText's initial setDisplaySize:withMaxDisplaySize: uses the old (screen-size) max. The willFlushCallback then overrides it. This works but the intermediate state could confuse debugging. Why it happens: DTMaxImageSize is consumed at attachment creation time; willFlushCallback runs after. How to avoid: Update BOTH: change DTMaxImageSize in dtOptions() to CGSize(width: 1080, height: 1920) AND add the max-size block in prepareHTMLElementForReaderRendering. The willFlushCallback block is the authoritative constraint; DTMaxImageSize is the first-pass optimization. Warning signs: If only one is updated, images may briefly appear at wrong size before being corrected.

Pitfall 2: Footnote Aspect Ratio Without Explicit Height

What goes wrong: After removing height:1em from HTML preprocessing AND removing the height calculation in prepareHTMLElementForReaderRendering, footnote images might render with incorrect aspect ratio if originalSize is not available. Why it happens: Some images in EPUBs may not have width/height attributes, so originalSize could be CGSizeZero. How to avoid: DTCoreText's setDisplaySize:withMaxDisplaySize: (line 218-237 of DTTextAttachment.m) handles the case where originalSize has width but not height (and vice versa) by calculating from aspect ratio. If originalSize is completely zero, the image falls back to the DTMaxImageSize constraint. This should be safe, but verify with the demo sample. Warning signs: Footnote images appearing at 0x0 or unreasonably large sizes.

Pitfall 3: Cover Image Double-Scaling

What goes wrong: The unified max-size block scales the image, then the cover-specific block scales it again, resulting in an image that's too small. Why it happens: Two sequential scaling operations compound. How to avoid: The cover-specific block in prepareHTMLElementForReaderRendering already uses UIScreen.main.bounds.insetBy(dx: 20, dy: 28) as its max, which is approximately 353x757 points. This is SMALLER than 1080x1920. So the unified max-size block will be a no-op for cover images (they're already within bounds). The cover block then applies its own tighter constraint. This is correct behavior — no double-scaling occurs. Warning signs: Cover images appearing smaller than expected.

Pitfall 4: normalizeAttachmentDisplayIfNeeded Still Overrides

What goes wrong: After fixing prepareHTMLElementForReaderRendering, the normalizeAttachmentDisplayIfNeeded function (called from normalizeReadingAttributes) still overrides footnote and cover sizes. Why it happens: This function runs AFTER willFlushCallback in the pipeline. How to avoid: Per D-08/D-09, remove the footnote height override in this function too. The cover path in this function can remain as a fallback (it uses maxWidth = max(UIScreen.main.bounds.width - 48, font.lineHeight * 8) which is similar to the cover block in prepareHTMLElementForReaderRendering). Warning signs: If footnote images still appear at wrong size after the fix, this function is the likely culprit.

Code Examples

WXRead Reference: _WRPostProcessElementTree (Objective-C)

// Source: Doc/WXRead/decompiled/WREpubTypesetter.m §672-701
if (element.textAttachment) {
    DTTextAttachment *attachment = element.textAttachment;

    CGSize maxSize = CGSizeMake(1080, 1920);
    NSNumber *maxW = options[@"maxImageWidth"];
    if (maxW) maxSize.width = maxW.floatValue;

    if (attachment.originalSize.width > maxSize.width) {
        CGFloat scale = maxSize.width / attachment.originalSize.width;
        attachment.displaySize = CGSizeMake(
            attachment.originalSize.width * scale,
            attachment.originalSize.height * scale
        );
    }

    element.textAttachment.attributes = @{
        WREpubTypesetterVerticalCenterStyleAttribute: @(2)
    };
    [element addClass:@"bodyPic"];
}

Key observations:

  1. Max size is applied to ALL images, unconditionally (no class check)
  2. Only width is checked against max (originalSize.width > maxSize.width), not height
  3. bodyPic class is auto-added to every image
  4. wr-vertical-center-style: 2 is set on every image

DTCoreText's setDisplaySize:withMaxDisplaySize: (Objective-C)

// Source: ReadViewDemo/Pods/DTCoreText/Core/Source/DTTextAttachment.m §216-245
- (void)setDisplaySize:(CGSize)displaySize withMaxDisplaySize:(CGSize)maxDisplaySize {
    if (_originalSize.width!=0 && _originalSize.height!=0) {
        if (displaySize.width==0 && displaySize.height==0) {
            displaySize = _originalSize;
        } else if (displaySize.width==0 && displaySize.height!=0) {
            CGFloat factor = _originalSize.height / displaySize.height;
            displaySize.width = round(_originalSize.width / factor);
        } else if (displaySize.width!=0 && displaySize.height==0) {
            CGFloat factor = _originalSize.width / displaySize.width;
            displaySize.height = round(_originalSize.height / factor);
        }
    }
    if (maxDisplaySize.width>0 && maxDisplaySize.height>0) {
        if (maxDisplaySize.width < displaySize.width || maxDisplaySize.height < displaySize.height) {
            displaySize = DTCGSizeThatFitsKeepingAspectRatio(displaySize, maxDisplaySize);
        }
    }
}

Key observation for footnote width-only approach: When displaySize.width != 0 and displaySize.height == 0, DTCoreText calculates height from the aspect ratio. This means setting only width:1em in CSS (which translates to a display width) will automatically produce correct height. This confirms D-07 is safe.

// Source: Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift §217-262
static func prepareHTMLElementForReaderRendering(
    _ element: DTHTMLElement,
    style: RDEPUBTextRenderStyle
) {
    guard let attachment = element.textAttachment else { return }

    // --- NEW: Unified max image size (WXRead alignment) ---
    let maxImageSize = CGSize(width: 1080, height: 1920)
    let originalSize = attachment.originalSize
    if originalSize.width > 0, originalSize.height > 0,
       (originalSize.width > maxImageSize.width || originalSize.height > maxImageSize.height) {
        let scale = min(maxImageSize.width / originalSize.width,
                        maxImageSize.height / originalSize.height)
        attachment.displaySize = CGSize(
            width: round(originalSize.width * scale),
            height: round(originalSize.height * scale)
        )
    }

    let lowercasedClasses = (((attachment.attributes["class"] as? String)
        ?? (element.attributes["class"] as? String) ?? "")).lowercased()
    let lowercasedPath = attachment.contentURL?.lastPathComponent.lowercased()
        ?? ((attachment.attributes["src"] as? String)
        ?? (element.attributes["src"] as? String) ?? "").lowercased()
    let pointSize = max(element.fontDescriptor.pointSize, style.font.pointSize)

    // Footnote: width-only sizing, no explicit height (WXRead alignment)
    if lowercasedClasses.contains("qqreader-footnote") || lowercasedPath == "note.png" {
        attachment.verticalAlignment = .center
        element.displayStyle = .inline
        // Note: NO height calculation here. Width is set via HTML preprocessing
        // (normalizeAttachmentHTMLMarkers adds width:1em). DTCoreText calculates
        // height from aspect ratio automatically.
        if !didLogFootnoteAttachment {
            didLogFootnoteAttachment = true
            print("[EPUB][Attachment] footnote classes=\(lowercasedClasses) path=\(lowercasedPath) original=\(string(from: originalSize)) display=\(string(from: attachment.displaySize)) font=\(pointSize)")
        }
        return
    }

    // Cover: screen-size base, already constrained by unified max above
    if lowercasedClasses.contains("rd-front-cover-image") || lowercasedPath == "cover.jpg" {
        let maxSize = UIScreen.main.bounds.insetBy(dx: 20, dy: 28).size
        let currentOriginalSize = attachment.originalSize
        if currentOriginalSize.width > 0, currentOriginalSize.height > 0 {
            let scale = min(maxSize.width / currentOriginalSize.width, maxSize.height / currentOriginalSize.height)
            if scale < 1.0 {  // Only re-scale if larger than screen
                attachment.displaySize = CGSize(
                    width: round(currentOriginalSize.width * scale),
                    height: round(currentOriginalSize.height * scale)
                )
            }
        } else {
            attachment.displaySize = CGSize(width: round(maxSize.width), height: round(maxSize.height))
        }
        attachment.verticalAlignment = .baseline
        element.displayStyle = .block
        if !didLogCoverAttachment {
            didLogCoverAttachment = true
            print("[EPUB][Attachment] cover classes=\(lowercasedClasses) path=\(lowercasedPath) original=\(string(from: currentOriginalSize)) display=\(string(from: attachment.displaySize))")
        }
    }
}

State of the Art

Old Approach Current Approach When Changed Impact
DTMaxImageSize = screen bounds DTMaxImageSize = 1080x1920 This phase All images get correct ceiling at creation time
Footnote: explicit height calc (0.54pt in renderer, 0.14pt in view) Footnote: width-only from CSS, height auto-calculated This phase Consistent with WXRead, simpler, no conflicting overrides
Cover: screen-size only, no unified max Cover: screen-size + unified max ceiling This phase Rare edge case: very large cover images get proper constraint
qrbodyPic: no max constraint qrbodyPic: constrained by unified max This phase Fixes height overflow for image-heavy chapters

Deprecated/outdated:

  • pointSize * 0.54 footnote height calculation in prepareHTMLElementForReaderRendering — replaced by width-only approach
  • pointSize * 0.14 footnote height override in normalizeInlineAttachments — removed entirely
  • pointSize * 0.14 footnote height override in normalizeAttachmentDisplayIfNeeded — removed entirely

Assumptions Log

# Claim Section Risk if Wrong
A1 DTCoreText correctly calculates height from aspect ratio when only width is set on an attachment (verified via DTTextAttachment.m source code reading) Pattern 2: Footnote Width-Only LOW — code at line 231-236 explicitly handles this case
A2 WXRead's _WRPostProcessElementTree checks only width, not height, against maxSize (verified via decompiled source) WXRead Reference LOW — source code at line 680 confirms originalSize.width > maxSize.width
A3 The normalizeAttachmentDisplayIfNeeded function at line 467-505 of RDEPUBTextRendererSupport.swift needs its footnote height override removed (per D-08/D-09) Pitfall 4 MEDIUM — if not removed, it will override the willFlushCallback changes

Open Questions

  1. Should we also update DTMaxImageSize in dtOptions() from screen bounds to 1080x1920?

    • What we know: dtOptions() currently uses UIScreen.main.bounds.insetBy(dx: 20, dy: 28).size (~353x757)
    • What's unclear: Whether changing this affects non-image attachments or has side effects
    • Recommendation: Yes, update to CGSize(width: 1080, height: 1920). This is the first-pass constraint at attachment creation time. The willFlushCallback block is the authoritative second pass. Aligning both to the same max size eliminates confusion.
  2. Should we auto-add bodyPic class to all images like WXRead does?

    • What we know: WXRead adds bodyPic class to every image in _WRPostProcessElementTree
    • What's unclear: Whether ReadViewSDK's CSS rules depend on bodyPic being present for proper centering
    • Recommendation: Not required for the immediate fix. The replaceCSS already handles img, svg, video, canvas with max-width: 100%; height: auto, and specific selectors like .bodyPic img for centering. Adding the class would be a nice-to-have for full WXRead alignment but is not blocking.
  3. Should normalizeAttachmentDisplayIfNeeded (line 467-505) be updated or removed entirely?

    • What we know: This function runs in normalizeReadingAttributes (post-willFlushCallback) and overrides footnote/cover sizes
    • What's unclear: Whether removing the footnote path is sufficient, or whether the cover path should also be simplified
    • Recommendation: Remove the footnote height override path (D-08). The cover path can remain as a fallback since it produces similar results to the prepareHTMLElementForReaderRendering cover block.

Environment Availability

No external dependencies required — all changes are to existing Swift source files.

Dependency Required By Available Version Fallback
Xcode / Swift compiler Build & run (project toolchain)
DTCoreText (CocoaPod) Image attachment API (project-local)
Demo EPUB sample Visual verification 宝山辽墓材料与释读

Missing dependencies with no fallback: None Missing dependencies with fallback: None

Validation Architecture

Test Framework

Property Value
Framework None (no unit test target for EPUBTextRendering)
Config file none
Quick run command xcodebuild build -project ReadViewDemo/ReadViewDemo.xcodeproj -scheme ReadViewDemo -destination 'platform=iOS Simulator,name=iPhone 16'
Full suite command Same as quick run (build-only verification)

Phase Requirements → Test Map

Req ID Behavior Test Type Automated Command File Exists?
QUAL-02 Images don't overflow page manual visual Build + run demo + navigate to 图文章节 N/A
QUAL-03 Image sizing rules are verifiable code review Verify max-size constant is 1080x1920 N/A
QUAL-04 No performance degradation manual timing Compare pagination time before/after N/A

Sampling Rate

  • Per task commit: xcodebuild build (compilation check)
  • Per wave merge: Build + run demo + visual inspection of 图文章节
  • Phase gate: Visual verification that all three image types display correctly

Wave 0 Gaps

  • No automated image size assertion tests — manual visual verification required
  • No snapshot/regression test infrastructure for image rendering

Sources

Primary (HIGH confidence)

  • Doc/WXRead/decompiled/WREpubTypesetter.m §672-701 — _WRPostProcessElementTree max size logic
  • Doc/WXRead/decompiled/WRCoreTextLayoutFrame.m §615-635 — cover image drawing
  • Doc/WXRead/resources/css/replace.css §91-103 — .bodyPic, .qrbodyPic, .qqreader-footnote CSS
  • Sources/RDReaderView/EPUBTextRendering/RDEPUBTextRendererSupport.swift §217-262, §302-343, §345-404, §467-505 — current implementation
  • ReadViewDemo/Pods/DTCoreText/Core/Source/DTTextAttachment.m §216-245 — setDisplaySize:withMaxDisplaySize: implementation
  • ReadViewDemo/Pods/DTCoreText/Core/Source/DTTextAttachment.horiginalSize, displaySize, verticalAlignment API

Secondary (MEDIUM confidence)

  • ReadViewDemo/ReadViewDemo/book/宝山辽墓材料与释读_副本/OEBPS/Text/Chapter_5.xhtmlqrbodyPic HTML structure
  • ReadViewDemo/ReadViewDemo/book/宝山辽墓材料与释读_副本/OEBPS/Text/cover.xhtml — cover HTML structure
  • ReadViewDemo/ReadViewDemo/book/宝山辽墓材料与释读_副本/OEBPS/Styles/stylesheets.css — EPUB's own CSS rules

Metadata

Confidence breakdown:

  • Standard stack: HIGH — DTCoreText is the existing project dependency, API is well-understood
  • Architecture: HIGH — pipeline is clearly documented, code paths are traced
  • Pitfalls: HIGH — all pitfalls identified from direct source code reading

Research date: 2026-05-23 Valid until: 2026-06-23 (stable — DTCoreText and project code are not changing rapidly)