summaryrefslogtreecommitdiff
path: root/platform/ios
diff options
context:
space:
mode:
authorPaul Gardiner <paul.gardiner@artifex.com>2013-11-14 15:24:53 +0000
committerPaul Gardiner <paul.gardiner@artifex.com>2013-11-19 10:18:28 +0000
commit6f532f78076d2bb24a8c6cf47c0f45994fa5b707 (patch)
tree870193262147eba8f35d06f3fd72ffba8369ebee /platform/ios
parente4c8dcc641b9cd3c7645b034b6f632e0965a059b (diff)
downloadmupdf-6f532f78076d2bb24a8c6cf47c0f45994fa5b707.tar.xz
iOS: add support for creating markup annotations
Diffstat (limited to 'platform/ios')
-rw-r--r--platform/ios/Classes/MuDocumentController.h16
-rw-r--r--platform/ios/Classes/MuDocumentController.m118
-rw-r--r--platform/ios/Classes/MuPageView.h3
-rw-r--r--platform/ios/Classes/MuPageViewNormal.h2
-rw-r--r--platform/ios/Classes/MuPageViewNormal.m243
-rw-r--r--platform/ios/Classes/MuPageViewReflow.m4
-rw-r--r--platform/ios/Classes/MuTextSelectView.h21
-rw-r--r--platform/ios/Classes/MuTextSelectView.m111
-rw-r--r--platform/ios/Classes/MuWord.h21
-rw-r--r--platform/ios/Classes/MuWord.m100
-rw-r--r--platform/ios/MuPDF.xcodeproj/project.pbxproj32
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;
};