diff options
author | Paul Gardiner <paul.gardiner@artifex.com> | 2013-11-14 15:24:53 +0000 |
---|---|---|
committer | Paul Gardiner <paul.gardiner@artifex.com> | 2013-11-19 10:18:28 +0000 |
commit | 6f532f78076d2bb24a8c6cf47c0f45994fa5b707 (patch) | |
tree | 870193262147eba8f35d06f3fd72ffba8369ebee /platform | |
parent | e4c8dcc641b9cd3c7645b034b6f632e0965a059b (diff) | |
download | mupdf-6f532f78076d2bb24a8c6cf47c0f45994fa5b707.tar.xz |
iOS: add support for creating markup annotations
Diffstat (limited to 'platform')
-rw-r--r-- | platform/ios/Classes/MuDocumentController.h | 16 | ||||
-rw-r--r-- | platform/ios/Classes/MuDocumentController.m | 118 | ||||
-rw-r--r-- | platform/ios/Classes/MuPageView.h | 3 | ||||
-rw-r--r-- | platform/ios/Classes/MuPageViewNormal.h | 2 | ||||
-rw-r--r-- | platform/ios/Classes/MuPageViewNormal.m | 243 | ||||
-rw-r--r-- | platform/ios/Classes/MuPageViewReflow.m | 4 | ||||
-rw-r--r-- | platform/ios/Classes/MuTextSelectView.h | 21 | ||||
-rw-r--r-- | platform/ios/Classes/MuTextSelectView.m | 111 | ||||
-rw-r--r-- | platform/ios/Classes/MuWord.h | 21 | ||||
-rw-r--r-- | platform/ios/Classes/MuWord.m | 100 | ||||
-rw-r--r-- | platform/ios/MuPDF.xcodeproj/project.pbxproj | 32 |
11 files changed, 659 insertions, 12 deletions
diff --git a/platform/ios/Classes/MuDocumentController.h b/platform/ios/Classes/MuDocumentController.h index 7cf943f8..ab95a88a 100644 --- a/platform/ios/Classes/MuDocumentController.h +++ b/platform/ios/Classes/MuDocumentController.h @@ -17,6 +17,16 @@ #import "MuDocRef.h" #import "MuDialogCreator.h" +enum +{ + BARMODE_MAIN, + BARMODE_SEARCH, + BARMODE_ANNOTATION, + BARMODE_HIGHLIGHT, + BARMODE_UNDERLINE, + BARMODE_STRIKE +}; + @interface MuDocumentController : UIViewController <UIScrollViewDelegate, UIGestureRecognizerDelegate, UISearchBarDelegate, MuDialogCreator> { fz_document *doc; @@ -29,8 +39,12 @@ UISlider *slider; UISearchBar *searchBar; UIBarButtonItem *nextButton, *prevButton, *cancelButton, *searchButton, *outlineButton, *linkButton; + UIBarButtonItem *moreButton; + UIBarButtonItem *highlightButton, *underlineButton, *strikeoutButton; + UIBarButtonItem *tickButton; UIBarButtonItem *reflowButton; UIBarButtonItem *sliderWrapper; + int barmode; int searchPage; int cancelSearch; int showLinks; @@ -45,7 +59,7 @@ - (void) gotoPage: (int)number animated: (BOOL)animated; - (void) onShowOutline: (id)sender; - (void) onShowSearch: (id)sender; -- (void) onCancelSearch: (id)sender; +- (void) onCancel: (id)sender; - (void) resetSearch; - (void) showSearchResults: (int)count forPage: (int)number; - (void) onSlide: (id)sender; diff --git a/platform/ios/Classes/MuDocumentController.m b/platform/ios/Classes/MuDocumentController.m index 675e9b68..1cf210fb 100644 --- a/platform/ios/Classes/MuDocumentController.m +++ b/platform/ios/Classes/MuDocumentController.m @@ -81,6 +81,7 @@ static void flattenOutline(NSMutableArray *titles, NSMutableArray *pages, fz_out - (void) addMainMenuButtons { NSMutableArray *array = [NSMutableArray arrayWithCapacity:3]; + [array addObject:moreButton]; [array addObject:searchButton]; if (outlineButton) [array addObject:outlineButton]; @@ -150,11 +151,16 @@ static void flattenOutline(NSMutableArray *titles, NSMutableArray *pages, fz_out outlineButton = [self resourceBasedButton:@"ic_list" withAction:@selector(onShowOutline:)]; } linkButton = [self resourceBasedButton:@"ic_link" withAction:@selector(onToggleLinks:)]; - cancelButton = [self resourceBasedButton:@"ic_cancel" withAction:@selector(onCancelSearch:)]; + cancelButton = [self resourceBasedButton:@"ic_cancel" withAction:@selector(onCancel:)]; searchButton = [self resourceBasedButton:@"ic_magnifying_glass" withAction:@selector(onShowSearch:)]; prevButton = [self resourceBasedButton:@"ic_arrow_left" withAction:@selector(onSearchPrev:)]; nextButton = [self resourceBasedButton:@"ic_arrow_right" withAction:@selector(onSearchNext:)]; reflowButton = [self resourceBasedButton:@"ic_reflow" withAction:@selector(onToggleReflow:)]; + moreButton = [self resourceBasedButton:@"ic_more" withAction:@selector(onMore:)]; + highlightButton = [self resourceBasedButton:@"ic_highlight" withAction:@selector(onHighlight:)]; + underlineButton = [self resourceBasedButton:@"ic_underline" withAction:@selector(onUnderline:)]; + strikeoutButton = [self resourceBasedButton:@"ic_strike" withAction:@selector(onStrikeout:)]; + tickButton = [self resourceBasedButton:@"ic_check" withAction:@selector(onTick:)]; searchBar = [[UISearchBar alloc] initWithFrame: CGRectMake(0,0,50,32)]; [searchBar setPlaceholder: @"Search"]; [searchBar setDelegate: self]; @@ -179,12 +185,17 @@ static void flattenOutline(NSMutableArray *titles, NSMutableArray *pages, fz_out [slider release]; slider = nil; [sliderWrapper release]; sliderWrapper = nil; [reflowButton release]; reflowButton = nil; + [moreButton release]; moreButton = nil; [searchBar release]; searchBar = nil; [outlineButton release]; outlineButton = nil; [searchButton release]; searchButton = nil; [cancelButton release]; cancelButton = nil; [prevButton release]; prevButton = nil; [nextButton release]; nextButton = nil; + [highlightButton release]; highlightButton = nil; + [underlineButton release]; underlineButton = nil; + [strikeoutButton release]; strikeoutButton = nil; + [tickButton release]; tickButton = nil; [canvas release]; canvas = nil; [outline release]; @@ -315,6 +326,54 @@ static void flattenOutline(NSMutableArray *titles, NSMutableArray *pages, fz_out [self scrollViewDidScroll:canvas]; } +- (void) showAnnotationMenu +{ + [[self navigationItem] setRightBarButtonItems:[NSArray arrayWithObjects:strikeoutButton, underlineButton, highlightButton, nil]]; + [[self navigationItem] setLeftBarButtonItem:cancelButton]; + barmode = BARMODE_ANNOTATION; +} + +- (void) onMore: (id)sender +{ + [self showAnnotationMenu]; +} + +- (void) textSelectModeOn +{ + [[self navigationItem] setRightBarButtonItems:[NSArray arrayWithObject:tickButton]]; + for (UIView<MuPageView> *view in [canvas subviews]) + { + if ([view number] == current) + [view textSelectModeOn]; + } +} + +- (void) textSelectModeOff +{ + for (UIView<MuPageView> *view in [canvas subviews]) + { + [view textSelectModeOff]; + } +} + +- (void) onHighlight: (id)sender +{ + barmode = BARMODE_HIGHLIGHT; + [self textSelectModeOn]; +} + +- (void) onUnderline: (id)sender +{ + barmode = BARMODE_UNDERLINE; + [self textSelectModeOn]; +} + +- (void) onStrikeout: (id)sender +{ + barmode = BARMODE_STRIKE; + [self textSelectModeOn]; +} + - (void) onShowSearch: (id)sender { [[self navigationItem] setRightBarButtonItems: @@ -322,16 +381,59 @@ static void flattenOutline(NSMutableArray *titles, NSMutableArray *pages, fz_out [[self navigationItem] setLeftBarButtonItem: cancelButton]; [[self navigationItem] setTitleView: searchBar]; [searchBar becomeFirstResponder]; + barmode = BARMODE_SEARCH; } -- (void) onCancelSearch: (id)sender +- (void) onTick: (id)sender { - cancelSearch = YES; - [searchBar resignFirstResponder]; - [[self navigationItem] setTitleView: nil]; - [self addMainMenuButtons]; - [[self navigationItem] setLeftBarButtonItem: nil]; - [self resetSearch]; + int type; + switch (barmode) + { + case BARMODE_HIGHLIGHT: + type = FZ_ANNOT_HIGHLIGHT; + break; + + case BARMODE_UNDERLINE: + type = FZ_ANNOT_UNDERLINE; + break; + + case BARMODE_STRIKE: + type = FZ_ANNOT_STRIKEOUT; + break; + } + + for (UIView<MuPageView> *view in [canvas subviews]) + { + if ([view number] == current) + [view saveMarkup:type]; + } + + [self showAnnotationMenu]; +} + +- (void) onCancel: (id)sender +{ + switch (barmode) + { + case BARMODE_SEARCH: + cancelSearch = YES; + [searchBar resignFirstResponder]; + [self resetSearch]; + /* fallthrough */ + case BARMODE_ANNOTATION: + [[self navigationItem] setTitleView: nil]; + [self addMainMenuButtons]; + [[self navigationItem] setLeftBarButtonItem: nil]; + barmode = BARMODE_MAIN; + break; + + case BARMODE_HIGHLIGHT: + case BARMODE_UNDERLINE: + case BARMODE_STRIKE: + [self showAnnotationMenu]; + [self textSelectModeOff]; + break; + } } - (void) resetSearch diff --git a/platform/ios/Classes/MuPageView.h b/platform/ios/Classes/MuPageView.h index d0b2f48d..690d8beb 100644 --- a/platform/ios/Classes/MuPageView.h +++ b/platform/ios/Classes/MuPageView.h @@ -18,4 +18,7 @@ -(void) resetZoomAnimated: (BOOL)animated; -(void) setScale:(float)scale; -(MuTapResult *) handleTap:(CGPoint)pt; +-(void) textSelectModeOn; +-(void) textSelectModeOff; +-(void) saveMarkup:(int)type; @end diff --git a/platform/ios/Classes/MuPageViewNormal.h b/platform/ios/Classes/MuPageViewNormal.h index cc4a8e1d..9232e359 100644 --- a/platform/ios/Classes/MuPageViewNormal.h +++ b/platform/ios/Classes/MuPageViewNormal.h @@ -17,6 +17,7 @@ #import "MuPageView.h" #import "MuDocRef.h" #import "MuDialogCreator.h" +#import "MuTextSelectView.h" @interface MuPageViewNormal : UIScrollView <UIScrollViewDelegate,MuPageView> { @@ -35,6 +36,7 @@ UIImageView *tileView; MuHitView *hitView; MuHitView *linkView; + MuTextSelectView *textSelectView; NSArray *widgetRects; CGSize pageSize; CGRect tileFrame; diff --git a/platform/ios/Classes/MuPageViewNormal.m b/platform/ios/Classes/MuPageViewNormal.m index 8c247297..6262a7b1 100644 --- a/platform/ios/Classes/MuPageViewNormal.m +++ b/platform/ios/Classes/MuPageViewNormal.m @@ -7,7 +7,15 @@ #include "common.h" #include "mupdf/pdf.h" +#import "MuWord.h" #import "MuTextFieldController.h" +#import "MuTextSelectView.h" + +#import "MuPageViewNormal.h" + +#define STRIKE_HEIGHT (0.375f) +#define UNDERLINE_HEIGHT (0.075f) +#define LINE_THICKNESS (0.07f) static void releasePixmap(void *info, const void *data, size_t size) { @@ -71,6 +79,181 @@ static NSArray *enumerateWidgetRects(fz_document *doc, fz_page *page, CGSize pag return [arr retain]; } +static NSArray *enumerateWords(fz_document *doc, fz_page *page) +{ + fz_text_sheet *sheet = NULL; + fz_text_page *text = NULL; + fz_device *dev = NULL; + NSMutableArray *lns = [NSMutableArray array]; + NSMutableArray *wds; + MuWord *word; + + if (!lns) + return NULL; + + fz_var(sheet); + fz_var(text); + fz_var(dev); + + fz_try(ctx); + { + int b, l, c; + + sheet = fz_new_text_sheet(ctx); + text = fz_new_text_page(ctx); + dev = fz_new_text_device(ctx, sheet, text); + fz_run_page(doc, page, dev, &fz_identity, NULL); + fz_free_device(dev); + dev = NULL; + + for (b = 0; b < text->len; b++) + { + fz_text_block *block; + + if (text->blocks[b].type != FZ_PAGE_BLOCK_TEXT) + continue; + + block = text->blocks[b].u.text; + + for (l = 0; l < block->len; l++) + { + fz_text_line *line = &block->lines[l]; + fz_text_span *span; + + wds = [NSMutableArray array]; + if (!wds) + fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to create word array"); + + word = [MuWord word]; + if (!word) + fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to create word"); + + for (span = line->first_span; span; span = span->next) + { + for (c = 0; c < span->len; c++) + { + fz_text_char *ch = &span->text[c]; + fz_rect bbox; + CGRect rect; + + fz_text_char_bbox(&bbox, span, c); + rect = CGRectMake(bbox.x0, bbox.y0, bbox.x1 - bbox.x0, bbox.y1 - bbox.y0); + + if (ch->c != ' ') + { + [word appendChar:ch->c withRect:rect]; + } + else if (word.string.length > 0) + { + [wds addObject:word]; + word = [MuWord word]; + if (!word) + fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to create word"); + } + } + } + + if (word.string.length > 0) + [wds addObject:word]; + + if ([wds count] > 0) + [lns addObject:wds]; + } + } + } + fz_always(ctx); + { + fz_free_text_page(ctx, text); + fz_free_text_sheet(ctx, sheet); + fz_free_device(dev); + } + fz_catch(ctx) + { + lns = NULL; + } + + return [lns retain]; +} + +static void addMarkupAnnot(fz_document *doc, fz_page *page, int type, NSArray *rects) +{ + pdf_document *idoc; + fz_point *quadpts = NULL; + float color[3]; + float alpha; + float line_height; + float line_thickness; + + idoc = pdf_specifics(doc); + if (!idoc) + return; + + switch (type) + { + case FZ_ANNOT_HIGHLIGHT: + color[0] = 1.0; + color[1] = 1.0; + color[2] = 0.0; + alpha = 0.5; + line_thickness = 1.0; + line_height = 0.5; + break; + case FZ_ANNOT_UNDERLINE: + color[0] = 0.0; + color[1] = 0.0; + color[2] = 1.0; + alpha = 1.0; + line_thickness = LINE_THICKNESS; + line_height = UNDERLINE_HEIGHT; + break; + case FZ_ANNOT_STRIKEOUT: + color[0] = 1.0; + color[1] = 0.0; + color[2] = 0.0; + alpha = 1.0; + line_thickness = LINE_THICKNESS; + line_height = STRIKE_HEIGHT; + break; + } + + fz_var(quadpts); + fz_try(ctx) + { + int i; + pdf_annot *annot; + + quadpts = fz_malloc_array(ctx, rects.count * 4, sizeof(fz_point)); + for (i = 0; i < rects.count; i++) + { + CGRect rect = [[rects objectAtIndex:i] CGRectValue]; + float top = rect.origin.y; + float bot = top + rect.size.height; + float left = rect.origin.x; + float right = left + rect.size.width; + quadpts[i*4].x = left; + quadpts[i*4].y = bot; + quadpts[i*4+1].x = right; + quadpts[i*4+1].y = bot; + quadpts[i*4+2].x = right; + quadpts[i*4+2].y = top; + quadpts[i*4+3].x = left; + quadpts[i*4+3].y = top; + } + + annot = pdf_create_annot(idoc, (pdf_page *)page, type); + pdf_set_markup_annot_quadpoints(idoc, annot, quadpts, rects.count*4); + pdf_set_markup_appearance(idoc, annot, color, alpha, line_thickness, line_height); + } + fz_always(ctx) + { + fz_free(ctx, quadpts); + } + fz_catch(ctx) + { + printf("Annotation creation failed\n"); + } +} + static int setFocussedWidgetText(fz_document *doc, fz_page *page, const char *text) { int accepted; @@ -329,7 +512,6 @@ static void updatePixmap(fz_document *doc, fz_display_list *page_list, fz_displa } } -#import "MuPageViewNormal.h" @implementation MuPageViewNormal @@ -426,6 +608,7 @@ static void updatePixmap(fz_document *doc, fz_display_list *page_list, fz_displa [widgetRects release]; [linkView release]; [hitView release]; + [textSelectView release]; [tileView release]; [loadingView release]; [imageView release]; @@ -490,6 +673,47 @@ static void updatePixmap(fz_document *doc, fz_display_list *page_list, fz_displa } } +- (void) textSelectModeOn +{ + dispatch_async(queue, ^{ + [self ensurePageLoaded]; + NSArray *words = enumerateWords(doc, page); + dispatch_sync(dispatch_get_main_queue(), ^{ + textSelectView = [[MuTextSelectView alloc] initWithWords:words pageSize:pageSize]; + [words release]; + if (imageView) + [textSelectView setFrame:[imageView frame]]; + [self addSubview:textSelectView]; + }); + }); +} + +- (void) textSelectModeOff +{ + [textSelectView removeFromSuperview]; + [textSelectView release]; + textSelectView = nil; +} + +-(void) saveMarkup:(int)type +{ + CGRect tframe = tileFrame; + float tscale = tileScale; + CGRect vframe = tframe; + vframe.origin.x -= imageView.frame.origin.x; + vframe.origin.y -= imageView.frame.origin.y; + + NSArray *rects = [textSelectView selectionRects]; + if (rects.count == 0) + return; + + dispatch_async(queue, ^{ + addMarkupAnnot(doc, page, type, rects); + [self updatePageAndTileWithTileFrame:tframe tileScale:tscale viewFrame:vframe]; + }); + [self textSelectModeOff]; +} + - (void) resetZoomAnimated: (BOOL)animated { // discard tile and any pending tile jobs @@ -554,6 +778,8 @@ static void updatePixmap(fz_document *doc, fz_display_list *page_list, fz_displa [self addSubview: imageView]; if (hitView) [self bringSubviewToFront: hitView]; + if (textSelectView) + [self bringSubviewToFront:textSelectView]; } else { [imageView setImage: image]; } @@ -632,6 +858,9 @@ static void updatePixmap(fz_document *doc, fz_display_list *page_list, fz_displa if (linkView) [linkView setFrame:[imageView frame]]; + + if (textSelectView) + [textSelectView setFrame:[imageView frame]]; } } @@ -694,6 +923,8 @@ static void updatePixmap(fz_document *doc, fz_display_list *page_list, fz_displa [self bringSubviewToFront: hitView]; if (linkView) [self bringSubviewToFront:linkView]; + if (textSelectView) + [self bringSubviewToFront:textSelectView]; } else { printf("discard tile\n"); } @@ -730,8 +961,14 @@ static void updatePixmap(fz_document *doc, fz_display_list *page_list, fz_displa - (void) scrollViewDidZoom: (UIScrollView*)scrollView { - if (hitView && imageView) - [hitView setFrame: [imageView frame]]; + if (imageView) + { + if (hitView) + [hitView setFrame: [imageView frame]]; + + if (textSelectView) + [textSelectView setFrame:[imageView frame]]; + } } - (void) setScale:(float)scale {} diff --git a/platform/ios/Classes/MuPageViewReflow.m b/platform/ios/Classes/MuPageViewReflow.m index de47aae3..f81f055e 100644 --- a/platform/ios/Classes/MuPageViewReflow.m +++ b/platform/ios/Classes/MuPageViewReflow.m @@ -122,6 +122,10 @@ NSString *textAsHtml(fz_document *doc, int pageNum) -(void) hideLinks {} -(void) showSearchResults: (int)count {} -(void) clearSearchResults {} +-(void) textSelectModeOn {} +-(void) textSelectModeOff {} +-(void) saveMarkup:(int)type {} + -(void) resetZoomAnimated: (BOOL)animated { [self.scrollView setContentOffset:CGPointZero animated:NO]; diff --git a/platform/ios/Classes/MuTextSelectView.h b/platform/ios/Classes/MuTextSelectView.h new file mode 100644 index 00000000..95710bd3 --- /dev/null +++ b/platform/ios/Classes/MuTextSelectView.h @@ -0,0 +1,21 @@ +// +// MuTextSelectView.h +// MuPDF +// +// Copyright (c) 2013 Artifex Software, Inc. All rights reserved. +// + +#include "common.h" + +@interface MuTextSelectView : UIView +{ + NSArray *words; + CGSize pageSize; + UIColor *color; + CGPoint start; + CGPoint end; +} +- (id) initWithWords:(NSArray *)_words pageSize:(CGSize)_pageSize; +- (NSArray *) selectionRects; +- (NSString *) selectedText; +@end diff --git a/platform/ios/Classes/MuTextSelectView.m b/platform/ios/Classes/MuTextSelectView.m new file mode 100644 index 00000000..433a3442 --- /dev/null +++ b/platform/ios/Classes/MuTextSelectView.m @@ -0,0 +1,111 @@ +// +// MuTextSelectView.m +// MuPDF +// +// Copyright (c) 2013 Artifex Software, Inc. All rights reserved. +// + +#include "common.h" +#import "MuTextSelectView.h" +#import "MuWord.h" + +@implementation MuTextSelectView + +- (id) initWithWords:(NSArray *)_words pageSize:(CGSize)_pageSize +{ + self = [super initWithFrame:CGRectMake(0,0,100,100)]; + if (self) + { + [self setOpaque:NO]; + words = [_words retain]; + pageSize = _pageSize; + color = [[UIColor colorWithRed:0x25/255.0 green:0x72/255.0 blue:0xAC/255.0 alpha:0.5] retain]; + UIPanGestureRecognizer *rec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onDrag:)]; + [self addGestureRecognizer:rec]; + [rec release]; + } + return self; +} + +-(void) dealloc +{ + [words release]; + [color release]; + [super dealloc]; +} + +- (NSArray *) selectionRects +{ + NSMutableArray *arr = [NSMutableArray array]; + __block CGRect r; + + [MuWord selectFrom:start to:end fromWords:words + onStartLine:^{ + r = CGRectNull; + } onWord:^(MuWord *w) { + r = CGRectUnion(r, w.rect); + } onEndLine:^{ + if (!CGRectIsNull(r)) + [arr addObject:[NSValue valueWithCGRect:r]]; + }]; + + return arr; +} + +- (NSString *) selectedText +{ + __block NSMutableString *text = [NSMutableString string]; + __block NSMutableString *line; + + [MuWord selectFrom:start to:end fromWords:words + onStartLine:^{ + line = [NSMutableString string]; + } onWord:^(MuWord *w) { + if (line.length > 0) + [line appendString:@" "]; + [line appendString:w.string]; + } onEndLine:^{ + if (text.length > 0) + [text appendString:@"\n"]; + [text appendString:line]; + }]; + + return text; +} + +-(void) onDrag:(UIPanGestureRecognizer *)rec +{ + CGSize scale = fitPageToScreen(pageSize, self.bounds.size); + CGPoint p = [rec locationInView:self]; + p.x /= scale.width; + p.y /= scale.height; + + if (rec.state == UIGestureRecognizerStateBegan) + start = p; + + end = p; + + [self setNeedsDisplay]; +} + +- (void) drawRect:(CGRect)rect +{ + CGSize scale = fitPageToScreen(pageSize, self.bounds.size); + CGContextRef cref = UIGraphicsGetCurrentContext(); + CGContextScaleCTM(cref, scale.width, scale.height); + __block CGRect r; + + [color set]; + + [MuWord selectFrom:start to:end fromWords:words + onStartLine:^{ + r = CGRectNull; + } onWord:^(MuWord *w) { + r = CGRectUnion(r, w.rect); + } onEndLine:^{ + if (!CGRectIsNull(r)) + UIRectFill(r); + }]; +} + +@end diff --git a/platform/ios/Classes/MuWord.h b/platform/ios/Classes/MuWord.h new file mode 100644 index 00000000..0087df1e --- /dev/null +++ b/platform/ios/Classes/MuWord.h @@ -0,0 +1,21 @@ +// +// MuWord.h +// MuPDF +// +// Copyright (c) 2013 Artifex Software, Inc. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import <UIKit/UIKit.h> + +@interface MuWord : NSObject +{ + NSMutableString *string; + CGRect rect; +} +@property(retain) NSString *string; +@property(assign) CGRect rect; ++ (MuWord *) word; +- (void) appendChar:(unichar)c withRect:(CGRect)rect; ++ (void) selectFrom:(CGPoint)pt1 to:(CGPoint)pt2 fromWords:(NSArray *)words onStartLine:(void (^)(void))startBlock onWord:(void (^)(MuWord *))wordBlock onEndLine:(void (^)(void))endBLock; +@end diff --git a/platform/ios/Classes/MuWord.m b/platform/ios/Classes/MuWord.m new file mode 100644 index 00000000..6ea10224 --- /dev/null +++ b/platform/ios/Classes/MuWord.m @@ -0,0 +1,100 @@ +// +// MuWord.m +// MuPDF +// +// Copyright (c) 2013 Artifex Software, Inc. All rights reserved. +// + +#import "MuWord.h" + +@implementation MuWord + +@synthesize string, rect; + +- (id) init +{ + self = [super init]; + if (self) + { + self.string = [NSMutableString string]; + self.rect = CGRectNull; + } + return self; +} + +- (void) dealloc +{ + self.string = nil; + [super dealloc]; +} + ++ (MuWord *) word +{ + return [[[MuWord alloc] init] autorelease]; +} + +- (void) appendChar:(unichar)c withRect:(CGRect)_rect +{ + [string appendFormat:@"%C", c]; + rect = CGRectUnion(rect, _rect); +} + ++ (void) selectFrom:(CGPoint)pt1 to:(CGPoint)pt2 fromWords:(NSArray *)words onStartLine:(void (^)(void))startBlock onWord:(void (^)(MuWord *))wordBlock onEndLine:(void (^)(void))endBLock +{ + CGPoint toppt, botpt; + + if (pt1.y < pt2.y) + { + toppt = pt1; + botpt = pt2; + } + else + { + toppt = pt2; + botpt = pt1; + } + + for (NSArray *line in words) + { + MuWord *fst = [line objectAtIndex:0]; + float ltop = fst.rect.origin.y; + float lbot = ltop + fst.rect.size.height; + + if (toppt.y < lbot && ltop < botpt.y) + { + BOOL topline = toppt.y > ltop; + BOOL botline = botpt.y < lbot; + float left = -INFINITY; + float right = INFINITY; + + if (topline && botline) + { + left = MIN(toppt.x, botpt.x); + right = MAX(toppt.x, botpt.x); + } + else if (topline) + { + left = toppt.x; + } + else if (botline) + { + right = botpt.x; + } + + startBlock(); + + for (MuWord *word in line) + { + float wleft = word.rect.origin.x; + float wright = wleft + word.rect.size.width; + + if (wright > left && wleft < right) + wordBlock(word); + } + + endBLock(); + } + } +} + +@end diff --git a/platform/ios/MuPDF.xcodeproj/project.pbxproj b/platform/ios/MuPDF.xcodeproj/project.pbxproj index 520a1e18..636b6635 100644 --- a/platform/ios/MuPDF.xcodeproj/project.pbxproj +++ b/platform/ios/MuPDF.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ DA1C68C417E8969C0061F586 /* common.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1C68C317E8969C0061F586 /* common.m */; }; DAA6E56F17F03F96002B1E4E /* MuPageViewReflow.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA6E56E17F03AAE002B1E4E /* MuPageViewReflow.m */; }; DAAC6A2E17FC6E4A00A0E83A /* MuTapResult.m in Sources */ = {isa = PBXBuildFile; fileRef = DAAC6A2D17FC6E4A00A0E83A /* MuTapResult.m */; }; + DAB067A61831225D00DDA774 /* MuWord.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB067A51831225D00DDA774 /* MuWord.m */; }; DAB20D4518042D830030623F /* MuTextFieldController.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB20D4318042D830030623F /* MuTextFieldController.m */; }; DAB20D4618042D830030623F /* MuTextFieldController.xib in Resources */ = {isa = PBXBuildFile; fileRef = DAB20D4418042D830030623F /* MuTextFieldController.xib */; }; DAB9F4BB17F1B24000117D2E /* ic_arrow_left.png in Resources */ = {isa = PBXBuildFile; fileRef = DAB9F4B517F1B24000117D2E /* ic_arrow_left.png */; }; @@ -49,6 +50,12 @@ DABDEF5A17EC484A00AC35F1 /* MuDocRef.m in Sources */ = {isa = PBXBuildFile; fileRef = DABDEF5917EC484A00AC35F1 /* MuDocRef.m */; }; DABF8CC3180C28E60069CB6D /* MuChoiceFieldController.xib in Resources */ = {isa = PBXBuildFile; fileRef = DABF8CC2180C28E60069CB6D /* MuChoiceFieldController.xib */; }; DABF8CC6180C2C650069CB6D /* MuChoiceFieldController.m in Sources */ = {isa = PBXBuildFile; fileRef = DABF8CC5180C2C640069CB6D /* MuChoiceFieldController.m */; }; + DACD12271833CDA600D4B9C5 /* ic_check.png in Resources */ = {isa = PBXBuildFile; fileRef = DACD12261833CDA600D4B9C5 /* ic_check.png */; }; + DAD2BA821835064E00E7C49D /* MuTextSelectView.m in Sources */ = {isa = PBXBuildFile; fileRef = DAD2BA811835064E00E7C49D /* MuTextSelectView.m */; }; + DAD47D2A1832475C00E173A0 /* ic_more.png in Resources */ = {isa = PBXBuildFile; fileRef = DAD47D291832475C00E173A0 /* ic_more.png */; }; + DAD47D2E1832615900E173A0 /* ic_highlight.png in Resources */ = {isa = PBXBuildFile; fileRef = DAD47D2B1832615900E173A0 /* ic_highlight.png */; }; + DAD47D2F1832615900E173A0 /* ic_strike.png in Resources */ = {isa = PBXBuildFile; fileRef = DAD47D2C1832615900E173A0 /* ic_strike.png */; }; + DAD47D301832615900E173A0 /* ic_underline.png in Resources */ = {isa = PBXBuildFile; fileRef = DAD47D2D1832615900E173A0 /* ic_underline.png */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -103,6 +110,8 @@ DAA6E56E17F03AAE002B1E4E /* MuPageViewReflow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MuPageViewReflow.m; path = Classes/MuPageViewReflow.m; sourceTree = "<group>"; }; DAAC6A2C17FC6E4A00A0E83A /* MuTapResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MuTapResult.h; path = Classes/MuTapResult.h; sourceTree = "<group>"; }; DAAC6A2D17FC6E4A00A0E83A /* MuTapResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MuTapResult.m; path = Classes/MuTapResult.m; sourceTree = "<group>"; }; + DAB067A41831225D00DDA774 /* MuWord.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MuWord.h; path = Classes/MuWord.h; sourceTree = "<group>"; }; + DAB067A51831225D00DDA774 /* MuWord.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MuWord.m; path = Classes/MuWord.m; sourceTree = "<group>"; }; DAB20D4218042D830030623F /* MuTextFieldController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MuTextFieldController.h; path = Classes/MuTextFieldController.h; sourceTree = "<group>"; }; DAB20D4318042D830030623F /* MuTextFieldController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MuTextFieldController.m; path = Classes/MuTextFieldController.m; sourceTree = "<group>"; }; DAB20D4418042D830030623F /* MuTextFieldController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = MuTextFieldController.xib; path = Classes/MuTextFieldController.xib; sourceTree = "<group>"; }; @@ -119,6 +128,13 @@ DABF8CC2180C28E60069CB6D /* MuChoiceFieldController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = MuChoiceFieldController.xib; path = Classes/MuChoiceFieldController.xib; sourceTree = "<group>"; }; DABF8CC4180C2C640069CB6D /* MuChoiceFieldController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MuChoiceFieldController.h; path = Classes/MuChoiceFieldController.h; sourceTree = "<group>"; }; DABF8CC5180C2C640069CB6D /* MuChoiceFieldController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MuChoiceFieldController.m; path = Classes/MuChoiceFieldController.m; sourceTree = "<group>"; }; + DACD12261833CDA600D4B9C5 /* ic_check.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = ic_check.png; path = "../android/res/drawable-ldpi/ic_check.png"; sourceTree = "<group>"; }; + DAD2BA801835064E00E7C49D /* MuTextSelectView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MuTextSelectView.h; path = Classes/MuTextSelectView.h; sourceTree = "<group>"; }; + DAD2BA811835064E00E7C49D /* MuTextSelectView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MuTextSelectView.m; path = Classes/MuTextSelectView.m; sourceTree = "<group>"; }; + DAD47D291832475C00E173A0 /* ic_more.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = ic_more.png; path = "../android/res/drawable-ldpi/ic_more.png"; sourceTree = "<group>"; }; + DAD47D2B1832615900E173A0 /* ic_highlight.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = ic_highlight.png; path = "../android/res/drawable-ldpi/ic_highlight.png"; sourceTree = "<group>"; }; + DAD47D2C1832615900E173A0 /* ic_strike.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = ic_strike.png; path = "../android/res/drawable-ldpi/ic_strike.png"; sourceTree = "<group>"; }; + DAD47D2D1832615900E173A0 /* ic_underline.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = ic_underline.png; path = "../android/res/drawable-ldpi/ic_underline.png"; sourceTree = "<group>"; }; DADD8D6917EB24C000C49E0B /* MuPageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MuPageView.h; path = Classes/MuPageView.h; sourceTree = "<group>"; }; /* End PBXFileReference section */ @@ -184,6 +200,11 @@ 968E1E461779A54F0050CEA3 /* Resources */ = { isa = PBXGroup; children = ( + DACD12261833CDA600D4B9C5 /* ic_check.png */, + DAD47D2B1832615900E173A0 /* ic_highlight.png */, + DAD47D2C1832615900E173A0 /* ic_strike.png */, + DAD47D2D1832615900E173A0 /* ic_underline.png */, + DAD47D291832475C00E173A0 /* ic_more.png */, DAB9F4C117F1CB6D00117D2E /* ic_reflow.png */, DAB9F4B517F1B24000117D2E /* ic_arrow_left.png */, DAB9F4B617F1B24000117D2E /* ic_arrow_right.png */, @@ -215,6 +236,10 @@ DA1C689D17E861020061F586 /* MuOutlineController.m */, DA1C689E17E861870061F586 /* MuHitView.h */, DA1C689F17E861870061F586 /* MuHitView.m */, + DAD2BA801835064E00E7C49D /* MuTextSelectView.h */, + DAD2BA811835064E00E7C49D /* MuTextSelectView.m */, + DAB067A41831225D00DDA774 /* MuWord.h */, + DAB067A51831225D00DDA774 /* MuWord.m */, DA1C68A417E863C70061F586 /* MuPageViewNormal.h */, DA1C68A517E863C70061F586 /* MuPageViewNormal.m */, DA1C68A617E864180061F586 /* MuDocumentController.h */, @@ -330,6 +355,11 @@ DAB9F4C217F1CB6D00117D2E /* ic_reflow.png in Resources */, DAB20D4618042D830030623F /* MuTextFieldController.xib in Resources */, DABF8CC3180C28E60069CB6D /* MuChoiceFieldController.xib in Resources */, + DAD47D2A1832475C00E173A0 /* ic_more.png in Resources */, + DAD47D2E1832615900E173A0 /* ic_highlight.png in Resources */, + DAD47D2F1832615900E173A0 /* ic_strike.png in Resources */, + DAD47D301832615900E173A0 /* ic_underline.png in Resources */, + DACD12271833CDA600D4B9C5 /* ic_check.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -376,6 +406,8 @@ DAAC6A2E17FC6E4A00A0E83A /* MuTapResult.m in Sources */, DAB20D4518042D830030623F /* MuTextFieldController.m in Sources */, DABF8CC6180C2C650069CB6D /* MuChoiceFieldController.m in Sources */, + DAB067A61831225D00DDA774 /* MuWord.m in Sources */, + DAD2BA821835064E00E7C49D /* MuTextSelectView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; |