ReadViewSDK/Doc/WXRead/decompiled/DTCoreTextLayouter.m
2026-05-21 19:40:51 +08:00

281 lines
6.8 KiB
Objective-C

//
// DTCoreTextLayouter.m
// WeRead
//
// Reverse-engineered from binary analysis
// Based on DTCoreText open-source library, customized by WeRead
//
// This file contains pseudo-code reconstruction of the DTCoreTextLayouter
// class based on ivar analysis, method signatures, and the open-source DTCoreText library.
//
#import "DTCoreTextLayouter.h"
#import "DTCoreTextLayoutFrame.h"
#import <CoreText/CoreText.h>
#pragma mark - Private Interface
@interface DTCoreTextLayouter () {
// CoreText typesetter - the core text processing engine
CTTypesetterRef _typesetter;
// Track whether the typesetter is valid
BOOL _typesetterValid;
// Internal lock for thread safety
NSLock *_lock;
}
@end
#pragma mark - DTCoreTextLayouter Implementation
@implementation DTCoreTextLayouter
#pragma mark - Lifecycle
- (instancetype)init {
self = [super init];
if (self) {
_layoutFrameCache = [[NSCache alloc] init];
_layoutFrameCache.countLimit = 20; // Cache up to 20 layout frames
_layoutFrames = [NSMutableArray array];
_typesetterValid = NO;
_lock = [[NSLock alloc] init];
}
return self;
}
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString {
self = [self init];
if (self) {
_attributedString = attributedString;
}
return self;
}
- (void)dealloc {
if (_typesetter) {
CFRelease(_typesetter);
_typesetter = NULL;
}
}
#pragma mark - Property Accessors
- (void)setAttributedString:(NSAttributedString *)attributedString {
[_lock lock];
_attributedString = attributedString;
// Invalidate the typesetter when the string changes
_typesetterValid = NO;
// Clear the layout frames array
[_layoutFrames removeAllObjects];
[_lock unlock];
}
#pragma mark - Typesetter Management
/**
* Returns the internal CTTypesetter, creating it if necessary.
*
* CTTypesetter is the low-level CoreText object that performs glyph layout.
* It analyzes the attributed string and prepares it for line-by-line layout.
*
* @return The CTTypesetter reference
*/
- (CTTypesetterRef)typesetter {
[_lock lock];
if (!_typesetterValid || !_typesetter) {
// Release old typesetter if any
if (_typesetter) {
CFRelease(_typesetter);
_typesetter = NULL;
}
if (_attributedString) {
// Create new typesetter from the attributed string
_typesetter = CTTypesetterCreateWithAttributedString(
(__bridge CFAttributedStringRef)_attributedString
);
_typesetterValid = YES;
}
}
CTTypesetterRef result = _typesetter;
[_lock unlock];
return result;
}
/**
* Invalidates the current typesetter.
* Forces recreation on next access.
*/
- (void)invalidateTypesetter {
[_lock lock];
_typesetterValid = NO;
[_lock unlock];
}
#pragma mark - Layout Frame Creation
/**
* Creates a DTCoreTextLayoutFrame for a given string range.
*
* This method creates a layout frame by:
* 1. Getting the typesetter
* 2. Creating a CTFrame for the range within the given rect
* 3. Wrapping it in a DTCoreTextLayoutFrame
*
* @param range Range of the attributed string to lay out
* @param frame Bounding rectangle for the layout
* @return A new DTCoreTextLayoutFrame
*/
- (DTCoreTextLayoutFrame *)layoutFrameWithRange:(NSRange)range
frame:(CGRect)frame {
CTTypesetterRef typesetter = [self typesetter];
if (!typesetter || range.length == 0) {
return nil;
}
[_lock lock];
// Create a CGPath for the frame bounds
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, frame);
// Create CTFrame from the typesetter
CTFrameRef ctFrame = CTTypesetterCreateFrame(
typesetter,
CFRangeMake(range.location, range.length),
path,
NULL
);
CGPathRelease(path);
if (!ctFrame) {
[_lock unlock];
return nil;
}
// Create layout frame wrapper
DTCoreTextLayoutFrame *layoutFrame = [[DTCoreTextLayoutFrame alloc]
initWithAttributedString:_attributedString
range:range
ctFrame:ctFrame];
CFRelease(ctFrame);
[_layoutFrames addObject:layoutFrame];
[_lock unlock];
return layoutFrame;
}
/**
* Suggests a line break for the given start index and width.
*
* Uses CTTypesetterSuggestLineBreak to determine how many characters
* fit within the given width.
*
* @param startIndex The starting index in the string
* @param width The available width
* @return The suggested line break index
*/
- (NSUInteger)suggestLineBreakStartIndex:(NSUInteger)startIndex
width:(CGFloat)width {
CTTypesetterRef typesetter = [self typesetter];
if (!typesetter) {
return 0;
}
// CTTypesetterSuggestLineBreak returns the number of characters
// that fit in the given width starting from startIndex
CFIndex breakIndex = CTTypesetterSuggestLineBreak(
typesetter,
startIndex,
width
);
return (NSUInteger)breakIndex;
}
/**
* Suggests a fitting string length for pagination.
*
* This method simulates pagination to determine how much text
* fits within the given constraints.
*
* @param width The available width
* @param startIndex The starting index
* @return The suggested fitting length
*/
- (NSUInteger)stringIndexFittingLengthForWidth:(CGFloat)width
startIndex:(NSUInteger)startIndex {
CTTypesetterRef typesetter = [self typesetter];
if (!_attributedString || !typesetter) {
return 0;
}
NSUInteger totalLength = [_attributedString length];
NSUInteger currentIndex = startIndex;
NSUInteger totalFitted = 0;
// Simulate line-by-line layout to find total fitting length
while (currentIndex < totalLength) {
CFIndex lineBreak = CTTypesetterSuggestLineBreak(
typesetter,
currentIndex,
width
);
if (lineBreak <= 0) {
break;
}
totalFitted += lineBreak;
currentIndex += lineBreak;
}
return totalFitted;
}
#pragma mark - Frame Caching
/**
* Returns a cached layout frame for the given key.
*/
- (DTCoreTextLayoutFrame *)cachedLayoutFrameForKey:(NSString *)key {
return [_layoutFrameCache objectForKey:key];
}
/**
* Caches a layout frame with the given key.
*/
- (void)cacheLayoutFrame:(DTCoreTextLayoutFrame *)frame
forKey:(NSString *)key {
if (frame && key) {
[_layoutFrameCache setObject:frame forKey:key];
}
}
/**
* Clears the layout frame cache.
*/
- (void)clearLayoutFrameCache {
[_layoutFrameCache removeAllObjects];
}
@end