Secure your PDFs with custom watermarks on Android

Adding a non-removable watermark to documents can discourage viewers from sharing your content or taking screenshots. For an additional layer of security, you can use a custom watermark for each individual user that contains identifying information such as their name, timestamp, and ID. This makes it easier to trace any leaks backs to the source.

Nutrient Android SDK allows you to draw content above a displayed document using PdfDrawable, which is a subtype of Android’s Drawable class and thus similar in use. This article covers using the PdfDrawable API to add a watermark into a document.

Our Android Catalog application contains a working example(opens in a new tab), WatermarkExample, with the code detailed here. For more information on our drawable API itself, refer to its specific guide.

Creating the DrawableProvider

To draw a watermark onto a page, you’ll need to use the PdfDrawable API. To do so, you must provide your implementation of PdfDrawable with a PdfFragment using a PdfDrawableProvider. In addition, you can provide it to PdfThumbnailBar and PdfThumbnailGrid to also add the watermark to thumbnails.

Inside your PdfActivity, you must add this provider, like so:

class WatermarkExampleActivity : PdfActivity() {
/**
* Drawable provider that provides example watermarks.
*/
private val customTestDrawableProvider: PdfDrawableProvider = object : PdfDrawableProvider() {
override fun getDrawablesForPage(context: Context, document: PdfDocument, @IntRange(from = 0) pageIndex: Int): List<PdfDrawable> {
return listOf(
// You can pass in multiple drawables if needed. Here is a single text watermark,
// tilted by 45 degrees with the bottom-left corner at (350, 350) in PDF coordinates.
WatermarkDrawable("Watermark", PointF(350f, 350f))
)
}
}
}

Notice the parameters being passed in. You can use information contained in the PdfDocument or the pageIndex to, for example, switch up the watermark drawables returned depending on different factors as desired.

Then, on the onCreate handler, register the provider on all of the applicable views:

class WatermarkExampleActivity : PdfActivity() {
/**
* Drawable provider that provides example watermarks.
*/
private val customTestDrawableProvider: PdfDrawableProvider = ...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Register the drawable provider on `PdfFragment` to provide drawables to document pages.
requirePdfFragment().addDrawableProvider(customTestDrawableProvider)
// Also register the drawable provider on the thumbnail bar and thumbnail grid.
pspdfKitViews.thumbnailBarView?.addDrawableProvider(customTestDrawableProvider)
pspdfKitViews.thumbnailGridView?.addDrawableProvider(customTestDrawableProvider)
// Outline displays page previews in the bookmarks list. Bookmarks are enabled in this example
// so you need to register the drawable provider on the outline view too.
pspdfKitViews.outlineView?.addDrawableProvider(customTestDrawableProvider)
}
}

Creating the PdfDrawable

With the provider in place, you need to now actually create the PdfDrawable. You’ll use the same example as the section above, called WatermarkDrawable.

The implemented class accepts a string and a PointF as parameters, representing the text to write as the watermark and its starting coordinates.

There’s also some simple math involved with page coordinates and how Nutrient does redrawing whenever the view changes. However, the main method is draw, where the content is put into the screen. As it uses the UI thread, you must ensure this method is as performant as possible.

The full snippet is presented below:

private class WatermarkDrawable(private val text: String, startingPoint: PointF) : PdfDrawable() {
private val redPaint = Paint().apply {
color = Color.RED
style = Paint.Style.FILL
alpha = 50
textSize = 100f
}
private val pageCoordinates = RectF()
private val screenCoordinates = RectF()
init {
calculatePageCoordinates(text, startingPoint)
}
private fun calculatePageCoordinates(text: String, point: PointF) {
val textBounds = Rect()
redPaint.getTextBounds(text, 0, text.length, textBounds)
pageCoordinates.set(
point.x,
point.y + textBounds.height().toFloat(),
point.x + textBounds.width().toFloat(),
point.y
)
}
private fun updateScreenCoordinates() {
pdfToPageTransformation.mapRect(screenCoordinates, pageCoordinates)
// Rounding out ensures no clipping of content.
val bounds = bounds
screenCoordinates.roundOut(bounds)
this.bounds = bounds
}
/**
* This method performs all the drawing required by this drawable.
* Keep this method fast to maintain a performant UI.
*/
override fun draw(canvas: Canvas) {
val bounds = bounds.toRectF()
canvas.save()
// Rotate canvas by 45 degrees.
canvas.rotate(-45f, bounds.left, bounds.bottom)
// Recalculate text size to much new bounds.
setTextSizeForWidth(redPaint, bounds.width(), text)
// Draw the text on the rotated canvas.
canvas.drawText(text, bounds.left, bounds.bottom, redPaint)
canvas.restore()
}
private fun setTextSizeForWidth(
paint: Paint,
desiredWidth: Float,
text: String
) {
// Pick a reasonably large value for the test.
val testTextSize = 60f
// Get the bounds of the text using `testTextSize`.
paint.textSize = testTextSize
val bounds = Rect()
paint.getTextBounds(text, 0, text.length, bounds)
// Calculate the desired size as a proportion of `testTextSize`.
val desiredTextSize = testTextSize * desiredWidth / bounds.width()
// Set the paint for that size.
paint.textSize = desiredTextSize
}
/**
* PSPDFKit calls this method every time the page was moved or resized on the screen.
* It will provide a fresh transformation for calculating screen coordinates from
* PDF coordinates.
*/
override fun updatePdfToViewTransformation(matrix: Matrix) {
super.updatePdfToViewTransformation(matrix)
updateScreenCoordinates()
}
@UiThread
override fun setAlpha(alpha: Int) {
redPaint.alpha = alpha
invalidateSelf()
}
@UiThread
override fun setColorFilter(colorFilter: ColorFilter?) {
redPaint.colorFilter = colorFilter
invalidateSelf()
}
override fun getOpacity(): Int {
return PixelFormat.TRANSLUCENT
}
}

Note that this guide is meant as a focused example. For a deeper dive into the PdfDrawable API itself, see our drawable API guide.