// // WRCoreTextLayoutFrame.h // WeRead // // Reverse-engineered from binary analysis // Single page layout frame for WeRead's reading engine // #import #import #import @class WRBookCoverView; @class WRDiscover; NS_ASSUME_NONNULL_BEGIN #pragma mark - Layout Line /** * Represents a single line of laid-out text. * Wrapper around CTLine with additional metadata. */ @interface WRCoreTextLayoutLine : NSObject /** The CTLine this wraps */ @property (nonatomic, assign, readonly) CTLineRef ctLine; /** Origin point of this line in the frame */ @property (nonatomic, assign) CGPoint origin; /** The range of characters in this line */ @property (nonatomic, assign) NSRange stringRange; /** Ascent of the line */ @property (nonatomic, assign, readonly) CGFloat ascent; /** Desent of the line */ @property (nonatomic, assign, readonly) CGFloat descent; /** Leading of the line */ @property (nonatomic, assign, readonly) CGFloat leading; /** Total height of the line (ascent + descent + leading) */ @property (nonatomic, assign, readonly) CGFloat height; /** Width of the line */ @property (nonatomic, assign, readonly) CGFloat width; /** Whether this line is the last line in a paragraph */ @property (nonatomic, assign) BOOL isLastLineInParagraph; @end #pragma mark - WRCoreTextLayoutFrame /** * WRCoreTextLayoutFrame - Manages a single page of text layout. * * This class wraps CTFrame and provides high-level access to the * layout of a single page. It handles: * - Text rendering to CGContext * - Image placement within text * - avoidPageBreakInside CSS property * - Render height calculation * - Line-by-line access for hit testing and selection * * Architecture: * - Contains CTFrame internally * - Manages array of WRCoreTextLayoutLine objects * - Supports direct drawing to CGContext * - Handles image attachments inline * - Integrates with RACSubject for reactive updates */ @interface WRCoreTextLayoutFrame : NSObject #pragma mark - Core Layout Data /** The attributed string that was laid out */ @property (nonatomic, strong, nullable) NSAttributedString *attributedString; /** The range of the attributed string represented by this frame */ @property (nonatomic, assign) NSRange stringRange; /** Array of WRCoreTextLayoutLine objects for this frame */ @property (nonatomic, strong, readonly) NSArray *layoutLines; /** The bounding rectangle for this frame */ @property (nonatomic, assign) CGRect frame; /** The CTFrame reference (accessor for internal use) */ @property (nonatomic, assign, readonly) CTFrameRef ctFrame; #pragma mark - Layout Properties /** Whether to avoid page breaks inside certain elements */ @property (nonatomic, assign) BOOL avoidPageBreakInside; /** The rendered content height (may differ from frame height) */ @property (nonatomic, assign, readonly) CGFloat renderedContentHeight; /** Padding around the content area */ @property (nonatomic, assign) UIEdgeInsets contentInsets; /** Number of columns in this frame */ @property (nonatomic, assign) NSUInteger numberOfColumns; /** Gap between columns */ @property (nonatomic, assign) CGFloat columnGap; #pragma mark - Content Arrays /** Array of attachment objects (images, etc.) */ @property (nonatomic, strong, nullable) NSArray *attachments; /** Array of strikethrough ranges */ @property (nonatomic, strong, nullable) NSArray *strikethroughRanges; /** Array of underline ranges */ @property (nonatomic, strong, nullable) NSArray *underlineRanges; /** Array of highlight ranges */ @property (nonatomic, strong, nullable) NSArray *highlightRanges; /** Set of selected line indices */ @property (nonatomic, strong, nullable) NSMutableSet *selectedLineIndices; /** Current selection string */ @property (nonatomic, copy, nullable) NSString *selectionString; /** Array of link ranges for tap handling */ @property (nonatomic, strong, nullable) NSArray *linkRanges; /** Search result string */ @property (nonatomic, copy, nullable) NSString *searchResultString; /** Array of search result ranges */ @property (nonatomic, strong, nullable) NSArray *searchResultRanges; #pragma mark - Reactive Components /** Subject for layout change notifications */ @property (nonatomic, strong, nullable) RACSubject *layoutChangeSubject; #pragma mark - UI Elements /** Long press gesture recognizer for text selection */ @property (nonatomic, strong, nullable) UILongPressGestureRecognizer *longPressRecognizer; /** Book cover view (for cover pages) */ @property (nonatomic, strong, nullable) WRBookCoverView *bookCoverView; /** Layer for custom drawing */ @property (nonatomic, strong, nullable) CALayer *customDrawingLayer; /** Discover view (for social features) */ @property (nonatomic, strong, nullable) WRDiscover *discoverView; /** Additional layout data */ @property (nonatomic, strong, nullable) NSArray *additionalLayoutData; /** Frame identifier */ @property (nonatomic, copy, nullable) NSString *frameId; /** Content identifier */ @property (nonatomic, copy, nullable) NSString *contentId; /** Mutable dictionary for custom attributes */ @property (nonatomic, strong, nullable) NSMutableDictionary *customAttributes; /** Mutable array for tracking visible elements */ @property (nonatomic, strong, nullable) NSMutableArray *visibleElements; /** Mutable array for tracking accessibility elements */ @property (nonatomic, strong, nullable) NSMutableArray *accessibilityElements; /** Mutable dictionary for caching computed values */ @property (nonatomic, strong, nullable) NSMutableDictionary *computedValueCache; #pragma mark - Initialization /** * Initialize with a CTFrame and string range. * * @param ctFrame The CoreText frame * @param range The string range * @return Initialized layout frame */ - (instancetype)initWithCTFrame:(CTFrameRef)ctFrame range:(NSRange)range; /** * Set the CTFrame and range (used by WRCoreTextLayouter). */ - (void)setCTFrame:(CTFrameRef)ctFrame range:(NSRange)range; #pragma mark - Line Access /** * Returns the array of layout lines. * This triggers line extraction from the CTFrame if not already done. * * @return Array of WRCoreTextLayoutLine objects */ - (NSArray *)lines; /** * Returns the number of lines in this frame. */ - (NSUInteger)lineCount; /** * Returns the layout line at the specified index. */ - (nullable WRCoreTextLayoutLine *)lineAtIndex:(NSUInteger)index; #pragma mark - Height Calculation /** * Returns the rendered content height. * * This calculates the actual height of the rendered content, * which may be less than the frame height if there's unused space. * * @return The height of the rendered content in points */ - (CGFloat)getRenderHeight; /** * Returns the height of the last line's descent. * Used for precise baseline alignment. */ - (CGFloat)lastLineDescent; #pragma mark - Drawing /** * Draw the layout frame content into a CGContext. * * This is the primary rendering method. It draws: * 1. Text content using CTFrameDraw * 2. Image attachments at their calculated positions * 3. Decorative elements (strikethrough, underline, etc.) * * @param context The CGContext to draw into * @param image Optional image to draw (for cover pages) * @param size The size of the drawing area * @param rect The rectangle to draw within * @param position The position offset for the content */ - (void)drawInContext:(CGContextRef)context image:(nullable UIImage *)image size:(CGSize)size inRect:(CGRect)rect position:(CGPoint)position; /** * Draw only the text content (no images). */ - (void)drawTextInContext:(CGContextRef)context inRect:(CGRect)rect; /** * Draw image attachments. */ - (void)drawAttachmentsInContext:(CGContextRef)context inRect:(CGRect)rect; #pragma mark - Page Break Avoidance /** * Removes the last lines if needed to avoid page breaks inside certain elements. * * This implements the CSS 'avoid-page-break-inside' property. When an element * (like a table, code block, or list) cannot fit entirely on the current page, * this method removes lines from the end until the element can fit. * * @return YES if lines were removed, NO otherwise */ - (BOOL)avoidPageBreakInsideByRemovingLastLinesIfNeeded; /** * Check if a range of text has the avoid-page-break-inside property. */ - (BOOL)shouldAvoidPageBreakInRange:(NSRange)range; #pragma mark - Hit Testing /** * Returns the character index at a given point. * * @param point The point to test (in the frame's coordinate system) * @return The character index, or NSNotFound if no character at that point */ - (NSUInteger)characterIndexAtPoint:(CGPoint)point; /** * Returns the line index at a given point. * * @param point The point to test * @return The line index, or NSNotFound */ - (NSUInteger)lineIndexAtPoint:(CGPoint)point; /** * Returns the rect for a character at the given index. */ - (CGRect)rectForCharacterAtIndex:(NSUInteger)index; #pragma mark - Selection /** * Returns the selected text string. */ - (nullable NSString *)selectedText; /** * Returns the ranges of selected text. */ - (NSArray *)selectedRanges; /** * Selects text in the given range. */ - (void)selectTextInRange:(NSRange)range; /** * Clears the current selection. */ - (void)clearSelection; #pragma mark - Search /** * Highlights search results within this frame. * * @param searchString The string to highlight * @return The number of matches found */ - (NSUInteger)highlightSearchResults:(NSString *)searchString; /** * Clears all search result highlights. */ - (void)clearSearchHighlights; @end NS_ASSUME_NONNULL_END