summaryrefslogtreecommitdiff
path: root/ios
diff options
context:
space:
mode:
authorTor Andersson <tor.andersson@artifex.com>2011-11-17 21:48:20 +0100
committerTor Andersson <tor.andersson@artifex.com>2011-11-22 16:52:38 +0100
commit6b166e7d69121aa321ec6173697c53c66fd17001 (patch)
tree58623aca7461d0db8d2093aa763e83b32cb88574 /ios
parent97ca1a0cbee19553bdc60bf18552738d6776da9f (diff)
downloadmupdf-6b166e7d69121aa321ec6173697c53c66fd17001.tar.xz
Display search results and search in background thread.
Diffstat (limited to 'ios')
-rw-r--r--ios/document.c25
-rw-r--r--ios/document.h3
-rw-r--r--ios/main.m242
3 files changed, 240 insertions, 30 deletions
diff --git a/ios/document.c b/ios/document.c
index 31e8cc57..9e46644c 100644
--- a/ios/document.c
+++ b/ios/document.c
@@ -209,24 +209,39 @@ match(fz_text_span *span, char *s, int n)
int
search_page(struct document *doc, int number, char *needle)
{
- int pos, len, count = 0;
+ int pos, len, i, n;
+
+ if (strlen(needle) == 0)
+ return 0;
+
fz_text_span *text = fz_new_text_span();
fz_device *dev = fz_new_text_device(text);
draw_page(doc, number, dev, fz_identity);
fz_free_device(dev);
+ doc->hit_count = 0;
+
len = textlen(text);
for (pos = 0; pos < len; pos++) {
- int n = match(text, needle, pos);
+ n = match(text, needle, pos);
if (n) {
- // TODO: extract bbox(es) into a result list
printf("found a match at page %d, pos %d!\n", number, pos);
- count++;
+ for (i = 0; i < n; i++) {
+ fz_bbox r = bboxat(text, pos + i);
+ if (!fz_is_empty_bbox(r) && doc->hit_count < nelem(doc->hit_bbox))
+ doc->hit_bbox[doc->hit_count++] = r;
+ }
}
}
fz_free_text_span(text);
- return count;
+ return doc->hit_count;
+}
+
+fz_bbox
+search_result_bbox(struct document *doc, int i)
+{
+ return doc->hit_bbox[i];
}
void
diff --git a/ios/document.h b/ios/document.h
index 00805a5d..95aa9703 100644
--- a/ios/document.h
+++ b/ios/document.h
@@ -20,6 +20,8 @@ struct document
int number;
pdf_page *pdf_page;
xps_page *xps_page;
+ fz_bbox hit_bbox[500];
+ int hit_count;
};
struct document *open_document(char *filename);
@@ -28,6 +30,7 @@ int count_pages(struct document *doc);
void measure_page(struct document *doc, int number, float *w, float *h);
void draw_page(struct document *doc, int number, fz_device *dev, fz_matrix ctm);
int search_page(struct document *doc, int number, char *needle);
+fz_bbox search_result_bbox(struct document *doc, int i);
void close_document(struct document *doc);
#endif
diff --git a/ios/main.m b/ios/main.m
index 5dbbbbc0..db6e1edd 100644
--- a/ios/main.m
+++ b/ios/main.m
@@ -12,7 +12,7 @@
#define GAP 20
#define INDICATOR_Y -44-24
-#define BUTTON_W (44*2+10-4)
+#define BUTTON_W (44*2+12-4)
#define SEARCH_W (width - GAP - BUTTON_W)
#define SLIDER_W (width - GAP - 24)
@@ -38,6 +38,16 @@ static float screenScale = 1;
- (id) initWithTarget: (id)aTarget titles: (NSMutableArray*)aTitles pages: (NSMutableArray*)aPages;
@end
+@interface MuHitView : UIView
+{
+ CGSize pageSize;
+ int hitCount;
+ CGRect hitRects[500];
+}
+- (id) initWithSearchResults: (int)n forDocument: (struct document *)doc;
+- (void) setPageSize: (CGSize)s;
+@end
+
@interface MuPageView : UIScrollView <UIScrollViewDelegate>
{
struct document *doc;
@@ -45,6 +55,8 @@ static float screenScale = 1;
UIActivityIndicatorView *loadingView;
UIImageView *imageView;
UIImageView *tileView;
+ MuHitView *hitView;
+ CGSize pageSize;
CGRect tileFrame;
float tileScale;
BOOL cancel;
@@ -56,6 +68,8 @@ static float screenScale = 1;
- (void) loadTile;
- (void) willRotate;
- (void) resetZoomAnimated: (BOOL)animated;
+- (void) showSearchResults: (int)count;
+- (void) clearSearchResults;
- (int) number;
@end
@@ -70,7 +84,8 @@ static float screenScale = 1;
UILabel *indicator;
UISlider *slider;
UISearchBar *searchBar;
- char *searchText;
+ UIBarButtonItem *nextButton, *prevButton;
+ int searchPage;
UINavigationBar *buttonBar;
UIBarButtonItem *sliderWrapper;
int width; // current screen size
@@ -84,6 +99,7 @@ static float screenScale = 1;
- (void) onShowOutline: (id)sender;
- (void) onToggleSearch: (id)sender;
- (void) resetSearch;
+- (void) showSearchResults: (int)count forPage: (int)number;
- (void) onSlide: (id)sender;
- (void) onTap: (UITapGestureRecognizer*)sender;
- (void) showNavigationBar;
@@ -194,6 +210,13 @@ static CGSize fitPageToScreen(CGSize page, CGSize screen)
return CGSizeMake(hscale, vscale);
}
+static CGSize measurePage(struct document *doc, int number)
+{
+ CGSize pageSize;
+ measure_page(doc, number, &pageSize.width, &pageSize.height);
+ return pageSize;
+}
+
static UIImage *renderPage(struct document *doc, int number, CGSize screenSize)
{
CGSize pageSize;
@@ -428,6 +451,51 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
#pragma mark -
+@implementation MuHitView
+
+- (id) initWithSearchResults: (int)n forDocument: (struct document *)doc
+{
+ self = [super init];
+ if (self) {
+ [self setOpaque: NO];
+
+ pageSize = CGSizeMake(1,1);
+
+ for (int i = 0; i < n && i < nelem(hitRects); i++) {
+ fz_bbox bbox = search_result_bbox(doc, i); // this is thread-safe enough
+ hitRects[i].origin.x = bbox.x0;
+ hitRects[i].origin.y = bbox.y0;
+ hitRects[i].size.width = bbox.x1 - bbox.x0;
+ hitRects[i].size.height = bbox.y1 - bbox.y0;
+ }
+ hitCount = n;
+ }
+ return self;
+}
+
+- (void) setPageSize: (CGSize)s
+{
+ pageSize = s;
+}
+
+- (void) drawRect: (CGRect)r
+{
+ CGSize scale = fitPageToScreen(pageSize, self.bounds.size);
+
+ [[UIColor colorWithRed: 0.3 green: 0.3 blue: 1 alpha: 0.5] set];
+
+ for (int i = 0; i < hitCount; i++) {
+ CGRect rect = hitRects[i];
+ rect.origin.x *= scale.width;
+ rect.origin.y *= scale.height;
+ rect.size.width *= scale.width;
+ rect.size.height *= scale.height;
+ UIRectFill(rect);
+ }
+}
+
+@end
+
@implementation MuPageView
- (id) initWithFrame: (CGRect)frame document: (struct document*)aDoc page: (int)aNumber
@@ -468,6 +536,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
__block id block_self = self; // don't auto-retain self!
dispatch_async(dispatch_get_main_queue(), ^{ [block_self dealloc]; });
} else {
+ [hitView release];
[tileView release];
[loadingView release];
[imageView release];
@@ -480,6 +549,30 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
return number;
}
+- (void) showSearchResults: (int)count
+{
+ if (hitView) {
+ [hitView removeFromSuperview];
+ [hitView release];
+ hitView = nil;
+ }
+ hitView = [[MuHitView alloc] initWithSearchResults: count forDocument: doc];
+ if (imageView) {
+ [hitView setFrame: [imageView frame]];
+ [hitView setPageSize: pageSize];
+ }
+ [self addSubview: hitView];
+}
+
+- (void) clearSearchResults
+{
+ if (hitView) {
+ [hitView removeFromSuperview];
+ [hitView release];
+ hitView = nil;
+ }
+}
+
- (void) resetZoomAnimated: (BOOL)animated
{
// discard tile and any pending tile jobs
@@ -509,8 +602,10 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
dispatch_async(queue, ^{
if (!cancel) {
printf("render page %d\n", number);
+ CGSize size = measurePage(doc, number);
UIImage *image = renderPage(doc, number, self.bounds.size);
dispatch_async(dispatch_get_main_queue(), ^{
+ pageSize = size;
[self displayImage: image];
[image release];
});
@@ -554,7 +649,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
CGSize scale = fitPageToScreen(imageView.image.size, self.bounds.size);
- if (fabs(scale.width - 1) > 0.1)
+ if (fabs(scale.width - 1) > 0.01)
[self loadPage];
});
});
@@ -564,8 +659,14 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
[self setContentSize: imageView.frame.size];
+ if (hitView) {
+ [hitView setPageSize: pageSize];
+ [self bringSubviewToFront: hitView];
+ }
+
[self layoutIfNeeded];
}
+
}
- (void) willRotate
@@ -601,6 +702,10 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
loadingView.frame = frameToCenter;
else
imageView.frame = frameToCenter;
+
+ if (hitView && imageView)
+ [hitView setFrame: [imageView frame]];
+
}
- (UIView*) viewForZoomingInScrollView: (UIScrollView*)scrollView
@@ -610,7 +715,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
- (void) loadTile
{
- CGSize pageSize = self.bounds.size;
+ CGSize screenSize = self.bounds.size;
tileFrame.origin = self.contentOffset;
tileFrame.size = self.bounds.size;
@@ -640,7 +745,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
}
printf("render tile\n");
- UIImage *image = renderTile(doc, number, pageSize, viewFrame, scale);
+ UIImage *image = renderTile(doc, number, screenSize, viewFrame, scale);
dispatch_async(dispatch_get_main_queue(), ^{
isValid = CGRectEqualToRect(frame, tileFrame) && scale == tileScale;
@@ -656,6 +761,8 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
tileView = [[UIImageView alloc] initWithFrame: frame];
[tileView setImage: image];
[self addSubview: tileView];
+ if (hitView)
+ [self bringSubviewToFront: hitView];
} else {
printf("discard tile\n");
}
@@ -690,6 +797,12 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
[self loadTile];
}
+- (void) scrollViewDidZoom: (UIScrollView*)scrollView
+{
+ if (hitView && imageView)
+ [hitView setFrame: [imageView frame]];
+}
+
@end
#pragma mark -
@@ -818,6 +931,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
searchBar = [[UISearchBar alloc] initWithFrame: CGRectMake(0,44,SEARCH_W,44)];
[searchBar setHidden: YES];
[searchBar setTranslucent: YES];
+ [searchBar setPlaceholder: @"Search"];
[searchBar setDelegate: self];
[view addSubview: searchBar];
@@ -830,16 +944,20 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
[buttonBar setTranslucent: YES];
[view addSubview: buttonBar];
- UIBarButtonItem *prevButton = [[UIBarButtonItem alloc]
+ prevButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem: UIBarButtonSystemItemRewind
target:self action:@selector(onSearchPrev:)];
+ nextButton = [[UIBarButtonItem alloc]
+ initWithBarButtonSystemItem: UIBarButtonSystemItemFastForward
+ target:self action:@selector(onSearchNext:)];
+
+ [prevButton setEnabled: NO];
+ [nextButton setEnabled: NO];
+
UIBarButtonItem *space = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem: UIBarButtonSystemItemFixedSpace
target:nil action:nil];
- [space setWidth: 10];
- UIBarButtonItem *nextButton = [[UIBarButtonItem alloc]
- initWithBarButtonSystemItem: UIBarButtonSystemItemFastForward
- target:self action:@selector(onSearchNext:)];
+ [space setWidth: 12];
// TODO: add activityindicator to search bar
@@ -848,9 +966,7 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
[buttonBar addSubview: rightBar];
[rightBar release];
- [prevButton release];
[space release];
- [nextButton release];
[self setView: view];
[view release];
@@ -864,6 +980,8 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
[slider release]; slider = nil;
[sliderWrapper release]; sliderWrapper = nil;
[searchBar release]; searchBar = nil;
+ [prevButton release]; prevButton = nil;
+ [nextButton release]; nextButton = nil;
[buttonBar release]; buttonBar = nil;
[canvas release]; canvas = nil;
}
@@ -877,7 +995,6 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
close_document(self_doc);
});
}
- free(searchText);
[outline release];
[key release];
[super dealloc];
@@ -1000,27 +1117,84 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
- (void) resetSearch
{
+ searchPage = -1;
+ for (MuPageView *view in [canvas subviews])
+ [view clearSearchResults];
+}
+
+- (void) showSearchResults: (int)count forPage: (int)number
+{
+ printf("search found %d matches on page %d\n", count, number);
+ searchPage = number;
+ [self gotoPage: number animated: NO];
+ for (MuPageView *view in [canvas subviews])
+ if ([view number] == number)
+ [view showSearchResults: count];
+ else
+ [view clearSearchResults];
+}
+
+- (void) searchInDirection: (int)dir
+{
+ UITextField *searchField;
+ char *needle;
+ int start;
+
[searchBar resignFirstResponder];
- free(searchText);
- searchText = strdup([[searchBar text] UTF8String]);
+
+ if (searchPage == current)
+ start = current + dir;
+ else
+ start = current;
+
+ needle = strdup([[searchBar text] UTF8String]);
+
+ searchField = nil;
+ for (id view in [searchBar subviews])
+ if ([view isKindOfClass: [UITextField class]])
+ searchField = view;
+
+ [prevButton setEnabled: NO];
+ [nextButton setEnabled: NO];
+
+ dispatch_async(queue, ^{
+ for (int i = start; i >= 0 && i < count_pages(doc); i += dir) {
+ int n = search_page(doc, i, needle);
+ if (n) {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ [prevButton setEnabled: YES];
+ [nextButton setEnabled: YES];
+ [self showSearchResults: n forPage: i]; // this is why we don't async
+ free(needle);
+ });
+ return;
+ }
+ }
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ printf("no search results found\n");
+ [prevButton setEnabled: YES];
+ [nextButton setEnabled: YES];
+ UIAlertView *alert = [[UIAlertView alloc]
+ initWithTitle: @"Search Completed"
+ message: @"No matches found"
+ delegate: nil
+ cancelButtonTitle: @"Okay"
+ otherButtonTitles: nil];
+ [alert show];
+ [alert release];
+ free(needle);
+ });
+ });
}
- (void) onSearchPrev: (id)sender
{
- [self resetSearch];
-
- dispatch_sync(queue, ^{});
- int n = search_page(doc, current, searchText);
- printf("search prev for '%s' found %d matches\n", searchText, n);
+ [self searchInDirection: -1];
}
- (void) onSearchNext: (id)sender
{
- [self resetSearch];
-
- dispatch_sync(queue, ^{});
- int n = search_page(doc, current, searchText);
- printf("search next for '%s' found %d matches\n", searchText, n);
+ [self searchInDirection: 1];
}
- (void) searchBarSearchButtonClicked: (UISearchBar*)sender
@@ -1028,6 +1202,18 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
[self onSearchNext: sender];
}
+- (void) searchBar: (UISearchBar*)sender textDidChange: (NSString*)searchText
+{
+ [self resetSearch];
+ if ([[searchBar text] length] > 0) {
+ [prevButton setEnabled: YES];
+ [nextButton setEnabled: YES];
+ } else {
+ [prevButton setEnabled: NO];
+ [nextButton setEnabled: NO];
+ }
+}
+
- (void) onSlide: (id)sender
{
int number = [slider value];
@@ -1094,6 +1280,10 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
[self createPageView: current];
[self createPageView: current - 1];
[self createPageView: current + 1];
+
+ // reset search results when page has flipped
+ if (current != searchPage)
+ [self resetSearch];
}
- (void) createPageView: (int)number
@@ -1118,6 +1308,8 @@ static UIImage *renderTile(struct document *doc, int number, CGSize screenSize,
number = 0;
if (number >= count_pages(doc))
number = count_pages(doc) - 1;
+ if (current == number)
+ return;
if (animated) {
// setContentOffset:animated: does not use the normal animation
// framework. It also doesn't play nice with the tap gesture