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

699 lines
17 KiB
Objective-C

//
// DTCoreTextGlyphRun.m
// WeRead
//
// Reverse-engineered from binary analysis
// Based on DTCoreText open-source library, customized by WeRead
//
// This file contains pseudo-code reconstruction of the DTCoreTextGlyphRun
// class based on ivar analysis, method signatures, and the open-source DTCoreText library.
//
#import "DTCoreTextGlyphRun.h"
#import "DTCoreTextLayoutLine.h"
#import <CoreText/CoreText.h>
#pragma mark - DTTextAttachment Stub
/**
* Stub for DTTextAttachment class.
* Represents an embedded object (image, view, etc.) in the text.
*/
@interface DTTextAttachment : NSObject
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, assign) CGSize displaySize;
@property (nonatomic, assign) CGRect frame;
@property (nonatomic, copy) NSString *contentType;
@end
@implementation DTTextAttachment
@end
#pragma mark - Private Interface
@interface DTCoreTextGlyphRun () {
// Cached glyph data
CGGlyph *_glyphs;
CGPoint *_positions;
CGFloat *_advances;
CFIndex _glyphCount;
// Whether glyph data has been extracted
BOOL _glyphsExtracted;
// Cached metrics
CGFloat _cachedAscent;
CGFloat _cachedDescent;
CGFloat _cachedLeading;
CGFloat _cachedWidth;
BOOL _metricsCached;
// Internal lock
NSLock *_lock;
}
@end
#pragma mark - DTCoreTextGlyphRun Implementation
@implementation DTCoreTextGlyphRun
#pragma mark - Lifecycle
- (instancetype)initWithCTRun:(CTRunRef)ctRun
frame:(CGRect)frame
range:(NSRange)range
attributes:(NSDictionary *)attributes
index:(NSUInteger)index {
self = [super init];
if (self) {
_lock = [[NSLock alloc] init];
// Store the CTRun with a retain
if (ctRun) {
_ctRun = (CTRunRef)CFRetain(ctRun);
}
_frame = frame;
_stringRange = range;
_attributes = [attributes copy];
_runIndex = index;
// Initialize state
_glyphsExtracted = NO;
_metricsCached = NO;
_glyphs = NULL;
_positions = NULL;
_advances = NULL;
_glyphCount = 0;
// Extract common attributes
[self extractAttributes];
}
return self;
}
- (void)dealloc {
if (_ctRun) {
CFRelease(_ctRun);
_ctRun = NULL;
}
// Free allocated glyph data
if (_glyphs) {
free(_glyphs);
_glyphs = NULL;
}
if (_positions) {
free(_positions);
_positions = NULL;
}
if (_advances) {
free(_advances);
_advances = NULL;
}
}
#pragma mark - Attribute Extraction
/**
* Extracts common attributes from the attributes dictionary.
*/
- (void)extractAttributes {
if (!_attributes) {
return;
}
// Extract font
CTFontRef ctFont = (__bridge CTFontRef)_attributes[(__bridge NSString *)kCTFontAttributeName];
if (ctFont) {
_font = [UIFont fontWithDescriptor:[UIFontDescriptor fontDescriptorWithCTFont:ctFont]
size:CTFontGetSize(ctFont)];
}
// Extract text color
CGColorRef textColor = (__bridge CGColorRef)_attributes[(__bridge NSString *)kCTForegroundColorAttributeName];
if (textColor) {
_textColor = [UIColor colorWithCGColor:textColor];
}
// Extract background color
CGColorRef bgColor = (__bridge CGColorRef)_attributes[@"DTBackgroundColor"];
if (bgColor) {
_backgroundColor = [UIColor colorWithCGColor:bgColor];
}
// Extract strikethrough
NSNumber *strikethrough = _attributes[(__bridge NSString *)kCTSuperscriptAttributeName];
if (strikethrough) {
_hasStrikethrough = [strikethrough boolValue];
}
// Extract underline
NSNumber *underlineStyle = _attributes[(__bridge NSString *)kCTUnderlineColorAttributeName];
if (underlineStyle) {
_hasUnderline = YES;
_underlineStyle = [underlineStyle integerValue];
}
// Check for attachment
_attachment = _attributes[@"DTTextAttachment"];
if (_attachment) {
_isAttachment = YES;
}
// Extract writing direction
NSArray *writingDirection = _attributes[(__bridge NSString *)kCTWritingDirectionAttributeName];
if ([writingDirection count] > 0) {
_writingDirection = [writingDirection[0] integerValue];
}
}
#pragma mark - Glyph Extraction
/**
* Extracts glyph data from the CTRun.
*
* This method extracts:
* - Glyph values (CGGlyph)
* - Glyph positions (CGPoint)
* - Glyph advances (CGFloat)
*/
- (void)extractGlyphs {
if (_glyphsExtracted || !_ctRun) {
return;
}
[_lock lock];
// Get the number of glyphs
_glyphCount = CTRunGetGlyphCount(_ctRun);
if (_glyphCount == 0) {
_glyphsExtracted = YES;
[_lock unlock];
return;
}
// Allocate memory for glyph data
_glyphs = (CGGlyph *)malloc(sizeof(CGGlyph) * _glyphCount);
_positions = (CGPoint *)malloc(sizeof(CGPoint) * _glyphCount);
_advances = (CGFloat *)malloc(sizeof(CGFloat) * _glyphCount);
// Extract glyph values
CTRunGetGlyphs(_ctRun, CFRangeMake(0, 0), _glyphs);
// Extract glyph positions
CTRunGetPositions(_ctRun, CFRangeMake(0, 0), _positions);
// Extract glyph advances
CTRunGetAdvances(_ctRun, CFRangeMake(0, 0), (CGSize *)_advances);
_glyphsExtracted = YES;
[_lock unlock];
}
#pragma mark - Glyph Access
- (CGGlyph)glyphAtIndex:(NSUInteger)index {
[self extractGlyphs];
if (index >= _glyphCount || !_glyphs) {
return 0;
}
return _glyphs[index];
}
- (CGPoint)positionForGlyphAtIndex:(NSUInteger)index {
[self extractGlyphs];
if (index >= _glyphCount || !_positions) {
return CGPointZero;
}
return _positions[index];
}
- (CGFloat)advanceForGlyphAtIndex:(NSUInteger)index {
[self extractGlyphs];
if (index >= _glyphCount || !_advances) {
return 0;
}
return _advances[index];
}
- (CGRect)rectForGlyphAtIndex:(NSUInteger)index {
[self extractGlyphs];
if (index >= _glyphCount) {
return CGRectNull;
}
CGPoint position = _positions[index];
CGFloat advance = _advances[index];
// Calculate the rect for this glyph
// The position is relative to the run's origin
return CGRectMake(
_frame.origin.x + position.x,
_frame.origin.y,
advance,
_frame.size.height
);
}
#pragma mark - Path Operations
/**
* Creates a CGPath containing all glyphs in this run.
*
* This method creates a path by transforming each glyph's outline
* to its position within the run. This is used for:
* - Custom drawing with effects
* - Hit testing with complex shapes
* - Creating outlines for selection
*
* Algorithm:
* 1. Extract all glyph data
* 2. For each glyph:
* a. Get the glyph's path from the font
* b. Transform to the glyph's position
* c. Add to the combined path
* 3. Return the combined path
*
* @return A CGPath containing all glyph outlines
*/
- (CGPathRef)newPathWithGlyphs {
[self extractGlyphs];
if (!_ctRun || _glyphCount == 0) {
return NULL;
}
// Create a mutable path for the combined glyphs
CGMutablePathRef combinedPath = CGPathCreateMutable();
// Get the font from attributes
CTFontRef font = (__bridge CTFontRef)_attributes[(__bridge NSString *)kCTFontAttributeName];
if (!font) {
return combinedPath;
}
// Process each glyph
for (CFIndex i = 0; i < _glyphCount; i++) {
CGGlyph glyph = _glyphs[i];
CGPoint position = _positions[i];
// Get the path for this glyph from the font
CGPathRef glyphPath = CTFontCreatePathForGlyph(font, glyph, NULL);
if (glyphPath) {
// Create a transform to position the glyph
// The position is relative to the run's origin
CGAffineTransform transform = CGAffineTransformMakeTranslation(
_frame.origin.x + position.x,
_frame.origin.y + position.y
);
// Add the transformed glyph path to the combined path
CGPathAddPath(combinedPath, &transform, glyphPath);
// Release the individual glyph path
CGPathRelease(glyphPath);
}
}
return combinedPath;
}
/**
* Creates a CGPath for a specific glyph.
*/
- (CGPathRef)newPathForGlyphAtIndex:(NSUInteger)index {
[self extractGlyphs];
if (!_ctRun || index >= _glyphCount) {
return NULL;
}
// Get the font from attributes
CTFontRef font = (__bridge CTFontRef)_attributes[(__bridge NSString *)kCTFontAttributeName];
if (!font) {
return NULL;
}
CGGlyph glyph = _glyphs[index];
CGPoint position = _positions[index];
// Get the path for this glyph
CGPathRef glyphPath = CTFontCreatePathForGlyph(font, glyph, NULL);
if (!glyphPath) {
return NULL;
}
// Create a transform to position the glyph
CGAffineTransform transform = CGAffineTransformMakeTranslation(
_frame.origin.x + position.x,
_frame.origin.y + position.y
);
// Create a new path with the transform applied
CGMutablePathRef transformedPath = CGPathCreateMutable();
CGPathAddPath(transformedPath, &transform, glyphPath);
// Release the original glyph path
CGPathRelease(glyphPath);
return transformedPath;
}
/**
* Creates a bounding path for all glyphs.
*/
- (CGPathRef)newBoundingPath {
[self extractGlyphs];
if (_glyphCount == 0) {
return NULL;
}
// Create a simple rectangular path that bounds all glyphs
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, _frame);
return path;
}
#pragma mark - Image Operations
/**
* Returns the image for the glyph at the specified index.
*
* This is used for custom rendering where glyphs are replaced with images.
*/
- (UIImage *)imageForGlyphAtIndex:(NSUInteger)index {
if (!_glyphImages || index >= [_glyphImages count]) {
return nil;
}
return _glyphImages[index];
}
/**
* Returns the bounding rect for the glyph image.
*/
- (CGRect)imageRectForGlyphAtIndex:(NSUInteger)index {
[self extractGlyphs];
if (index >= _glyphCount) {
return CGRectNull;
}
CGPoint position = _positions[index];
CGFloat advance = _advances[index];
// The image rect is centered on the glyph position
return CGRectMake(
_frame.origin.x + position.x,
_frame.origin.y,
advance,
_frame.size.height
);
}
#pragma mark - Hit Testing
/**
* Returns the glyph index at the given point.
*
* This method checks if the point falls within any glyph's bounding rect.
*
* @param point The point to test
* @return The glyph index, or NSNotFound
*/
- (NSUInteger)glyphIndexAtPoint:(CGPoint)point {
[self extractGlyphs];
for (NSUInteger i = 0; i < _glyphCount; i++) {
CGRect glyphRect = [self rectForGlyphAtIndex:i];
if (CGRectContainsPoint(glyphRect, point)) {
return i;
}
}
return NSNotFound;
}
/**
* Returns the string index at the given point.
*/
- (NSUInteger)stringIndexAtPoint:(CGPoint)point {
NSUInteger glyphIndex = [self glyphIndexAtPoint:point];
if (glyphIndex == NSNotFound) {
return NSNotFound;
}
// Convert glyph index to string index
// This assumes a 1:1 mapping between glyphs and characters
// For complex scripts, this may need more sophisticated mapping
return _stringRange.location + glyphIndex;
}
/**
* Returns the rect for a given string index.
*/
- (CGRect)rectForStringIndex:(NSUInteger)index {
if (index < _stringRange.location ||
index >= _stringRange.location + _stringRange.length) {
return CGRectNull;
}
// Convert string index to glyph index
NSUInteger glyphIndex = index - _stringRange.location;
return [self rectForGlyphAtIndex:glyphIndex];
}
#pragma mark - Drawing
/**
* Draws this run into a CGContext.
*
* This method draws the glyphs using CTLineDraw or by manually
* drawing each glyph at its position.
*
* @param context The CGContext to draw into
*/
- (void)drawInContext:(CGContextRef)context {
if (!context || !_ctRun) {
return;
}
// Save graphics state
CGContextSaveGState(context);
// Draw the run using CoreText
CTRunDraw(_ctRun, context, CFRangeMake(0, 0));
// Restore graphics state
CGContextRestoreGState(context);
}
/**
* Draws this run with a specific color.
*/
- (void)drawInContext:(CGContextRef)context withColor:(UIColor *)color {
if (!context || !_ctRun || !color) {
return;
}
// Save graphics state
CGContextSaveGState(context);
// Set the text color
CGContextSetFillColorWithColor(context, color.CGColor);
// Draw the run
CTRunDraw(_ctRun, context, CFRangeMake(0, 0));
// Restore graphics state
CGContextRestoreGState(context);
}
/**
* Draws the attachment (if any) into the context.
*/
- (void)drawAttachmentInContext:(CGContextRef)context {
if (!context || !_attachment || !_attachment.image) {
return;
}
// Save graphics state
CGContextSaveGState(context);
// Draw the attachment image at the run's frame
CGContextDrawImage(context, _frame, _attachment.image.CGImage);
// Restore graphics state
CGContextRestoreGState(context);
}
#pragma mark - Run Comparison
/**
* Compares this run to another run for ordering.
*/
- (NSComparisonResult)compareToRun:(DTCoreTextGlyphRun *)otherRun {
// Compare by string range location
if (_stringRange.location < otherRun.stringRange.location) {
return NSOrderedAscending;
} else if (_stringRange.location > otherRun.stringRange.location) {
return NSOrderedDescending;
}
// If same location, compare by run index
if (_runIndex < otherRun.runIndex) {
return NSOrderedAscending;
} else if (_runIndex > otherRun.runIndex) {
return NSOrderedDescending;
}
return NSOrderedSame;
}
/**
* Returns whether this run contains the given string index.
*/
- (BOOL)containsStringIndex:(NSUInteger)index {
return index >= _stringRange.location &&
index < _stringRange.location + _stringRange.length;
}
/**
* Returns whether this run intersects with the given range.
*/
- (BOOL)intersectsRange:(NSRange)range {
NSRange intersection = NSIntersectionRange(_stringRange, range);
return intersection.length > 0;
}
#pragma mark - Metrics
- (void)calculateMetrics {
if (_metricsCached || !_ctRun) {
return;
}
[_lock lock];
// Get typographic bounds from CTRun
_cachedWidth = CTRunGetTypographicBounds(_ctRun, CFRangeMake(0, 0),
&_cachedAscent, &_cachedDescent, &_cachedLeading);
_metricsCached = YES;
[_lock unlock];
}
- (CGFloat)getTypographicBoundsAscent:(CGFloat *)ascent
descent:(CGFloat *)descent
leading:(CGFloat *)leading {
[self calculateMetrics];
if (ascent) *ascent = _cachedAscent;
if (descent) *descent = _cachedDescent;
if (leading) *leading = _cachedLeading;
return _cachedWidth;
}
- (CGRect)bounds {
[self calculateMetrics];
return CGRectMake(0, -_cachedDescent, _cachedWidth, _cachedAscent + _cachedDescent);
}
- (CGFloat)width {
[self calculateMetrics];
return _cachedWidth;
}
- (CGFloat)height {
[self calculateMetrics];
return _cachedAscent + _cachedDescent;
}
- (NSUInteger)numberOfGlyphs {
[self extractGlyphs];
return _glyphCount;
}
- (NSArray<NSNumber *> *)glyphs {
[self extractGlyphs];
NSMutableArray *glyphArray = [NSMutableArray arrayWithCapacity:_glyphCount];
for (CFIndex i = 0; i < _glyphCount; i++) {
[glyphArray addObject:@(_glyphs[i])];
}
return [glyphArray copy];
}
- (NSArray<NSValue *> *)glyphPositions {
[self extractGlyphs];
NSMutableArray *positionArray = [NSMutableArray arrayWithCapacity:_glyphCount];
for (CFIndex i = 0; i < _glyphCount; i++) {
[positionArray addObject:[NSValue valueWithCGPoint:_positions[i]]];
}
return [positionArray copy];
}
- (NSArray<NSNumber *> *)glyphAdvances {
[self extractGlyphs];
NSMutableArray *advanceArray = [NSMutableArray arrayWithCapacity:_glyphCount];
for (CFIndex i = 0; i < _glyphCount; i++) {
[advanceArray addObject:@(_advances[i])];
}
return [advanceArray copy];
}
- (BOOL)isRTL {
return _writingDirection == kCTWritingDirectionRightToLeft;
}
- (BOOL)isWhitespace {
if (!_ctRun || _glyphCount == 0) {
return NO;
}
// Check if all characters in the range are whitespace
// This would need access to the full attributed string
// For now, return NO as a conservative default
return NO;
}
- (BOOL)isNewline {
if (!_ctRun || _glyphCount == 0) {
return NO;
}
// Check if the run contains only newline characters
// This would need access to the full attributed string
return NO;
}
@end