
Contributing to open-source projects is an excellent way to improve your programming skills while helping the community. In this tutorial, we'll walk through enhancing the watermark functionality of Stirling-PDF, an open-source Spring Boot application that offers over 50 different PDF operations. We'll implement several requested features from the project's GitHub issues, demonstrating how to approach feature development in an existing codebase.
Project Overview: Stirling-PDF
Stirling-PDF is a feature-rich Spring Boot application that handles various PDF operations, including adding watermarks, password protection, image insertion, and PDF-to-image conversion. The project is actively maintained on GitHub and has a clean, functional codebase that makes it ideal for contributions.

Before diving into development, I'll test the current watermark functionality to understand its implementation. The existing feature allows users to add text or image watermarks to PDFs with basic customization options like font size, color, and rotation.
Understanding the Feature Request
Looking at the GitHub issues, a user has requested several enhancements to the watermark functionality. Let's document these requirements in our project before implementation:
- Support for fixed or randomly positioned watermarks
- Random orientation options for watermarks
- Mixed font support for varied typography
- Minimum and maximum font size settings
- Control over the number of watermarks
- Adjustable watermark transparency levels
- Fixed or random watermark shading
- Support for mirrored watermarks
These enhancements will significantly improve the watermark feature, allowing users to create more dynamic and customized watermarks for their PDF documents.
Project Structure Analysis
Before making changes, let's understand the project's structure. Stirling-PDF follows standard Spring Boot conventions with controllers, services, and repositories. The application has approximately 21 components, 65 controllers, and several repositories, indicating it uses a database for some functionality.

This is a medium-sized application - larger than a simple demo project but not overly complex, making it approachable for feature contributions.
Implementation Plan
Based on our analysis of the existing watermark functionality and the requested enhancements, here's our implementation plan:
- Extend the watermark controller to accept new parameters
- Update the service layer to process these parameters
- Implement the randomization logic for positioning, orientation, and fonts
- Add support for transparency and shading adjustments
- Implement mirroring functionality
- Update the UI to expose these new options to users
- Write tests to ensure the functionality works as expected
Extending the Watermark Controller
First, we need to identify the controller handling watermark functionality. In a Spring Boot application, this would typically be in a class ending with 'Controller'. After examining the codebase, we find the relevant controller and need to extend it to accept our new parameters.
// Existing watermark controller method
@PostMapping("/api/watermark")
public ResponseEntity<byte[]> addWatermark(
@RequestParam("file") MultipartFile file,
@RequestParam("watermarkType") String watermarkType,
@RequestParam("text") String text,
@RequestParam("fontSize") int fontSize,
@RequestParam("rotation") int rotation,
@RequestParam("opacity") float opacity,
@RequestParam("color") String color) {
// Implementation
}
// Enhanced version with new parameters
@PostMapping("/api/watermark")
public ResponseEntity<byte[]> addWatermark(
@RequestParam("file") MultipartFile file,
@RequestParam("watermarkType") String watermarkType,
@RequestParam("text") String text,
@RequestParam("fontSize") int fontSize,
@RequestParam("rotation") int rotation,
@RequestParam("opacity") float opacity,
@RequestParam("color") String color,
@RequestParam(value = "positionType", defaultValue = "fixed") String positionType,
@RequestParam(value = "orientationType", defaultValue = "fixed") String orientationType,
@RequestParam(value = "fontType", defaultValue = "fixed") String fontType,
@RequestParam(value = "minFontSize", defaultValue = "0") int minFontSize,
@RequestParam(value = "maxFontSize", defaultValue = "0") int maxFontSize,
@RequestParam(value = "watermarkCount", defaultValue = "1") int watermarkCount,
@RequestParam(value = "shadingType", defaultValue = "fixed") String shadingType,
@RequestParam(value = "mirrorWatermark", defaultValue = "false") boolean mirrorWatermark) {
// Implementation with enhanced features
}
Notice how we've added new parameters with default values to maintain backward compatibility. This ensures existing API calls will continue to work while allowing new calls to utilize the enhanced features.
Implementing the Watermark Service Logic
Next, we need to update the service layer to implement the actual watermark functionality with our new features. This involves generating random positions, orientations, and font selections based on the parameters.

private void applyWatermarks(PDDocument document, WatermarkOptions options) {
Random random = new Random();
for (int i = 0; i < options.getWatermarkCount(); i++) {
// For each page in the document
for (PDPage page : document.getPages()) {
try (PDPageContentStream contentStream =
new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
// Position calculation - fixed or random
float xPosition, yPosition;
if ("random".equals(options.getPositionType())) {
xPosition = random.nextFloat() * page.getMediaBox().getWidth();
yPosition = random.nextFloat() * page.getMediaBox().getHeight();
} else {
// Use fixed position logic
xPosition = page.getMediaBox().getWidth() / 2;
yPosition = page.getMediaBox().getHeight() / 2;
}
// Orientation - fixed or random
float rotation;
if ("random".equals(options.getOrientationType())) {
rotation = random.nextFloat() * 360;
} else {
rotation = options.getRotation();
}
// Font selection - fixed or random
PDFont font;
if ("random".equals(options.getFontType())) {
// Select from available fonts
String[] availableFonts = {"Helvetica", "Courier", "Times-Roman"};
String selectedFont = availableFonts[random.nextInt(availableFonts.length)];
font = PDType1Font.valueOf(selectedFont);
} else {
font = PDType1Font.HELVETICA;
}
// Font size - fixed, or random within range
float fontSize;
if (options.getMinFontSize() > 0 && options.getMaxFontSize() > 0) {
fontSize = options.getMinFontSize() +
random.nextFloat() * (options.getMaxFontSize() - options.getMinFontSize());
} else {
fontSize = options.getFontSize();
}
// Apply the watermark with all options
applyWatermark(contentStream, options.getText(), font, fontSize,
xPosition, yPosition, rotation, options.getOpacity(),
options.getColor(), options.isMirrorWatermark());
}
}
}
}
This implementation handles the core logic for our enhanced watermark features. The method takes a PDDocument (the PDF document) and a WatermarkOptions object containing all our parameters. For each watermark count and each page, it applies a watermark with the specified options.
Creating a WatermarkOptions Class
To organize our parameters, we'll create a WatermarkOptions class that encapsulates all the settings for the watermark operation:
public class WatermarkOptions {
private String text;
private int fontSize;
private int rotation;
private float opacity;
private Color color;
private String positionType; // "fixed" or "random"
private String orientationType; // "fixed" or "random"
private String fontType; // "fixed" or "random"
private int minFontSize;
private int maxFontSize;
private int watermarkCount;
private String shadingType; // "fixed" or "random"
private boolean mirrorWatermark;
// Constructors, getters, and setters
// ...
}
Updating the UI
After implementing the backend functionality, we need to update the UI to expose these new options to users. Since Stirling-PDF likely uses a web interface, we'll need to modify the HTML and JavaScript files related to the watermark feature.
<!-- Additional form fields for watermark options -->
<div class="form-group">
<label for="positionType">Position Type:</label>
<select class="form-control" id="positionType" name="positionType">
<option value="fixed">Fixed</option>
<option value="random">Random</option>
</select>
</div>
<div class="form-group">
<label for="orientationType">Orientation Type:</label>
<select class="form-control" id="orientationType" name="orientationType">
<option value="fixed">Fixed</option>
<option value="random">Random</option>
</select>
</div>
<div class="form-group">
<label for="fontType">Font Type:</label>
<select class="form-control" id="fontType" name="fontType">
<option value="fixed">Fixed</option>
<option value="random">Random</option>
</select>
</div>
<!-- Font size range for random font sizes -->
<div class="form-group" id="fontSizeRangeGroup" style="display: none;">
<label for="minFontSize">Minimum Font Size:</label>
<input type="number" class="form-control" id="minFontSize" name="minFontSize" value="12">
<label for="maxFontSize">Maximum Font Size:</label>
<input type="number" class="form-control" id="maxFontSize" name="maxFontSize" value="36">
</div>
<div class="form-group">
<label for="watermarkCount">Number of Watermarks:</label>
<input type="number" class="form-control" id="watermarkCount" name="watermarkCount" value="1" min="1">
</div>
<div class="form-group">
<label for="shadingType">Shading Type:</label>
<select class="form-control" id="shadingType" name="shadingType">
<option value="fixed">Fixed</option>
<option value="random">Random</option>
</select>
</div>
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="mirrorWatermark" name="mirrorWatermark">
<label class="form-check-label" for="mirrorWatermark">Mirror Watermark</label>
</div>
We'll also need some JavaScript to handle the dynamic UI behavior, such as showing/hiding the font size range inputs when the random font option is selected:
document.addEventListener('DOMContentLoaded', function() {
const fontTypeSelect = document.getElementById('fontType');
const fontSizeRangeGroup = document.getElementById('fontSizeRangeGroup');
// Show/hide font size range based on font type selection
fontTypeSelect.addEventListener('change', function() {
if (this.value === 'random') {
fontSizeRangeGroup.style.display = 'block';
} else {
fontSizeRangeGroup.style.display = 'none';
}
});
});
Testing the Implementation
Once we've implemented our changes, we should test them thoroughly to ensure they work as expected. We can create unit tests for the service methods and also manually test the functionality through the UI.
@Test
public void testRandomPositionWatermark() {
// Setup test PDF document
PDDocument document = new PDDocument();
document.addPage(new PDPage());
// Create watermark options with random positioning
WatermarkOptions options = new WatermarkOptions();
options.setText("TEST");
options.setFontSize(12);
options.setRotation(0);
options.setOpacity(0.5f);
options.setColor(Color.RED);
options.setPositionType("random");
options.setOrientationType("fixed");
options.setFontType("fixed");
options.setWatermarkCount(5);
// Apply watermarks
watermarkService.applyWatermarks(document, options);
// Verify watermarks were applied (this would need more specific assertions
// based on how we can inspect the content streams)
assertEquals(1, document.getNumberOfPages());
// Cleanup
document.close();
}
Submitting the Contribution
After implementing and testing our changes, we can prepare a pull request to contribute back to the Stirling-PDF project:
- Fork the repository on GitHub
- Create a new branch for our feature
- Commit our changes with descriptive commit messages
- Push the branch to our fork
- Create a pull request with a detailed description of the changes
- Address any feedback from the project maintainers
Conclusion
In this tutorial, we've walked through the process of enhancing the watermark functionality in an existing Spring Boot application. We've added support for random positioning, orientation, font variations, and other customization options that make the watermark feature more flexible and powerful.
This approach demonstrates how to contribute to open-source projects by identifying feature requests, understanding the existing codebase, implementing new functionality, and submitting your changes. By following these steps, you can make meaningful contributions to open-source projects while improving your programming skills.
The enhanced watermark feature we've implemented provides users with much more creative control over their PDF watermarks, allowing for dynamic and varied watermark styles that can better protect documents while maintaining visual appeal.
Let's Watch!
Live Coding: Enhancing PDF Watermark Features in a Spring Boot Application
Ready to enhance your neural network?
Access our quantum knowledge cores and upgrade your programming abilities.
Initialize Training Sequence