Epub阅读器0.0.1
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
//
|
||||
// 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
|
||||
Reference in New Issue
Block a user