summaryrefslogtreecommitdiff
path: root/platform/ios/Classes
diff options
context:
space:
mode:
Diffstat (limited to 'platform/ios/Classes')
-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
10 files changed, 627 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