Skip to content

Commit

Permalink
Distinguish separate search hit results.
Browse files Browse the repository at this point in the history
Add a new optional argument to mark search hits to all of the search functions.

With the marker each individual hit on the page can be identified. A quad that
starts a new array has a matching 1 in the 'hits' array. Each continuation quad
is marked with a 0 instead.

This is the minimally invasive API change, it just adds an optional extra int
array for recording the hit markers.

The Java and mutool run APIs have been changed to return an array of array of
quads for the search results.
  • Loading branch information
ccxvii committed Dec 8, 2021
1 parent 115a027 commit 2a951a4
Show file tree
Hide file tree
Showing 21 changed files with 205 additions and 66 deletions.
29 changes: 29 additions & 0 deletions docs/api/changes.html
Expand Up @@ -39,6 +39,35 @@ <h2>Why does the API change?</h2>
Note, that we only list changes in existing APIs here, not all additions
to the API.

<h2>Changes from 1.19</h2>

<p>
The search API has been extended with one optional parameter: an integer array.
If the 'hit_mark' array is provided it must be the same length as the quad array.
Each entry corresponds to the quad of the same index.
It will be filled in with 1 for each quad that starts a new search hit, and 0 for
each quad that is a continuation of a search hit
(for example when a match spans a line-break).

<ul>
<li>int fz_search_stext_page(ctx, text, needle, <i>int *hit_mark</i>, fz_quad *hit_bbox, int hit_max);
<li>int fz_search_display_list(ctx, list, needle, <i>int *hit_mark</i>, fz_quad *hit_bbox, int hit_max);
<li>int fz_search_page(ctx, page, needle, <i>int *hit_mark</i>, fz_quad *hit_bbox, int hit_max);
<li>int fz_search_page_number(ctx, doc, page, needle, <i>int *hit_mark</i>, fz_quad *hit_bbox, int hit_max);
<li>int fz_search_chapter_page_number(ctx, doc, c, p, needle, <i>int *hit_mark</i>, fz_quad *hit_bbox, int hit_max);
</ul>

<p>
The Java search functions have also changed.
They now return an array of array of quads instead of an array of quads.

<ul>
<li>Quad[][] StructuredText.search(String needle);
<li>Quad[][] DisplayList.search(String needle);
<li>Quad[][] Page.search(String needle);
<li>Quad[][] Document.search(int chapter, int page, String needle);
</ul>

<h2>Changes from 1.18 to 1.19</h2>

<p>We were inconsistent with the behaviour of fz_create_ and pdf_create_
Expand Down
2 changes: 1 addition & 1 deletion include/mupdf/fitz/structured-text.h
Expand Up @@ -244,7 +244,7 @@ void fz_print_stext_page_as_text(fz_context *ctx, fz_output *out, fz_stext_page
NOTE: This is an experimental interface and subject to change
without notice.
*/
int fz_search_stext_page(fz_context *ctx, fz_stext_page *text, const char *needle, fz_quad *quads, int max_quads);
int fz_search_stext_page(fz_context *ctx, fz_stext_page *text, const char *needle, int *hit_mark, fz_quad *hit_bbox, int hit_max);

/**
Return a list of quads to highlight lines inside the selection
Expand Down
8 changes: 4 additions & 4 deletions include/mupdf/fitz/util.h
Expand Up @@ -95,10 +95,10 @@ fz_buffer *fz_new_buffer_from_display_list(fz_context *ctx, fz_display_list *lis
Record the hits in the hit_bbox array and return the number of
hits. Will stop looking once it has filled hit_max rectangles.
*/
int fz_search_page(fz_context *ctx, fz_page *page, const char *needle, fz_quad *hit_bbox, int hit_max);
int fz_search_page_number(fz_context *ctx, fz_document *doc, int number, const char *needle, fz_quad *hit_bbox, int hit_max);
int fz_search_chapter_page_number(fz_context *ctx, fz_document *doc, int chapter, int page, const char *needle, fz_quad *hit_bbox, int hit_max);
int fz_search_display_list(fz_context *ctx, fz_display_list *list, const char *needle, fz_quad *hit_bbox, int hit_max);
int fz_search_page(fz_context *ctx, fz_page *page, const char *needle, int *hit_mark, fz_quad *hit_bbox, int hit_max);
int fz_search_page_number(fz_context *ctx, fz_document *doc, int number, const char *needle, int *hit_mark, fz_quad *hit_bbox, int hit_max);
int fz_search_chapter_page_number(fz_context *ctx, fz_document *doc, int chapter, int page, const char *needle, int *hit_mark, fz_quad *hit_bbox, int hit_max);
int fz_search_display_list(fz_context *ctx, fz_display_list *list, const char *needle, int *hit_mark, fz_quad *hit_bbox, int hit_max);

/**
Parse an SVG document into a display-list.
Expand Down
2 changes: 1 addition & 1 deletion platform/gl/gl-annotate.c
Expand Up @@ -1299,7 +1299,7 @@ static int mark_search_step(int cancel)
return -1;
}

count = fz_search_page_number(ctx, (fz_document*)pdf, rds_state.i-1, search_needle, quads, nelem(quads));
count = fz_search_page_number(ctx, (fz_document*)pdf, rds_state.i-1, search_needle, NULL, quads, nelem(quads));
if (count > 0)
{
pdf_page *page = pdf_load_page(ctx, pdf, rds_state.i-1);
Expand Down
2 changes: 1 addition & 1 deletion platform/gl/gl-main.c
Expand Up @@ -2534,7 +2534,7 @@ void do_main(void)
search_hit_count = fz_search_chapter_page_number(ctx, doc,
search_page.chapter, search_page.page,
search_needle,
search_hit_quads, nelem(search_hit_quads));
NULL, search_hit_quads, nelem(search_hit_quads));
trace_action("hits = doc.loadPage(%d).search(%q);\n", fz_page_number_from_location(ctx, doc, search_page), search_needle);
trace_action("print('Search page %d:', repr(%q), hits.length, repr(hits));\n", fz_page_number_from_location(ctx, doc, search_page), search_needle);
if (search_hit_count)
Expand Down
10 changes: 6 additions & 4 deletions platform/java/example/Viewer.java
Expand Up @@ -61,7 +61,7 @@ public class Viewer extends Frame implements WindowListener, ActionListener, Ite
protected int searchDirection = 1;
protected Location searchPage = null;
protected Location searchHitPage = null;
protected Quad[] searchHits = new Quad[0];
protected Quad[][] searchHits = new Quad[0][];
protected EventQueue eq = null;
protected SearchTask searchTask = null;

Expand Down Expand Up @@ -224,8 +224,10 @@ public void paint(Graphics g) {
if (currentPage.equals(searchHitPage) && searchHits != null) {
g2d.setColor(new Color(1, 0, 0, 0.4f));
for (int i = 0; i < searchHits.length; ++i) {
Rect hit = new Rect(searchHits[i]).transform(pageCTM);
g2d.fillRect((int)hit.x0, (int)hit.y0, (int)(hit.x1-hit.x0), (int)(hit.y1-hit.y0));
for (int k = 0; k < searchHits[i].length; ++k) {
Rect hit = new Rect(searchHits[i][k]).transform(pageCTM);
g2d.fillRect((int)hit.x0, (int)hit.y0, (int)(hit.x1-hit.x0), (int)(hit.y1-hit.y0));
}
}
}

Expand Down Expand Up @@ -763,7 +765,7 @@ public void run() {
while (!done && !isCancelled() && System.currentTimeMillis() < executionUntil) {
searchHits = doc.search(searchPage.chapter, searchPage.page, needle);
if (searchHits != null && searchHits.length > 0) {
searchHitPage = new Location(searchPage, searchHits[0].ul_x, searchHits[0].ul_y);
searchHitPage = new Location(searchPage, searchHits[0][0].ul_x, searchHits[0][0].ul_y);
jumpToLocation(searchHitPage);
done = true;
}
Expand Down
7 changes: 4 additions & 3 deletions platform/java/jni/displaylist.c
Expand Up @@ -139,7 +139,8 @@ FUN(DisplayList_search)(JNIEnv *env, jobject self, jstring jneedle)
{
fz_context *ctx = get_context(env);
fz_display_list *list = from_DisplayList(env, self);
fz_quad hits[256];
fz_quad hits[500];
int marks[500];
const char *needle = NULL;
int n = 0;

Expand All @@ -150,11 +151,11 @@ FUN(DisplayList_search)(JNIEnv *env, jobject self, jstring jneedle)
if (!needle) return NULL;

fz_try(ctx)
n = fz_search_display_list(ctx, list, needle, hits, nelem(hits));
n = fz_search_display_list(ctx, list, needle, marks, hits, nelem(hits));
fz_always(ctx)
(*env)->ReleaseStringUTFChars(env, jneedle, needle);
fz_catch(ctx)
jni_rethrow(env, ctx);

return to_QuadArray_safe(ctx, env, hits, n);
return to_SearchHits_safe(ctx, env, marks, hits, n);
}
7 changes: 4 additions & 3 deletions platform/java/jni/document.c
Expand Up @@ -1006,7 +1006,8 @@ FUN(Document_search)(JNIEnv *env, jobject self, jint chapter, jint page, jstring
{
fz_context *ctx = get_context(env);
fz_document *doc = from_Document(env, self);
fz_quad hits[256];
fz_quad hits[500];
int marks[500];
const char *needle = NULL;
int n = 0;

Expand All @@ -1017,11 +1018,11 @@ FUN(Document_search)(JNIEnv *env, jobject self, jint chapter, jint page, jstring
if (!needle) return 0;

fz_try(ctx)
n = fz_search_chapter_page_number(ctx, doc, chapter, page, needle, hits, nelem(hits));
n = fz_search_chapter_page_number(ctx, doc, chapter, page, needle, marks, hits, nelem(hits));
fz_always(ctx)
(*env)->ReleaseStringUTFChars(env, jneedle, needle);
fz_catch(ctx)
jni_rethrow(env, ctx);

return to_QuadArray_safe(ctx, env, hits, n);
return to_SearchHits_safe(ctx, env, marks, hits, n);
}
7 changes: 4 additions & 3 deletions platform/java/jni/page.c
Expand Up @@ -267,7 +267,8 @@ FUN(Page_search)(JNIEnv *env, jobject self, jstring jneedle)
{
fz_context *ctx = get_context(env);
fz_page *page = from_Page(env, self);
fz_quad hits[256];
fz_quad hits[500];
int marks[500];
const char *needle = NULL;
int n = 0;

Expand All @@ -278,13 +279,13 @@ FUN(Page_search)(JNIEnv *env, jobject self, jstring jneedle)
if (!needle) return 0;

fz_try(ctx)
n = fz_search_page(ctx, page, needle, hits, nelem(hits));
n = fz_search_page(ctx, page, needle, marks, hits, nelem(hits));
fz_always(ctx)
(*env)->ReleaseStringUTFChars(env, jneedle, needle);
fz_catch(ctx)
jni_rethrow(env, ctx);

return to_QuadArray_safe(ctx, env, hits, n);
return to_SearchHits_safe(ctx, env, marks, hits, n);
}

JNIEXPORT jobject JNICALL
Expand Down
7 changes: 4 additions & 3 deletions platform/java/jni/structuredtext.c
Expand Up @@ -37,7 +37,8 @@ FUN(StructuredText_search)(JNIEnv *env, jobject self, jstring jneedle)
{
fz_context *ctx = get_context(env);
fz_stext_page *text = from_StructuredText(env, self);
fz_quad hits[256];
fz_quad hits[500];
int marks[500];
const char *needle = NULL;
int n = 0;

Expand All @@ -48,13 +49,13 @@ FUN(StructuredText_search)(JNIEnv *env, jobject self, jstring jneedle)
if (!needle) return NULL;

fz_try(ctx)
n = fz_search_stext_page(ctx, text, needle, hits, nelem(hits));
n = fz_search_stext_page(ctx, text, needle, marks, hits, nelem(hits));
fz_always(ctx)
(*env)->ReleaseStringUTFChars(env, jneedle, needle);
fz_catch(ctx)
jni_rethrow(env, ctx);

return to_QuadArray_safe(ctx, env, hits, n);
return to_SearchHits_safe(ctx, env, marks, hits, n);
}

JNIEXPORT jobject JNICALL
Expand Down
59 changes: 59 additions & 0 deletions platform/java/jni/wrap.c
Expand Up @@ -403,6 +403,65 @@ static inline jobjectArray to_QuadArray_safe(fz_context *ctx, JNIEnv *env, const
return arr;
}

static int count_next_hits(const int *marks, int a, int end)
{
int b = a + 1;
while (b < end && !marks[b])
++b;
return b - a;
}

/* Array of Array of Quad */
static inline jobjectArray to_SearchHits_safe(fz_context *ctx, JNIEnv *env, const int *marks, const fz_quad *quads, jint n)
{
jobjectArray toparr, arr;
int i, k, a, m;

if (!ctx || !marks || !quads)
return NULL;

/* Count total number of search hits */
i = a = 0;
while (a < n)
{
a += count_next_hits(marks, a, n);
++i;
}

toparr = (*env)->NewObjectArray(env, i, cls_ArrayOfQuad, NULL);
if (!toparr || (*env)->ExceptionCheck(env))
return NULL;

i = a = 0;
while (a < n)
{
m = count_next_hits(marks, a, n);

arr = (*env)->NewObjectArray(env, m, cls_Quad, NULL);
if (!arr || (*env)->ExceptionCheck(env))
return NULL;
(*env)->SetObjectArrayElement(env, toparr, i++, arr);
if ((*env)->ExceptionCheck(env))
return NULL;

for (k = 0; k < m; ++k) {
jobject jquad = to_Quad_safe(ctx, env, quads[a+k]);
if (!jquad || (*env)->ExceptionCheck(env))
return NULL;
(*env)->SetObjectArrayElement(env, arr, k, jquad);
if ((*env)->ExceptionCheck(env))
return NULL;
(*env)->DeleteLocalRef(env, jquad);
}

(*env)->DeleteLocalRef(env, arr);

a += m;
}

return toparr;
}

static inline jobject to_Rect_safe(fz_context *ctx, JNIEnv *env, fz_rect rect)
{
if (!ctx) return NULL;
Expand Down
4 changes: 4 additions & 0 deletions platform/java/mupdf_native.c
Expand Up @@ -87,6 +87,7 @@ static JavaVM *jvm = NULL;

/* All the cached classes/mids/fids we need. */

static jclass cls_ArrayOfQuad;
static jclass cls_Buffer;
static jclass cls_ColorSpace;
static jclass cls_Context;
Expand Down Expand Up @@ -938,6 +939,8 @@ static int find_fids(JNIEnv *env)
fid_Quad_lr_y = get_field(&err, env, "lr_y", "F");
mid_Quad_init = get_method(&err, env, "<init>", "(FFFFFFFF)V");

cls_ArrayOfQuad = get_class(&err, env, "[L"PKG"Quad;");

cls_Rect = get_class(&err, env, PKG"Rect");
fid_Rect_x0 = get_field(&err, env, "x0", "F");
fid_Rect_x1 = get_field(&err, env, "x1", "F");
Expand Down Expand Up @@ -1090,6 +1093,7 @@ static void jni_detach_thread(jboolean detach)

static void lose_fids(JNIEnv *env)
{
(*env)->DeleteGlobalRef(env, cls_ArrayOfQuad);
(*env)->DeleteGlobalRef(env, cls_Buffer);
(*env)->DeleteGlobalRef(env, cls_ColorSpace);
(*env)->DeleteGlobalRef(env, cls_Context);
Expand Down
8 changes: 4 additions & 4 deletions platform/java/mupdf_native.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion platform/java/src/com/artifex/mupdf/fitz/DisplayList.java
Expand Up @@ -53,7 +53,7 @@ public StructuredText toStructuredText() {
return toStructuredText(null);
}

public native Quad[] search(String needle);
public native Quad[][] search(String needle);

public native void run(Device dev, Matrix ctm, Rect scissor, Cookie cookie);

Expand Down
2 changes: 1 addition & 1 deletion platform/java/src/com/artifex/mupdf/fitz/Document.java
Expand Up @@ -191,7 +191,7 @@ public int pageNumberFromLocation(Location loc) {
return -1;
}

public native Quad[] search(int chapter, int page, String needle);
public native Quad[][] search(int chapter, int page, String needle);

public native Location resolveLink(String uri);
public Location resolveLink(Outline link) {
Expand Down
2 changes: 1 addition & 1 deletion platform/java/src/com/artifex/mupdf/fitz/Page.java
Expand Up @@ -68,7 +68,7 @@ public StructuredText toStructuredText() {
return toStructuredText(null);
}

public native Quad[] search(String needle);
public native Quad[][] search(String needle);

public native byte[] textAsHtml();

Expand Down
Expand Up @@ -46,7 +46,7 @@ private StructuredText(long p) {
pointer = p;
}

public native Quad[] search(String needle);
public native Quad[][] search(String needle);
public native Quad[] highlight(Point a, Point b);
public native Quad snapSelection(Point a, Point b, int mode);
public native String copy(Point a, Point b);
Expand Down
2 changes: 1 addition & 1 deletion platform/x11/pdfapp.c
Expand Up @@ -1186,7 +1186,7 @@ static void pdfapp_search_in_direction(pdfapp_t *app, enum panning *panto, int d
pdfapp_showpage(app, 1, 0, 0, 0, 1);
}

app->hit_count = fz_search_stext_page(app->ctx, app->page_text, app->search, app->hit_bbox, nelem(app->hit_bbox));
app->hit_count = fz_search_stext_page(app->ctx, app->page_text, app->search, NULL, app->hit_bbox, nelem(app->hit_bbox));
if (app->hit_count > 0)
{
*panto = dir == 1 ? PAN_TO_TOP : PAN_TO_BOTTOM;
Expand Down

0 comments on commit 2a951a4

Please sign in to comment.