Implementing MuPDF Library for Android

  • September 20, 2019
  • Android Application

Problem Statement

Developing apps that require you to show PDF files can sometimes be very complex. If there is a requirement that needs you to do text highlighting and pdf manipulation then it becomes very difficult. Of course Android provides its own API by the name PdfRenderer[Added in API 21], but it does not have many features. MuPDF is one of the best PDF libraries that we found. In this article, you will understand how to implement it in your project.

Introduction

MuPDF is a  lightweight PDF and XPS viewer for Android and iOS. There are many other libraries that provide PDF support but MuPDF is by far the best that we have used. And following this article will help you understand it’s advantages.

MuPDF Advantages

There are many other native and third-party libraries that provide support for PDF, but all have some or the other limitations.

Some of the advantages that we found out for using MuPDF are as follows:

  • Less Rendering Time.
  • Pinch-To-Zoom feature.
  • Text Highlight Support.
  • Searching Functionality.
  • Feature Rich Library.

 

The renderer in MuPDF is tailored for high-quality anti-aliased graphics. It renders text with metrics and spacing accurate to within fractions of a pixel for the highest fidelity in reproducing the look of a printed page on the screen.

 

Challenges in Using MuPDF Library

No doubt MuPDF is a great library, but it’s worth mentioning that building this library is something not everyone can do. The normal mupdf build process involves running some code on the host (the machine on which you are compiling), rather than the target (the machine/device on which you eventually want to run mupdf). This code repacks various bits of information (fonts, CMAPs etc) into a more compact and usable form.

Unfortunately, the Android SDK does not provide a compiler for the host machine, so we cannot run this step automatically as part of the Android build. You will need to generate it by running a different build, such as the windows or Linux native builds. We do not make a snapshot of the generated directory available to download as the contents of this directory change frequently, and we’d have to keep multiple versions on the website. We assume that anyone capable of building for Android is capable of doing a normal hosted build. On windows (where you are using Cygwin), or on Linux/MacOS, this can be as simple as running ‘make’ in the top level directory. Even if the make process fails, it should get far enough to generate you the required ‘generated’ directory, and you can continue through these instructions.

Implementation in Android

You can get the documentation and the library from here.

If you are not able to build the whole module, then you can use this module. It has been made by other developer and you just need to add this as a module in your project. You can find the module from here.

For this tutorial, we created a module and imported the same in our project. Let us look at the steps required:

Step 1

Create a new Project in Android Studio, once your project is ready, click on File>New>Import Module


 

Select the downloaded MuPDF library and add it to your project.

Step 2

After adding the module, we need to add a layout to show a UI for the app.

We are assuming you have created the UI and in our Demo app, we are getting a sample pdf file from the device storage.

So our UI looks like the image below.

 
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="in.dnxlogic.mupdfdemo.MainActivity">

<android.support.v7.widget.Toolbar
android:id=”@+id/toolbar”
android:layout_width=”match_parent”
android:layout_height=”?android:attr/actionBarSize”
android:background=”@color/colorPrimary” />

<TextView
android:id=”@+id/msgTxt”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:layout_centerInParent=”true”
android:layout_margin=”20dp”
android:gravity=”center”
android:text=”Click the button to open your \n desired PDF file.”
android:textAppearance=”?android:attr/textAppearanceMedium”
android:textColor=”#999999″ />

<RelativeLayout
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:layout_above=”@+id/open_btn”
android:layout_below=”@+id/toolbar”>

<TextView
android:id=”@+id/title”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:background=”#50000000″
android:padding=”5dp”
android:text=”Heading”
android:textAppearance=”?android:attr/textAppearanceMedium”
android:textColor=”#FFF”
android:textStyle=”bold” />

<RelativeLayout
android:id=”@+id/pdfViewRL”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:layout_below=”@+id/title”
android:layout_marginLeft=”@dimen/activity_horizontal_margin”
android:layout_marginRight=”@dimen/activity_horizontal_margin”>

</RelativeLayout>
</RelativeLayout>

<android.support.v7.widget.AppCompatButton
android:id=”@+id/open_btn”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:layout_alignParentBottom=”true”
android:layout_centerHorizontal=”true”
android:layout_margin=”10dp”
android:gravity=”center”
android:text=”Open” />

</RelativeLayout>

 

Step 3

Now let’s take a look at our MainActivity class.

The MainActivity class has one method named pickFileOnClick().
Intent.ACTION_GET_CONTENT- 
This method is used to open an Action Intent which opens up the default file manager of the device. Allow the user to select a particular kind of data and return it. This is different than {@link #ACTION_PICK} in that here we just say what kind of data is desired, not a URI of existing data from which the user can pick. ACTION_GET_CONTENT could allow the user to create the data as it runs (for example taking a picture or recording a sound), let them browse the web and download the desired data, etc.

intent.setType(“application/pdf”)- Specifies the type of files it can handle. This is used to create intents that only specify a type and not data, for example to indicate the type of data to return.

After the intent is fired, the Android File Manager is opened. You can select the PDF file you want to read. After you select the desired pdf file, onActivityResult() method is called. In this method the PDF file’s path is given to the displayPDF() method.

displayPDF()-This method uses the classes from MuPDF library to show the PDF file in a container. In our case it is an Activity’s Relative Layout.

MuPDFCore core = new MuPDFCore(getApplicationContext(), buffer, null);
MuPDFReaderView mDocView = new MuPDFReaderView(getApplicationContext());
mDocView.setAdapter(new MuPDFPageAdapter(getApplicationContext(), null, core));
pdfViewRL.addView(mDocView, new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, 
RelativeLayout.LayoutParams.WRAP_CONTENT));

 

MuPDFReaderView-This is the custom view provided by MuPDF that shows the actual PDF file.

 
MainActivity.class
package in.dnxlogic.mupdfdemo;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.AppCompatButton;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.artifex.mupdfdemo.MuPDFActivity;
import com.artifex.mupdfdemo.MuPDFCore;
import com.artifex.mupdfdemo.MuPDFPageAdapter;
import com.artifex.mupdfdemo.MuPDFReaderView;

public class MainActivity extends AppCompatActivity {
private static final int REQUEST_FINE_LOCATION = 0;
private AppCompatButton openFileManagerBtn;
private Toolbar toolbar;
private RelativeLayout pdfViewRL;
private TextView title, msgTxt;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Marshmallow Runtime Permissions
loadPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, REQUEST_FINE_LOCATION);
openFileManagerBtn = (AppCompatButton) findViewById(R.id.open_btn);
toolbar = (Toolbar) findViewById(R.id.toolbar);
title = (TextView) findViewById(R.id.title);
msgTxt = (TextView) findViewById(R.id.msgTxt);
title.setVisibility(View.GONE);
pdfViewRL = (RelativeLayout) findViewById(R.id.pdfViewRL);
setSupportActionBar(toolbar);
toolbar.setTitleTextColor(Color.WHITE);
openFileManagerBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
pickFileOnClick(view);
}
});
}
private void displayPDF(String path) throws Exception {
MuPDFCore core = new MuPDFCore(getApplicationContext(), buffer,null);
MuPDFReaderView mDocView = new MuPDFReaderView(getApplicationContext());
mDocView.setAdapter(new MuPDFPageAdapter(getApplicationContext(), null, core));
pdfViewRL.addView(mDocView, new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT));
}
@Override
public void onActivityResult(int reqCode, int result, Intent intent) {
if (reqCode == 1) {
if (result == RESULT_OK) {
Uri data = intent.getData();
Log.i(“Main”, “File path: ” + data.getPath());
Log.i(“Main”, “File name: ” + data.getLastPathSegment());
if (data.getLastPathSegment() != null) {
title.setVisibility(View.VISIBLE);
title.setText(data.getLastPathSegment());
msgTxt.setVisibility(View.GONE);
}
try {
displayPDF(data.getPath());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

public void pickFileOnClick(View v) {
//Method to open pdf files
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType(“application/pdf”);
startActivityForResult(intent, 1);
}

private void loadPermissions(String perm, int requestCode) {
if (ContextCompat.checkSelfPermission(this, perm) != PackageManager.PERMISSION_GRANTED) {
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, perm)) {
ActivityCompat.requestPermissions(this, new String[]{perm}, requestCode);
}
}
}

// Code Added
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case REQUEST_FINE_LOCATION: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.v(“STATUS”, “Permission Granted!”);

} else {
// no granted

}
return;
}

}
}
}

 

Screenshots

    

Conclusion

So this was a short tutorial on implementing MuPDF library for Android. If you have any doubts or queries then you can share your comments below. We’ll be happy to help you out. You can check out the full source from code here.

CONNECT
WITH US

Subscribe to our Newsletter