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