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

262 lines
7.7 KiB
Objective-C

//
// WRPageUnderline.m
// WeRead (微信读书)
// Reverse-engineered implementation reconstruction
//
// 10 methods identified from binary. WRPageUnderline renders underline
// annotations with configurable styles (solid, dashed, wavy, dotted).
//
#import "WRPageUnderline.h"
#import "WRBookmark.h"
#pragma mark - Implementation
@implementation WRPageUnderline
#pragma mark - Initialization
- (instancetype)initWithBookId:(NSString *)bookId
chapterUid:(NSString *)chapterUid
startPos:(NSInteger)startPos
endPos:(NSInteger)endPos
text:(NSString *)text
style:(WRUnderlineStyle)style
{
self = [super init];
if (self) {
_underlineId = [[NSUUID UUID] UUIDString];
_bookId = [bookId copy];
_chapterUid = [chapterUid copy];
_startPos = startPos;
_endPos = endPos;
_chapterOffset = startPos;
_markedText = [text copy];
_style = style;
_color = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:0.8];
_createTime = [[NSDate date] timeIntervalSince1970];
_isSynced = NO;
}
return self;
}
#pragma mark - Conversion
- (WRBookmark *)toBookmark
{
WRBookmark *bm = [WRBookmark bookmarkWithBookId:_bookId
chapterUid:_chapterUid
offset:_startPos
text:_markedText
type:WRBookmarkTypeUnderline];
bm.bookmarkId = _underlineId;
bm.startPos = _startPos;
bm.endPos = _endPos;
bm.rangeLength = _endPos - _startPos;
bm.chapterOffset = _chapterOffset;
bm.noteContent = _noteContent;
bm.createTime = _createTime;
bm.isSynced = _isSynced;
// Store underline style in extra metadata
bm.extraMetadata = @{@"underlineStyle": @(_style)};
return bm;
}
+ (nullable instancetype)fromBookmark:(WRBookmark *)bookmark
{
if (!bookmark || bookmark.type != WRBookmarkTypeUnderline) return nil;
WRPageUnderline *ul = [[WRPageUnderline alloc]
initWithBookId:bookmark.bookId
chapterUid:bookmark.chapterUid
startPos:bookmark.startPos
endPos:bookmark.endPos
text:bookmark.markText
style:[bookmark.extraMetadata[@"underlineStyle"] integerValue]];
ul.underlineId = bookmark.bookmarkId;
ul.chapterOffset = bookmark.chapterOffset;
ul.noteContent = bookmark.noteContent;
ul.createTime = bookmark.createTime;
ul.isSynced = bookmark.isSynced;
return ul;
}
#pragma mark - Drawing
- (UIBezierPath *)underlinePathForRect:(CGRect)rect
{
UIBezierPath *path = [UIBezierPath bezierPath];
CGFloat y = CGRectGetMaxY(rect) - 2.0; // 2pt below baseline
switch (_style) {
case WRUnderlineStyleSolid: {
[path moveToPoint:CGPointMake(CGRectGetMinX(rect), y)];
[path addLineToPoint:CGPointMake(CGRectGetMaxX(rect), y)];
path.lineWidth = 1.5;
break;
}
case WRUnderlineStyleDashed: {
[path moveToPoint:CGPointMake(CGRectGetMinX(rect), y)];
[path addLineToPoint:CGPointMake(CGRectGetMaxX(rect), y)];
path.lineWidth = 1.5;
// Set dash pattern: 6pt dash, 3pt gap
CGFloat pattern[] = {6.0, 3.0};
[path setLineDash:pattern count:2 phase:0];
break;
}
case WRUnderlineStyleWavy: {
// Wavy underline: sine wave approximation
CGFloat startX = CGRectGetMinX(rect);
CGFloat endX = CGRectGetMaxX(rect);
CGFloat width = endX - startX;
CGFloat amplitude = 2.0;
CGFloat wavelength = 8.0;
[path moveToPoint:CGPointMake(startX, y)];
for (CGFloat x = startX; x < endX; x += 1.0) {
CGFloat progress = (x - startX) / wavelength;
CGFloat waveY = y + sin(progress * M_PI * 2) * amplitude;
[path addLineToPoint:CGPointMake(x, waveY)];
}
path.lineWidth = 1.0;
break;
}
case WRUnderlineStyleDotted: {
[path moveToPoint:CGPointMake(CGRectGetMinX(rect), y)];
[path addLineToPoint:CGPointMake(CGRectGetMaxX(rect), y)];
path.lineWidth = 2.0;
CGFloat pattern[] = {1.0, 4.0};
[path setLineDash:pattern count:2 phase:0];
break;
}
}
return path;
}
- (UIColor *)underlineColor
{
return _color ?: [UIColor darkGrayColor];
}
#pragma mark - Public Methods
- (void)setStyle:(WRUnderlineStyle)style
{
_style = style;
}
- (void)setNote:(NSString *)note
{
_noteContent = [note copy];
}
- (BOOL)containsPosition:(NSInteger)position
{
return position >= _startPos && position < _endPos;
}
- (BOOL)mergeWithUnderline:(WRPageUnderline *)other
{
if (!other) return NO;
// Check if ranges are adjacent or overlapping
if (other.endPos < _startPos - 1 || other.startPos > _endPos + 1) {
return NO; // Not adjacent
}
// Check same book and chapter
if (![_bookId isEqualToString:other.bookId] ||
![_chapterUid isEqualToString:other.chapterUid]) {
return NO;
}
// Merge ranges
_startPos = MIN(_startPos, other.startPos);
_endPos = MAX(_endPos, other.endPos);
// Merge text (preserve order)
if (other.startPos < _startPos) {
_markedText = [other.markedText stringByAppendingString:_markedText];
} else {
_markedText = [_markedText stringByAppendingString:other.markedText];
}
return YES;
}
- (NSInteger)rangeLength
{
return _endPos - _startPos;
}
- (NSDictionary *)toDictionary
{
return @{
@"underlineId" : _underlineId ?: @"",
@"bookId" : _bookId ?: @"",
@"chapterUid" : _chapterUid ?: @"",
@"startPos" : @(_startPos),
@"endPos" : @(_endPos),
@"chapterOffset" : @(_chapterOffset),
@"markedText" : _markedText ?: @"",
@"style" : @(_style),
@"pageIndex" : @(_pageIndex),
@"createTime" : @(_createTime),
@"isSynced" : @(_isSynced),
@"noteContent" : _noteContent ?: @"",
};
}
+ (nullable instancetype)fromDictionary:(NSDictionary *)dict
{
if (!dict) return nil;
WRPageUnderline *ul = [[WRPageUnderline alloc]
initWithBookId:dict[@"bookId"]
chapterUid:dict[@"chapterUid"]
startPos:[dict[@"startPos"] integerValue]
endPos:[dict[@"endPos"] integerValue]
text:dict[@"markedText"]
style:[dict[@"style"] integerValue]];
ul.underlineId = dict[@"underlineId"];
ul.chapterOffset = [dict[@"chapterOffset"] integerValue];
ul.pageIndex = [dict[@"pageIndex"] integerValue];
ul.createTime = [dict[@"createTime"] doubleValue];
ul.isSynced = [dict[@"isSynced"] boolValue];
ul.noteContent = dict[@"noteContent"];
return ul;
}
- (NSComparisonResult)compareTo:(WRPageUnderline *)other
{
if (_startPos < other.startPos) return NSOrderedAscending;
if (_startPos > other.startPos) return NSOrderedDescending;
if (_endPos < other.endPos) return NSOrderedAscending;
if (_endPos > other.endPos) return NSOrderedDescending;
return NSOrderedSame;
}
#pragma mark - NSObject
- (NSString *)description
{
return [NSString stringWithFormat:
@"<WRPageUnderline: %@ style=%ld range=[%ld,%ld] '%@'>",
_underlineId, (long)_style, (long)_startPos, (long)_endPos,
[_markedText substringToIndex:MIN(30, _markedText.length)]];
}
@end