Skip to content

Create a Custom Tool

Event Handling

Tool Handlers and Annotation Handlers receive touch and gesture events from PDFViewCtrl. Events go to UIExtensionsManager:

  • Tool Handler: If a current Tool Handler exists, the event is passed to it and processing stops.
  • Annotation Handler: If an annotation is selected, the event goes to its Annotation Handler.
  • Selection Tool Handler: Otherwise the Selection Tool Handler receives the event.
    • Text Selection Tool: Text selection (e.g. add highlight).
    • Blank Selection Tool: Blank area (e.g. add note).

NOTE

  • Tool Handler and Annotation Handler do not handle the same event at once.
  • Tool Handlers mainly create annotations (Link not supported), signatures, and text selection.
  • Annotation Handlers mainly edit annotations and fill forms.

Event flow:

Tool Handler and Annotation Handler event flow

Custom Tool Example

This guide adds a custom tool on UI Extensions. Built-in tools in UI Extensions can be used as references.

Example: a region screen capture tool that selects an area on the page and saves it as an image.

INFO

The example is based on samples/viewer_ctrl_demo and implements ToolHandler.java.

Overview

  • Create ScreenCaptureToolHandler implementing ToolHandler
  • Handle onTouchEvent and onDraw (selection, overlay, render and save bitmap)
  • Register with UIExtensionsManager
  • Set as current tool handler

Step 1: Create ScreenCaptureToolHandler

Load viewer_ctrl_demo in Android Studio, create ScreenCaptureToolHandler in com.foxit.pdf.viewctrl implementing ToolHandler.

Step 2: Handle onTouchEvent and onDraw

Reference implementation:

Details
java
package com.foxit.pdf.pdfviewer;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Environment;
import android.view.MotionEvent;
import android.widget.Toast;

import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.PDFException;
import com.foxit.sdk.common.Progressive;
import com.foxit.sdk.common.fxcrt.Matrix2D;
import com.foxit.sdk.pdf.PDFPage;
import com.foxit.sdk.common.Renderer;
import com.foxit.uiextensions.ToolHandler;
import com.foxit.uiextensions.UIExtensionsManager;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class ScreenCaptureToolHandler implements ToolHandler {

    private Context mContext;
    private PDFViewCtrl mPdfViewCtrl;

    public ScreenCaptureToolHandler(Context context, PDFViewCtrl pdfViewCtrl) {
        mPdfViewCtrl = pdfViewCtrl;
        mContext = context;
    }

    @Override
    public String getType() {
        return "";
    }

    @Override
    public void onActivate() {

    }

    @Override
    public void onDeactivate() {

    }

    private PointF mStartPoint = new PointF(0, 0);
    private PointF mEndPoint = new PointF(0, 0);
    private PointF mDownPoint = new PointF(0, 0);
    private Rect mRect = new Rect(0, 0, 0, 0);
    private RectF mNowRect = new RectF(0, 0, 0, 0);
    private int mLastPageIndex = -1;

// Handle touch events
    @Override
    public boolean onTouchEvent(int pageIndex, MotionEvent motionEvent) {
        // Get the display-view point in device coordinates
        PointF devPt = new PointF(motionEvent.getX(), motionEvent.getY());
        PointF point = new PointF();
        // Convert display-view coordinates to page-view coordinates
        mPdfViewCtrl.convertDisplayViewPtToPageViewPt(devPt, point, pageIndex);
        float x = point.x;
        float y = point.y;

        switch (motionEvent.getAction()) {
            // Handle ACTION_DOWN: record the start point
            case MotionEvent.ACTION_DOWN:
                if (mLastPageIndex == -1 || mLastPageIndex == pageIndex) {
                    mStartPoint.x = x;
                    mStartPoint.y = y;
                    mEndPoint.x = x;
                    mEndPoint.y = y;
                    mDownPoint.set(x, y);
                    if (mLastPageIndex == -1) {
                        mLastPageIndex = pageIndex;
                    }
                }
                return true;

            // Handle ACTION_MOVE: update the selection rectangle
            case MotionEvent.ACTION_MOVE:
                if (mLastPageIndex != pageIndex)
                    break;
                if (!mDownPoint.equals(x, y)) {
                    mEndPoint.x = x;
                    mEndPoint.y = y;

                    // Calculate selection rectangle coordinates
                    getDrawRect(mStartPoint.x, mStartPoint.y, mEndPoint.x, mEndPoint.y);

                    // Convert float coordinates to integers
                    mRect.set((int) mNowRect.left, (int) mNowRect.top, (int) mNowRect.right, (int) mNowRect.bottom);

                    // Refresh PDFViewCtrl to trigger onDraw
                    mPdfViewCtrl.refresh(pageIndex, mRect);
                    mDownPoint.set(x, y);
                }
                return true;

            // Save the selected area as a bitmap
            case MotionEvent.ACTION_UP:
                if (mLastPageIndex != pageIndex)
                    break;
                if (!mStartPoint.equals(mEndPoint.x, mEndPoint.y)) {
                    renderToBmp(pageIndex, Environment.getExternalStorageDirectory().getPath() + "/FoxitSDK/ScreenCapture.bmp");
                    Toast.makeText(mContext, "The selected area was saved as a bitmap stored in the /FoxitSDK/ScreenCapture.bmp", Toast.LENGTH_LONG).show();
                }
                mDownPoint.set(0, 0);
                mLastPageIndex = -1;
                return true;
            default:
                return true;
        }
        return true;
    }

    // Save the Bitmap to the specified path
    public static void saveBitmap(Bitmap bm, String outPath) throws IOException {
        File file = new File(outPath);
        file.createNewFile();

        FileOutputStream fileout = null;
        try {
            fileout = new FileOutputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        bm.compress(Bitmap.CompressFormat.JPEG, 100, fileout);
        try {
            fileout.flush();
            fileout.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // Render the selected area to a bitmap
    private void renderToBmp(int pageIndex, String filePath) {
        try {
            PDFPage page = mPdfViewCtrl.getDoc().getPage(pageIndex);

            mPdfViewCtrl.convertPageViewRectToPdfRect(mNowRect, mNowRect, pageIndex);
            int width = (int) page.getWidth();
            int height = (int) page.getHeight();

            Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            bmp.eraseColor(Color.WHITE);

            // Create a Renderer
            Renderer renderer = new Renderer(bmp, true);

            // Get the display matrix
            Matrix2D matrix = page.getDisplayMatrix(0, 0, width, height, 0);
            Progressive progress = renderer.startRender(page, matrix, null);
            int state = Progressive.e_ToBeContinued;
            while (state == Progressive.e_ToBeContinued) {
                state = progress.resume();
            }

            // Crop the bitmap to the selected area
            bmp = Bitmap.createBitmap(bmp, (int) mNowRect.left, (int) (height - mNowRect.top), (int) mNowRect.width(), (int) Math.abs(mNowRect.height()));
            try {
                saveBitmap(bmp, filePath);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (PDFException e) {
            e.printStackTrace();
        }
    }

    // Calculate normalized coordinates of the selection rectangle
    private void getDrawRect(float x1, float y1, float x2, float y2) {
        float minx = Math.min(x1, x2);
        float miny = Math.min(y1, y2);
        float maxx = Math.max(x1, x2);
        float maxy = Math.max(y1, y2);

        mNowRect.left = minx;
        mNowRect.top = miny;
        mNowRect.right = maxx;
        mNowRect.bottom = maxy;
    }

    @Override
    public boolean onLongPress(int pageIndex, MotionEvent motionEvent) {
        return false;
    }

    @Override
    public boolean onSingleTapConfirmed(int pageIndex, MotionEvent motionEvent) {
        return false;
    }

    @Override
    public boolean isContinueAddAnnot() {
        return false;
    }

    @Override
    public void setContinueAddAnnot(boolean continueAddAnnot) {

    }

    // Handle draw events: draw the selection rectangle
    @Override
    public void onDraw(int i, Canvas canvas) {
        if (((UIExtensionsManager) mPdfViewCtrl.getUIExtensionsManager()).getCurrentToolHandler() != this)
            return;
        if (mLastPageIndex != i) {
            return;
        }
        canvas.save();
        Paint mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(Color.BLUE);
        mPaint.setAlpha(200);
        mPaint.setStrokeWidth(3);
        canvas.drawRect(mNowRect, mPaint);
        canvas.restore();
    }
}

NOTE

The sample saves to /FoxitSDK/ScreenCapture.bmp and shows a Toast with the path. Ensure the device or emulator can access that directory.

Step 3: Register with UIExtensionsManager

JAVA
private ScreenCaptureToolHandler screenCapture = null;
...
screenCapture = new ScreenCaptureToolHandler(mContext, pdfViewCtrl);
uiExtensionsManager.registerToolHandler(screenCapture);

Step 4: Set as Current Tool

java
uiExtensionsManager.setCurrentToolHandler(screenCapture);

Add Tool to the UI

Add a menu action item and handle clicks in MainActivity.java onActionItemClicked().

1) Add entry in Main.xml (app/src/main/res/menu)

xml
<item
   android:id="@+id/ScreenCapture"
   android:title="@string/screencapture"/>

2) Add string in strings.xml

xml
<string name="screencapture">ScreenCapture</string>

3) Handle click in onActionItemClicked()

java
if (itemId == R.id.ScreenCapture) {
    if (screenCapture == null) {
        screenCapture = new ScreenCaptureToolHandler(mContext, pdfViewCtrl);
        uiExtensionsManager.registerToolHandler(screenCapture);
    }
    uiExtensionsManager.setCurrentToolHandler(screenCapture);
}

Run and Verify

  1. Declare and hold a ScreenCaptureToolHandler instance, e.g. private ScreenCaptureToolHandler screenCapture = null;
  2. Build and run viewer_ctrl_demo.
  3. Push Sample.pdf to the path used by the sample (typically /FoxitSDK/Sample.pdf).
  4. Grant file access when prompted (Allow).
  5. Open the document, tap the page to open the page menu, and tap ScreenCapture.
  6. Press and drag to select a rectangle. A Toast shows the save path.
  7. In Device Explorer, open FoxitSDK and confirm ScreenCapture.bmp.