PDF stamp annotations on Android

Nutrient supports stamp annotations, which you can customize to your liking. To change the default set of stamp annotations available for your application, you have to provide a StampAnnotationConfiguration in the AnnotationConfigurationRegistry:

// Adding custom subject stamps.
val items = listOf(
    StampPickerItem.fromTitle(this, "Great!").build(),
    StampPickerItem.fromTitle(this, "Stamp!").build(),
    StampPickerItem.fromTitle(this, "Like").build()
)

// Available stamps can be configured through the `PdfFragment`.
requirePdfFragment().annotationConfiguration.put(
    AnnotationType.STAMP,
    StampAnnotationConfiguration.builder(this)
        // Here you set the list of stamp picker items that are going to be available in the stamp picker.
        .setAvailableStampPickerItems(items)
        .build()
)
// Create list of stamps that are going to be added to the picker.
final List<StampPickerItem> items = new ArrayList<>();

// Adding custom subject stamps.
items.add(StampPickerItem.fromTitle(this, "Great!").build());
items.add(StampPickerItem.fromTitle(this, "Stamp!").build());
items.add(StampPickerItem.fromTitle(this, "Like").build());

// Available stamps can be configured through the `PdfFragment`.
getPdfFragment().getAnnotationConfiguration().put(
    AnnotationType.STAMP,
    StampAnnotationConfiguration.builder(this)
        // Here you set the list of stamp picker items that are going to be available in the stamp picker.
        .setAvailableStampPickerItems(items)
        .build()
);
Information

Call StampPickerItem.getDefaultStampPickerItems() to retrieve the default set already bundled in the library.

Stamp picker dialog with a custom set of default stamps

Default stamp annotations

Nutrient comes with some out-of-the-box stamp annotations available in the stamp picker dialog:

val items = mutableListOf<StampPickerItem>()

// Standard stamps.
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.APPROVED).build())
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.NOT_APPROVED).build())
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.DRAFT).build())
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.FINAL).build())
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.COMPLETED).build())
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.CONFIDENTIAL).build())
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.FOR_PUBLIC_RELEASE).build())
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.NOT_FOR_PUBLIC_RELEASE).build())
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.FOR_COMMENT).build())
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.VOID).build())
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.PRELIMINARY_RESULTS).build())
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.INFORMATION_ONLY).build())

// Tick and cross stamps.
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.ACCEPTED).build())
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.REJECTED).build())

// Signature stamps.
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.INITIAL_HERE).build())
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.SIGN_HERE).build())
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.WITNESS).build())

// Custom stamp.
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.CUSTOM).build())

// Revised/rejected stamps with localized datetime subtext.
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.REVISED).withDateTimeSubtitle(true, true).build())
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.REJECTED).withDateTimeSubtitle(true, true).build())
final List<StampPickerItem> items = new ArrayList<>();

// Standard stamps.
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.APPROVED).build());
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.NOT_APPROVED).build());
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.DRAFT).build());
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.FINAL).build());
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.COMPLETED).build());
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.CONFIDENTIAL).build());
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.FOR_PUBLIC_RELEASE).build());
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.NOT_FOR_PUBLIC_RELEASE).build());
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.FOR_COMMENT).build());
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.VOID).build());
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.PRELIMINARY_RESULTS).build());
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.INFORMATION_ONLY).build());

// Tick and cross stamps.
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.ACCEPTED).build());
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.REJECTED).build());

// Signature stamps.
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.INITIAL_HERE).build());
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.SIGN_HERE).build());
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.WITNESS).build());

// Custom stamp.
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.CUSTOM).build());

// Revised/rejected stamps with localized datetime subtext.
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.REVISED).withDateTimeSubtitle(true, true).build());
items.add(StampPickerItem.fromPredefinedType(context, PredefinedStampType.REJECTED).withDateTimeSubtitle(true, true).build());
Information

When a user picks the PredefinedStampType.CUSTOM stamp, Nutrient will show a stamp creation dialog that can be used to create custom stamps with user-definable text and colors, and (if desired) the current date.

Image stamp annotations

Nutrient supports stamp annotations generated from a bitmap image. These image stamp annotations cannot have a localized subject, subtext, or text color. Use StampPickerItem#fromBitmap(android.graphics.Bitmap) to create a builder for bitmap stamp annotations:

val items = ArrayList<StampPickerItem>()
...
try {
    val bitmap = BitmapFactory.decodeStream(getAssets().open("inline-media/images/exampleimage.jpg"))
    items.add(StampPickerItem.fromBitmap(bitmap)
        // Specifying only a single size dimension will produce bitmap stamps preserving the original aspect ratio.
        .withSize(StampPickerItem.DEFAULT_STAMP_ANNOTATION_PDF_WIDTH)
        .build())
} catch (e: IOException) {
    e.printStackTrace()
}
final List<StampPickerItem> items = new ArrayList<>();
...
try {
    final Bitmap bitmap = BitmapFactory.decodeStream(getAssets().open("inline-media/images/exampleimage.jpg"));
    items.add(StampPickerItem.fromBitmap(bitmap)
        .withSize(StampPickerItem.DEFAULT_STAMP_ANNOTATION_PDF_WIDTH)
        .build());
} catch (IOException e) {
    e.printStackTrace();
}
Information

Stamp images are encoded to the JPEG format, which does not allow transparency.

Vector stamp annotations

Nutrient allows you to override the appearance stream of any annotation. This is especially useful for stamp annotations. Unlike bitmap stamp annotations, stamp annotations with custom appearance streams allow transparency and high-resolution zooming.

Here’s how to define a stamp picker item with a custom appearance stream generator set:

val items = ArrayList<StampPickerItem>()
...
// Create the appearance stream generator with a PDF containing a vector logo.
val appearanceStreamGenerator = AssetAppearanceStreamGenerator("PSPDFKit Logo.pdf")

// Create the picker item with a custom subject and custom appearance stream generator set.
items.add(StampPickerItem.fromTitle(context, "Custom subject")
    .withSize(StampPickerItem.DEFAULT_STAMP_ANNOTATION_PDF_WIDTH)
    .withAppearanceStreamGenerator(appearanceStreamGenerator)
    .build())
final List<StampPickerItem> items = new ArrayList<>();
...
// Create the appearance stream generator with a PDF containing a vector logo.
AssetAppearanceStreamGenerator appearanceStreamGenerator = new AssetAppearanceStreamGenerator("PSPDFKit Logo.pdf");

// Create the picker item with a custom subject and custom appearance stream generator set.
items.add(StampPickerItem.fromTitle(context, "Custom subject")
    .withSize(StampPickerItem.DEFAULT_STAMP_ANNOTATION_PDF_WIDTH)
    .withAppearanceStreamGenerator(appearanceStreamGenerator)
    .build());

Appearance streams of created stamps will be saved into the document when saving. However, they will need to be regenerated whenever stamp annotations are modified after the document is reloaded. For this case, we register a global appearance stream generator on a document. This will generate custom appearance streams for stamp annotations based on their subjects:

override fun onDocumentLoaded(document: PdfDocument) {
    super.onDocumentLoaded(document)

    // Register the custom stamp appearance stream generator as a global appearance stream generator.
    val customStampAppearanceStreamGenerator = CustomStampAppearanceStreamGenerator()
    document.annotationProvider.addAppearanceStreamGenerator(customStampAppearanceStreamGenerator)

    // Create the appearance stream generator with a PDF containing a vector logo.
    val appearanceStreamGenerator = AssetAppearanceStreamGenerator("PSPDFKit Logo.pdf")

    // Register the created appearance stream generator for the custom subject.
    customStampAppearanceStreamGenerator.addAppearanceStreamGenerator("Custom subject", appearanceStreamGenerator)
}
@Override
public void onDocumentLoaded(@NonNull PdfDocument document) {
    super.onDocumentLoaded(document);

    // Register the custom stamp appearance stream generator as a global appearance stream generator.
    CustomStampAppearanceStreamGenerator customStampAppearanceStreamGenerator = new CustomStampAppearanceStreamGenerator();
    document.getAnnotationProvider().addAppearanceStreamGenerator(customStampAppearanceStreamGenerator);

    // Create the appearance stream generator with a PDF containing a vector logo.
    AssetAppearanceStreamGenerator appearanceStreamGenerator = new AssetAppearanceStreamGenerator("PSPDFKit Logo.pdf");

    // Register the created appearance stream generator for the custom subject.
    customStampAppearanceStreamGenerator.addAppearanceStreamGenerator("Custom subject", appearanceStreamGenerator);
}

For a comprehensive example, take a look at CustomStampAnnotationsExample inside the Catalog app, which shows how to create a different set of default stamp annotations.