From e7fa2ff8f4763ae18d194e82943a68c75cb9d28b Mon Sep 17 00:00:00 2001 From: fred ross-perry Date: Mon, 8 Aug 2016 18:21:36 -0700 Subject: Android example : improved text selection. --- .../com/artifex/mupdf/android/DocPageView.java | 218 +++++++++++++++++---- .../java/com/artifex/mupdf/android/DocView.java | 71 +++---- .../com/artifex/mupdf/android/DocViewBase.java | 2 +- 3 files changed, 208 insertions(+), 83 deletions(-) (limited to 'platform') diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java index f6a4853f..536488f1 100755 --- a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocPageView.java @@ -26,6 +26,8 @@ import com.artifex.mupdf.fitz.R; import com.artifex.mupdf.fitz.StructuredText; import com.artifex.mupdf.fitz.android.AndroidDrawDevice; +import java.util.ArrayList; + public class DocPageView extends View implements Callback { private final Document mDoc; @@ -73,10 +75,8 @@ public class DocPageView extends View implements Callback // use this to control whether the blue dot is drawn in the upper left corner. private boolean isMostVisible = false; - /// structured text, defined once at page setup time. - StructuredText mStructuredText = null; - Rect mHighlightRects[] = null; - Rect hlRect = new Rect(); + // currently selected TextChars + ArrayList mSelection = null; public DocPageView(Context context, Document theDoc) { @@ -135,9 +135,6 @@ public class DocPageView extends View implements Callback mZoom = w / pagew; mSize = new Point((int) (pagew * mZoom), (int) (pageH * mZoom)); - - // get structured text - mStructuredText = getPage().toStructuredText(); } public Page getPage() @@ -380,30 +377,88 @@ public class DocPageView extends View implements Callback } } - public void setHighlight(Point ul, Point dr) + public Point getSelectionStart() { - // remember the rect we've been given - hlRect.set(ul.x, ul.y, dr.x, dr.y); + if (mSelection == null) + return null; + if (mSelection.size()==0) + return null; - // find the included text rects - com.artifex.mupdf.fitz.Rect rects[] = mStructuredText.highlight(new com.artifex.mupdf.fitz.Rect(ul.x, ul.y, dr.x, dr.y)); - if (rects == null || rects.length <= 0) - { - mHighlightRects = null; - return; - } + StructuredText.TextChar tchar = mSelection.get(0); + + return new Point((int)tchar.bbox.x0, (int)tchar.bbox.y0); + } + + public Point getSelectionEnd() + { + if (mSelection == null) + return null; + if (mSelection.size()==0) + return null; + + StructuredText.TextChar tchar = mSelection.get(mSelection.size()-1); + + return new Point((int)tchar.bbox.x1, (int)tchar.bbox.y1); + } + + public void setSelection(Point ul, Point dr) + { + mSelection = new ArrayList<>(); + + // get structured text and the block structure + StructuredText structuredText = getPage().toStructuredText(); + StructuredText.TextBlock textBlocks[] = structuredText.getBlocks(); - // convert to Android Rects. They will be used to draw the highlights. - mHighlightRects = new Rect[rects.length]; - for (int i = 0; i < rects.length; i++) + com.artifex.mupdf.fitz.Rect r = new com.artifex.mupdf.fitz.Rect(ul.x, ul.y, dr.x, dr.y); + for (StructuredText.TextBlock block : textBlocks) { - mHighlightRects[i] = new Rect((int) rects[i].x0, (int) rects[i].y0, (int) rects[i].x1, (int) rects[i].y1); + for (StructuredText.TextLine line : block.lines) + { + boolean firstLine = false; + boolean lastLine = false; + boolean middleLine = false; + if (line.bbox.contains(ul.x, ul.y)) + firstLine = true; + if (line.bbox.contains(dr.x, dr.y)) + lastLine = true; + if (line.bbox.y0 >= ul.y && line.bbox.y1 <= dr.y) + middleLine = true; + + for (StructuredText.TextSpan span : line.spans) + { + for (StructuredText.TextChar tchar : span.chars) + { + if (firstLine && lastLine) + { + if (tchar.bbox.x0 >= ul.x && tchar.bbox.x1 <= dr.x) + mSelection.add(tchar); + } + else if (firstLine) + { + if (tchar.bbox.x0 >= ul.x) + mSelection.add(tchar); + } + else if (lastLine) + { + if (tchar.bbox.x1 <= dr.x) + mSelection.add(tchar); + } + else if (middleLine) + { + mSelection.add(tchar); + } + } + } + } } + + invalidate(); } - public void removeHighlight() + public void removeSelection() { - mHighlightRects = null; + mSelection = new ArrayList<>(); + invalidate(); } @Override @@ -440,10 +495,11 @@ public class DocPageView extends View implements Callback canvas.drawBitmap(mDrawBitmap, mSrcRect, mDstRect, mPainter); // highlights - if (mHighlightRects != null) + if (mSelection != null && !mSelection.isEmpty()) { - for (Rect r : mHighlightRects) + for (StructuredText.TextChar tchar : mSelection) { + Rect r = new Rect((int) tchar.bbox.x0, (int) tchar.bbox.y0, (int) tchar.bbox.x1, (int) tchar.bbox.y1); Rect r2 = pageToView(r); canvas.drawRect(r2, mHighlightPainter); } @@ -458,30 +514,114 @@ public class DocPageView extends View implements Callback canvas.restore(); } - public Rect getTappedRect(Point p) + public Rect selectWord(Point p) { + // in page units Point pPage = screenToPage(p.x, p.y); - com.artifex.mupdf.fitz.Rect bounds = mPage.getBounds(); - com.artifex.mupdf.fitz.Rect rects[] = - mStructuredText.highlight(new com.artifex.mupdf.fitz.Rect(bounds.x0, bounds.y0, bounds.x1, bounds.y1)); - Rect rfound = null; - for (com.artifex.mupdf.fitz.Rect r : rects) + // get structured text and the block structure + StructuredText structuredText = getPage().toStructuredText(); + StructuredText.TextBlock textBlocks[] = structuredText.getBlocks(); + + StructuredText.TextBlock block = blockContainingPoint(textBlocks, pPage); + if (block == null) + return null; + + StructuredText.TextLine line = lineContainingPoint(block.lines, pPage); + if (line == null) + return null; + + StructuredText.TextSpan span = spanContainingPoint(line.spans, pPage); + if (span == null) + return null; + + // find the char containing my point + int n = -1; + int i; + for (i = 0; i < span.chars.length; i++) { - if (r.contains(pPage.x, pPage.y)) - rfound = new Rect((int) r.x0, (int) r.y0, (int) r.x1, (int) r.y1); + if (span.chars[i].bbox.contains(pPage.x, pPage.y)) + { + n = i; + break; + } } + // not found + if (n == -1) + return null; + // must be non-blank + if (isBlank(span.chars[n].c)) + return null; - return rfound; + // look forward for a space, or the end + int nEnd = n; + while (nEnd + 1 < span.chars.length && !isBlank(span.chars[nEnd + 1].c)) + nEnd++; + + // look backward for a space, or the beginning + int nStart = n; + while (nStart - 1 >= 0 && !isBlank(span.chars[nStart - 1].c)) + nStart--; + + mSelection = new ArrayList<>(); + com.artifex.mupdf.fitz.Rect rWord = new com.artifex.mupdf.fitz.Rect(); + for (i = nStart; i <= nEnd; i++) + { + mSelection.add(span.chars[i]); + rWord.union(span.chars[i].bbox); + } + + return new Rect((int) rWord.x0, (int) rWord.y0, (int) rWord.x1, (int) rWord.y1); + } + + private boolean isBlank(int c) + { + String s = Character.toString((char) c); + return s.trim().isEmpty(); } - public String getSelectedText() + private StructuredText.TextBlock blockContainingPoint(StructuredText.TextBlock blocks[], Point p) { - // convert to fitz rect - com.artifex.mupdf.fitz.Rect r = - new com.artifex.mupdf.fitz.Rect(hlRect.left, hlRect.top, hlRect.right, hlRect.bottom); + for (StructuredText.TextBlock block : blocks) + { + if (block.bbox.contains(p.x, p.y)) + return block; + } + + return null; + } + + private StructuredText.TextLine lineContainingPoint(StructuredText.TextLine lines[], Point p) + { + for (StructuredText.TextLine line : lines) + { + if (line.bbox.contains(p.x, p.y)) + return line; + } + + return null; + } + + private StructuredText.TextSpan spanContainingPoint(StructuredText.TextSpan spans[], Point p) + { + for (StructuredText.TextSpan span : spans) + { + if (span.bbox.contains(p.x, p.y)) + return span; + } + + return null; + } + + private StructuredText.TextChar charContainingPoint(StructuredText.TextChar chars[], Point p) + { + for (StructuredText.TextChar tchar : chars) + { + if (tchar.bbox.contains(p.x, p.y)) + return tchar; + } - return mStructuredText.copy(r); + return null; } public Point screenToPage(Point p) diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java index 71c0fbe6..c057da5c 100644 --- a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocView.java @@ -6,7 +6,6 @@ import android.graphics.Point; import android.graphics.Rect; import android.util.AttributeSet; import android.util.DisplayMetrics; -import android.util.Log; import android.util.TypedValue; import android.view.View; import android.widget.RelativeLayout; @@ -122,7 +121,7 @@ public class DocView extends DocViewBase implements DragHandleListener for (int i = 0; i < numPages; i++) { DocPageView cv = (DocPageView) getOrCreateChild(i); - cv.removeHighlight(); + cv.removeSelection(); if (cv.isReallyVisible()) cv.invalidate(); } @@ -130,7 +129,7 @@ public class DocView extends DocViewBase implements DragHandleListener else { // point in screen coordinates, result in page coordinates - Rect r = dpv.getTappedRect(p); + Rect r = dpv.selectWord(p); if (r != null) { // show handles @@ -142,34 +141,11 @@ public class DocView extends DocViewBase implements DragHandleListener selectionEndPage = dpv; selectionEndLoc.set(r.right, r.bottom); - // do highlight - doHighlight(); - moveHandlesToCorners(); } } } - private void doHighlight() - { - // TODO: for now, we're dealing with one page at a time - int numPages = getPageCount(); - for (int i = 0; i < numPages; i++) - { - DocPageView cv = (DocPageView) getOrCreateChild(i); - if (cv.isReallyVisible() && cv == selectionStartPage && cv == selectionEndPage) - { - cv.setHighlight(selectionStartLoc, selectionEndLoc); - logSelectedText(); - } - else - { - cv.removeHighlight(); - } - cv.invalidate(); - } - } - @Override protected void doDoubleTap(float fx, float fy) { @@ -247,30 +223,29 @@ public class DocView extends DocViewBase implements DragHandleListener } } - doHighlight(); - } - - @Override - public void onEndDrag(DragHandle handle) - { - moveHandlesToCorners(); - logSelectedText(); - } - - private void logSelectedText() - { + // TODO: for now, we're dealing with one page at a time int numPages = getPageCount(); for (int i = 0; i < numPages; i++) { DocPageView cv = (DocPageView) getOrCreateChild(i); - String s = cv.getSelectedText(); - if (s != null) + if (cv.isReallyVisible() && cv == selectionStartPage && cv == selectionEndPage) { - Log.i("example", s); + cv.setSelection(selectionStartLoc, selectionEndLoc); } + else + { + cv.removeSelection(); + } + cv.invalidate(); } } + @Override + public void onEndDrag(DragHandle handle) + { + moveHandlesToCorners(); + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { @@ -284,8 +259,18 @@ public class DocView extends DocViewBase implements DragHandleListener { if (selectionStartPage != null && selectionEndPage != null) { - positionHandle(mSelectionHandleTopLeft, selectionStartPage, selectionStartLoc.x, selectionStartLoc.y); - positionHandle(mSelectionHandleBottomRight, selectionEndPage, selectionEndLoc.x, selectionEndLoc.y); + Point p1 = selectionStartPage.getSelectionStart(); + Point p2 = selectionEndPage.getSelectionEnd(); + + if (p1 != null && p2 != null) + { + selectionStartLoc.set(p1.x, p1.y); + selectionEndLoc.set(p2.x, p2.y); + positionHandle(mSelectionHandleTopLeft, selectionStartPage, selectionStartLoc.x, selectionStartLoc.y); + positionHandle(mSelectionHandleBottomRight, selectionEndPage, selectionEndLoc.x, selectionEndLoc.y); + } + + } } } diff --git a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocViewBase.java b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocViewBase.java index 23f60026..bb9d422d 100755 --- a/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocViewBase.java +++ b/platform/android/example/mupdf/src/main/java/com/artifex/mupdf/android/DocViewBase.java @@ -45,7 +45,7 @@ public class DocViewBase // bitmaps for rendering // these are created by the activity and set using setBitmaps() - private final static double OVERSIZE_FACTOR = 1.3; + private final static double OVERSIZE_FACTOR = 1.4; private final Bitmap[] bitmaps = {null, null}; private int bitmapIndex = 0; -- cgit v1.2.3