PDF Report Generator (java, written by Claude Code)
envgap__claude-code__java-t1-47
Written by a coding agent; not on GitHubWritten 2026-02-27
01 / FAILURE SIGNATURE
As the study recorded it
incompatible types: java.lang.Double cannot be converted to float
Not a benchmark task.
- In a clean container the reported failure did not reproduce, or the known fix did not make the project run.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
pom.xml- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
claude-code/java-t1 #47 · read the task the agent was given
Claude Code wrote this java project from the task below. It does not run on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: PDF Report Generator Write a program that generates formatted PDF reports from structured data, supporting tables, charts, headers/footers, page numbers, and custom styling. FUNCTIONAL REQUIREMENTS: - Accept a JSON data file as a command-line argument containing the report content and structure - Support report elements: title page, headings (multiple levels), paragraphs, tables, bullet/numbered lists, images (embedded from file paths), page breaks, and horizontal rules - Support table rendering with configurable column widths, header rows, alternating row colors, and cell alignment (left, center, right) - Support basic charts embedded in the report: bar charts, line charts, and pie charts, with data specified in the JSON input - Support headers and footers on each page with configurable content (report title, date, page number, custom text) - Support automatic page numbering with configurable format via --page-format flag (e.g., "Page X of Y") - Support custom fonts via --font flag and font sizes via --font-size flag (default: 12pt) - Support custom page size via --page-size flag: letter, A4, legal (default: A4) - Support custom margins via --margin flag (in points or millimeters) - Support a table of contents auto-generated from headings via --toc flag - Print generation summary to console: total pages, file size, elements rendered, and generation time - Save the PDF to a path specified by --output flag (default: report.pdf) - If no input is given, generate a sample business report with a title page, table of contents, executive summary paragraph, a data table with 20 rows, a bar chart and a pie chart, bullet point lists, and page numbers, then save it as a PDF - Handle errors: missing image files, invalid JSON structure, font not found, oversized content, and page overflow Create a complete Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include: - Source code - pom.xml with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.pdfgen</groupId>
<artifactId>pdf-report-generator</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<mainClass>pdfgen.PdfReportGenerator</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
README.md
# PDF Report Generator (Java - Trial 1) Generates formatted PDF reports with tables, charts, headers/footers, page numbers, and a table of contents. ## Dependencies - **Apache PDFBox** (3.0.1) - PDF document creation and manipulation - **Gson** (2.10.1) - JSON serialization for report metadata ## Build ```bash mvn clean package ``` ## Usage ```bash java -jar target/pdf-report-generator-1.0.0.jar -o report.pdf -t "Annual Performance Report" -a "Author Name" ``` ### Options | Flag | Description | Default | |------|-------------|---------| | `-o, --output` | Output PDF file path | `report.pdf` | | `-t, --title` | Report title | `Annual Performance Report` | | `-a, --author` | Report author name | `PDF Report Generator` | | `-h, --help` | Show usage information | | ## Features - Custom headers and footers with date and author info - Automatic page numbering - Table of contents with PDF bookmarks/outline - Bar charts and pie charts drawn directly via PDFBox - Styled data tables with alternating row colors - Cover page with title and metadata - JSON metadata sidecar output
src/main/java/pdfgen/PdfReportGenerator.java
package pdfgen;
/**
* PDF Report Generator - Generates formatted PDF reports with tables, charts,
* headers/footers, page numbers, and a table of contents.
*
* Dependencies:
* - Apache PDFBox 3.0.1
* - Gson 2.10.1
*/
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageDestination;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageFitWidthDestination;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
import java.awt.Color;
import java.io.IOException;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
public class PdfReportGenerator {
private static final float MARGIN = 60f;
private static final float PAGE_WIDTH = PDRectangle.LETTER.getWidth();
private static final float PAGE_HEIGHT = PDRectangle.LETTER.getHeight();
private static final float CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;
private static final Color DARK_BLUE = new Color(44, 62, 80);
private static final Color LIGHT_GRAY = new Color(236, 240, 241);
private static final Color MEDIUM_GRAY = new Color(149, 165, 166);
private static final Color[] CHART_COLORS = {
new Color(52, 152, 219), new Color(46, 204, 113),
new Color(231, 76, 60), new Color(243, 156, 18),
new Color(155, 89, 182), new Color(26, 188, 156)
};
private final PDDocument document;
private final String title;
private final String author;
private final List<TocEntry> tocEntries = new ArrayList<>();
private final Map<PDPage, Integer> pageNumbers = new LinkedHashMap<>();
private PDPageContentStream contentStream;
private PDPage currentPage;
private float yPosition;
private int pageCount = 0;
private PDType1Font fontBold;
private PDType1Font fontRegular;
private PDType1Font fontItalic;
public PdfReportGenerator(String title, String author) {
this.document = new PDDocument();
this.title = title;
this.author = author;
this.fontBold = new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD);
this.fontRegular = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
this.fontItalic = new PDType1Font(Standard14Fonts.FontName.HELVETICA_OBLIQUE);
}
// -----------------------------------------------------------------------
// Page management
// -----------------------------------------------------------------------
private PDPage newPage() throws IOException {
if (contentStream != null) {
contentStream.close();
}
PDPage page = new PDPage(PDRectangle.LETTER);
document.addPage(page);
pageCount++;
pageNumbers.put(page, pageCount);
currentPage = page;
contentStream = new PDPageContentStream(document, page);
yPosition = PAGE_HEIGHT - MARGIN - 30;
return page;
}
private void checkPageBreak(float requiredHeight) throws IOException {
if (yPosition - requiredHeight < MARGIN + 40) {
newPage();
}
}
// -----------------------------------------------------------------------
// Drawing primitives
// -----------------------------------------------------------------------
private void drawHeaderFooter(PDPage page, int pageNum) throws IOException {
PDPageContentStream cs = new PDPageContentStream(document, page,
PDPageContentStream.AppendMode.APPEND, true, true);
// Header bar
cs.setNonStrokingColor(DARK_BLUE);
cs.addRect(MARGIN, PAGE_HEIGHT - MARGIN + 5, CONTENT_WIDTH, 25);
cs.fill();
cs.setNonStrokingColor(Color.WHITE);
cs.beginText();
cs.setFont(fontBold, 9);
cs.newLineAtOffset(MARGIN + 8, PAGE_HEIGHT - MARGIN + 13);
cs.showText(title);
cs.endText();
String dateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("MMMM dd, yyyy"));
cs.beginText();
cs.setFont(fontRegular, 8);
float dateWidth = fontRegular.getStringWidth(dateStr) / 1000 * 8;
cs.newLineAtOffset(MARGIN + CONTENT_WIDTH - dateWidth - 8, PAGE_HEIGHT - MARGIN + 13);
cs.showText(dateStr);
cs.endText();
// Footer bar
cs.setNonStrokingColor(DARK_BLUE);
cs.addRect(MARGIN, MARGIN - 20, CONTENT_WIDTH, 18);
cs.fill();
cs.setNonStrokingColor(Color.WHITE);
cs.beginText();
cs.setFont(fontRegular, 7);
cs.newLineAtOffset(MARGIN + 8, MARGIN - 15);
cs.showText("Author: " + author);
cs.endText();
String pageStr = "Page " + pageNum;
float pageStrWidth = fontRegular.getStringWidth(pageStr) / 1000 * 7;
cs.beginText();
cs.newLineAtOffset(MARGIN + CONTENT_WIDTH / 2 - pageStrWidth / 2, MARGIN - 15);
cs.showText(pageStr);
cs.endText();
String gen = "Generated with PDF Report Generator";
float genWidth = fontRegular.getStringWidth(gen) / 1000 * 7;
cs.beginText();
cs.newLineAtOffset(MARGIN + CONTENT_WIDTH - genWidth - 8, MARGIN - 15);
cs.showText(gen);
cs.endText();
cs.close();
}
private void drawText(String text, PDType1Font font, float size, Color color) throws IOException {
contentStream.setNonStrokingColor(color);
contentStream.beginText();
contentStream.setFont(font, size);
contentStream.newLineAtOffset(MARGIN, yPosition);
contentStream.showText(text);
contentStream.endText();
yPosition -= size + 6;
}
private void drawWrappedText(String text, float fontSize) throws IOException {
List<String> lines = wrapText(text, fontRegular, fontSize, CONTENT_WIDTH);
for (String line : lines) {
checkPageBreak(fontSize + 4);
contentStream.setNonStrokingColor(Color.BLACK);
contentStream.beginText();
contentStream.setFont(fontRegular, fontSize);
contentStream.newLineAtOffset(MARGIN, yPosition);
contentStream.showText(line);
contentStream.endText();
yPosition -= fontSize + 4;
}
yPosition -= 4;
}
private List<String> wrapText(String text, PDType1Font font, float fontSize, float maxWidth) throws IOException {
List<String> lines = new ArrayList<>();
String[] words = text.split("\\s+");
StringBuilder currentLine = new StringBuilder();
for (String word : words) {
String testLine = currentLine.length() == 0 ? word : currentLine + " " + word;
float width = font.getStringWidth(testLine) / 1000 * fontSize;
if (width > maxWidth && currentLine.length() > 0) {
lines.add(currentLine.toString());
currentLine = new StringBuilder(word);
} else {
currentLine = new StringBuilder(testLine);
}
}
if (currentLine.length() > 0) {
lines.add(currentLine.toString());
}
return lines;
}
// -----------------------------------------------------------------------
// Table drawing
// -----------------------------------------------------------------------
private void drawTable(String[] headers, String[][] rows) throws IOException {
int cols = headers.length;
float colWidth = CONTENT_WIDTH / cols;
float rowHeight = 22f;
float tableHeight = rowHeight * (rows.length + 1);
checkPageBreak(Math.min(tableHeight, 100));
// Header row
contentStream.setNonStrokingColor(DARK_BLUE);
contentStream.addRect(MARGIN, yPosition - rowHeight + 4, CONTENT_WIDTH, rowHeight);
contentStream.fill();
contentStream.setNonStrokingColor(Color.WHITE);
for (int c = 0; c < cols; c++) {
contentStream.beginText();
contentStream.setFont(fontBold, 9);
contentStream.newLineAtOffset(MARGIN + c * colWidth + 5, yPosition - rowHeight + 11);
contentStream.showText(headers[c]);
contentStream.endText();
}
yPosition -= rowHeight;
// Data rows
for (int r = 0; r < rows.length; r++) {
checkPageBreak(rowHeight + 10);
Color bg = (r % 2 == 0) ? LIGHT_GRAY : Color.WHITE;
contentStream.setNonStrokingColor(bg);
contentStream.addRect(MARGIN, yPosition - rowHeight + 4, CONTENT_WIDTH, rowHeight);
contentStream.fill();
// Grid lines
contentStream.setStrokingColor(Color.GRAY);
contentStream.setLineWidth(0.5f);
contentStream.addRect(MARGIN, yPosition - rowHeight + 4, CONTENT_WIDTH, rowHeight);
contentStream.stroke();
contentStream.setNonStrokingColor(Color.BLACK);
for (int c = 0; c < cols && c < rows[r].length; c++) {
contentStream.beginText();
contentStream.setFont(fontRegular, 8);
contentStream.newLineAtOffset(MARGIN + c * colWidth + 5, yPosition - rowHeight + 11);
contentStream.showText(rows[r][c]);
contentStream.endText();
}
yPosition -= rowHeight;
}
yPosition -= 10;
}
// -----------------------------------------------------------------------
// Bar chart drawing
// -----------------------------------------------------------------------
private void drawBarChart(Map<String, Double> data, String chartTitle,
String xlabel, String ylabel) throws IOException {
float chartWidth = CONTENT_WIDTH - 60;
float chartHeight = 150f;
float barAreaX = MARGIN + 50;
float barAreaY = yPosition - chartHeight - 40;
checkPageBreak(chartHeight + 80);
// Title
drawText(chartTitle, fontBold, 11, DARK_BLUE);
yPosition -= 5;
float maxVal = data.values().stream().max(Double::compare).orElse(1.0);
int barCount = data.size();
float barWidth = chartWidth / barCount * 0.6f;
float gap = chartWidth / barCount * 0.4f;
// Y-axis
contentStream.setStrokingColor(Color.GRAY);
contentStream.setLineWidth(0.5f);
contentStream.moveTo(barAreaX, barAreaY);
contentStream.lineTo(barAreaX, barAreaY + chartHeight);
contentStream.stroke();
// Y-axis labels
for (int i = 0; i <= 4; i++) {
float val = (float) (maxVal * i / 4);
float yy = barAreaY + chartHeight * i / 4;
contentStream.setNonStrokingColor(Color.GRAY);
contentStream.beginText();
contentStream.setFont(fontRegular, 7);
contentStream.newLineAtOffset(barAreaX - 30, yy - 3);
contentStream.showText(String.format("%.1f", val));
contentStream.endText();
contentStream.setStrokingColor(new Color(220, 220, 220));
contentStream.moveTo(barAreaX, yy);
contentStream.lineTo(barAreaX + chartWidth, yy);
contentStream.stroke();
}
// Bars
int idx = 0;
for (Map.Entry<String, Double> entry : data.entrySet()) {
float x = barAreaX + idx * (barWidth + gap) + gap / 2;
float h = (float) (entry.getValue() / maxVal * chartHeight);
Color color = CHART_COLORS[idx % CHART_COLORS.length];
contentStream.setNonStrokingColor(color);
contentStream.addRect(x, barAreaY, barWidth, h);
contentStream.fill();
// Label below bar
contentStream.setNonStrokingColor(Color.BLACK);
contentStream.beginText();
contentStream.setFont(fontRegular, 7);
String label = entry.getKey();
if (label.length() > 10) label = label.substring(0, 10);
contentStream.newLineAtOffset(x + 2, barAreaY - 12);
contentStream.showText(label);
contentStream.endText();
idx++;
}
yPosition = barAreaY - 25;
}
// -----------------------------------------------------------------------
// Pie chart drawing (simple sectors)
// -----------------------------------------------------------------------
private void drawPieChart(Map<String, Double> data, String chartTitle) throws IOException {
float radius = 60f;
float centerX = MARGIN + CONTENT_WIDTH / 2 - 60;
float centerY = yPosition - radius - 30;
checkPageBreak(radius * 2 + 70);
drawText(chartTitle, fontBold, 11, DARK_BLUE);
double total = data.values().stream().mapToDouble(Double::doubleValue).sum();
double angle = 0;
int idx = 0;
// Draw pie as colored sectors approximated by triangles
for (Map.Entry<String, Double> entry : data.entrySet()) {
double sweep = entry.getValue() / total * 360;
Color color = CHART_COLORS[idx % CHART_COLORS.length];
contentStream.setNonStrokingColor(color);
// Approximate each sector with small triangle segments
for (double a = angle; a < angle + sweep; a += 2) {
double rad1 = Math.toRadians(a);
double rad2 = Math.toRadians(Math.min(a + 2, angle + sweep));
contentStream.moveTo(centerX, centerY);
contentStream.lineTo(centerX + (float)(radius * Math.cos(rad1)),
centerY + (float)(radius * Math.sin(rad1)));
contentStream.lineTo(centerX + (float)(radius * Math.cos(rad2)),
centerY + (float)(radius * Math.sin(rad2)));
contentStream.closePath();
contentStream.fill();
}
// Legend
float legendX = centerX + radius + 30;
float legendY = centerY + radius - idx * 16;
contentStream.setNonStrokingColor(color);
contentStream.addRect(legendX, legendY - 3, 10, 10);
contentStream.fill();
String pct = String.format("%s (%.1f%%)", entry.getKey(),
entry.getValue() / total * 100);
contentStream.setNonStrokingColor(Color.BLACK);
contentStream.beginText();
contentStream.setFont(fontRegular, 8);
contentStream.newLineAtOffset(legendX + 14, legendY);
contentStream.showText(pct);
contentStream.endText();
angle += sweep;
idx++;
}
yPosition = centerY - radius - 15;
}
// -----------------------------------------------------------------------
// Report generation
// -----------------------------------------------------------------------
public void generate(String outputPath) throws IOException {
// Cover page
newPage();
yPosition = PAGE_HEIGHT / 2 + 50;
drawText(title, fontBold, 26, DARK_BLUE);
yPosition -= 5;
String subtitle = "Prepared by " + author + " | "
+ LocalDate.now().format(DateTimeFormatter.ofPattern("MMMM yyyy"));
drawText(subtitle, fontItalic, 13, MEDIUM_GRAY);
yPosition -= 15;
drawWrappedText("This report provides a comprehensive analysis of organizational "
+ "performance metrics, financial summaries, and strategic insights "
+ "for the current fiscal year.", 10);
// TOC page
PDPage tocPage = newPage();
drawText("Table of Contents", fontBold, 18, DARK_BLUE);
yPosition -= 10;
// Placeholder - will record entries as sections are added
float tocYStart = yPosition;
// Section 1: Executive Summary
newPage();
tocEntries.add(new TocEntry("1. Executive Summary", currentPage, 0));
drawText("1. Executive Summary", fontBold, 16, DARK_BLUE);
yPosition -= 6;
drawWrappedText("This section provides a high-level overview of the key findings "
+ "and recommendations presented in this report. The organization has "
+ "demonstrated strong growth across multiple business units, with "
+ "notable improvements in operational efficiency and customer satisfaction.", 10);
drawWrappedText("Revenue increased by 15.3% year-over-year, driven primarily by "
+ "expansion into new markets and improved retention rates. Operating "
+ "costs were reduced by 8.2% through strategic process optimization.", 10);
tocEntries.add(new TocEntry("1.1 Key Performance Indicators", currentPage, 1));
drawText("1.1 Key Performance Indicators", fontBold, 13, DARK_BLUE);
yPosition -= 4;
drawTable(
new String[]{"Metric", "Q1", "Q2", "Q3", "Q4", "YoY Change"},
new String[][]{
{"Revenue ($M)", "12.4", "14.1", "15.8", "17.2", "+15.3%"},
{"Net Profit ($M)", "2.1", "2.5", "2.9", "3.4", "+22.1%"},
{"Customers (K)", "45.2", "48.7", "52.1", "56.8", "+12.4%"},
{"Satisfaction (%)", "87.3", "89.1", "91.5", "93.2", "+5.9pp"},
{"Employee Count", "342", "358", "371", "389", "+13.7%"}
}
);
// Section 2: Financial Analysis
newPage();
tocEntries.add(new TocEntry("2. Financial Analysis", currentPage, 0));
drawText("2. Financial Analysis", fontBold, 16, DARK_BLUE);
yPosition -= 6;
drawWrappedText("The financial performance of the organization has exceeded "
+ "projections across all major categories. Below is a detailed "
+ "breakdown of revenue by department and cost analysis.", 10);
Map<String, Double> revenueData = new LinkedHashMap<>();
revenueData.put("Engineering", 18.5);
revenueData.put("Sales", 24.3);
revenueData.put("Marketing", 8.7);
revenueData.put("Operations", 5.2);
revenueData.put("Support", 3.8);
drawBarChart(revenueData, "Revenue by Department ($M)", "Department", "Revenue ($M)");
tocEntries.add(new TocEntry("2.1 Expense Breakdown", currentPage, 1));
drawText("2.1 Expense Breakdown", fontBold, 13, DARK_BLUE);
yPosition -= 4;
Map<String, Double> expenseData = new LinkedHashMap<>();
expenseData.put("Salaries", 42.0);
expenseData.put("Infrastructure", 18.0);
expenseData.put("Marketing", 15.0);
expenseData.put("R&D", 12.0);
expenseData.put("Operations", 8.0);
expenseData.put("Other", 5.0);
drawPieChart(expenseData, "Expense Distribution");
drawTable(
new String[]{"Category", "Budget ($M)", "Actual ($M)", "Variance"},
new String[][]{
{"Salaries & Benefits", "18.5", "17.9", "-3.2%"},
{"Cloud Infrastructure", "6.2", "6.8", "+9.7%"},
{"Marketing Campaigns", "5.1", "4.7", "-7.8%"},
{"Research & Development", "4.8", "5.2", "+8.3%"},
{"Office Operations", "3.1", "2.9", "-6.5%"},
{"Travel & Events", "1.5", "1.2", "-20.0%"}
}
);
// Section 3: Growth Trends
newPage();
tocEntries.add(new TocEntry("3. Growth Trends", currentPage, 0));
drawText("3. Growth Trends", fontBold, 16, DARK_BLUE);
yPosition -= 6;
drawWrappedText("The following charts illustrate growth trends across key metrics "
+ "over the past twelve months, demonstrating consistent upward "
+ "trajectory in all measured categories.", 10);
Map<String, Double> monthlyRevenue = new LinkedHashMap<>();
double[] revValues = {10.2, 10.8, 11.1, 12.4, 12.9, 13.5, 14.1, 14.8, 15.2, 15.8, 16.5, 17.2};
String[] months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
for (int i = 0; i < months.length; i++) {
monthlyRevenue.put(months[i], revValues[i]);
}
drawBarChart(monthlyRevenue, "Monthly Revenue Trend ($M)", "Month", "Revenue ($M)");
// Section 4: Regional Performance
newPage();
tocEntries.add(new TocEntry("4. Regional Performance", currentPage, 0));
drawText("4. Regional Performance", fontBold, 16, DARK_BLUE);
yPosition -= 6;
tocEntries.add(new TocEntry("4.1 Revenue by Region", currentPage, 1));
drawText("4.1 Revenue by Region", fontBold, 13, DARK_BLUE);
yPosition -= 4;
drawTable(
new String[]{"Region", "Revenue ($M)", "Growth (%)", "Market Share", "Customers"},
new String[][]{
{"North America", "28.4", "+12.1%", "34.2%", "22,450"},
{"Europe", "19.7", "+18.5%", "23.7%", "15,820"},
{"Asia-Pacific", "15.3", "+25.3%", "18.4%", "12,100"},
{"Latin America", "8.9", "+31.2%", "10.7%", "7,240"},
{"Middle East", "5.6", "+14.8%", "6.7%", "4,180"},
{"Africa", "5.2", "+42.1%", "6.3%", "3,890"}
}
);
Map<String, Double> regionalRevenue = new LinkedHashMap<>();
regionalRevenue.put("N. America", 28.4);
regionalRevenue.put("Europe", 19.7);
regionalRevenue.put("Asia-Pac", 15.3);
regionalRevenue.put("LATAM", 8.9);
regionalRevenue.put("Mid East", 5.6);
regionalRevenue.put("Africa", 5.2);
drawBarChart(regionalRevenue, "Revenue by Region ($M)", "Region", "Revenue ($M)");
// Section 5: Conclusions
newPage();
tocEntries.add(new TocEntry("5. Conclusions & Recommendations", currentPage, 0));
drawText("5. Conclusions & Recommendations", fontBold, 16, DARK_BLUE);
yPosition -= 6;
drawWrappedText("Based on the analysis presented in this report, the organization "
+ "is well-positioned for continued growth. Key recommendations include:", 10);
String[] recommendations = {
"1. Increase investment in Asia-Pacific and African markets.",
"2. Continue optimizing operational costs through automation.",
"3. Expand the product portfolio for emerging customer needs.",
"4. Invest in employee development programs.",
"5. Establish a dedicated data analytics team."
};
for (String rec : recommendations) {
drawWrappedText(rec, 10);
}
yPosition -= 15;
drawText("This report was automatically generated using the PDF Report Generator.",
fontItalic, 8, MEDIUM_GRAY);
// Close content stream
if (contentStream != null) {
contentStream.close();
contentStream = null;
}
// Write TOC on the TOC page
PDPageContentStream tocCs = new PDPageContentStream(document, tocPage,
PDPageContentStream.AppendMode.APPEND, true, true);
float tocY = tocYStart;
for (TocEntry entry : tocEntries) {
float indent = entry.level == 0 ? 0 : 20;
PDType1Font tocFont = entry.level == 0 ? fontBold : fontRegular;
float fontSize = entry.level == 0 ? 12 : 10;
int pn = pageNumbers.getOrDefault(entry.page, 0);
tocCs.setNonStrokingColor(Color.BLACK);
tocCs.beginText();
tocCs.setFont(tocFont, fontSize);
tocCs.newLineAtOffset(MARGIN + indent, tocY);
tocCs.showText(entry.title);
tocCs.endText();
String pageStr = String.valueOf(pn);
float pageStrW = fontRegular.getStringWidth(pageStr) / 1000 * fontSize;
tocCs.beginText();
tocCs.setFont(fontRegular, fontSize);
tocCs.newLineAtOffset(MARGIN + CONTENT_WIDTH - pageStrW, tocY);
tocCs.showText(pageStr);
tocCs.endText();
tocY -= fontSize + 8;
}
tocCs.close();
// Build document outline (bookmarks)
PDDocumentOutline outline = new PDDocumentOutline();
for (TocEntry entry : tocEntries) {
if (entry.level == 0) {
PDOutlineItem item = new PDOutlineItem();
item.setTitle(entry.title);
PDPageFitWidthDestination dest = new PDPageFitWidthDestination();
dest.setPage(entry.page);
item.setDestination(dest);
outline.addLast(item);
}
}
document.getDocumentCatalog().setDocumentOutline(outline);
// Draw headers/footers on all pages
for (Map.Entry<PDPage, Integer> entry : pageNumbers.entrySet()) {
drawHeaderFooter(entry.getKey(), entry.getValue());
}
// Export report data as JSON sidecar
Gson gson = new GsonBuilder().setPrettyPrinting().create();
Map<String, Object> metadata = new LinkedHashMap<>();
metadata.put("title", title);
metadata.put("author", author);
metadata.put("generatedDate", LocalDate.now().toString());
metadata.put("totalPages", pageCount);
List<String> sections = new ArrayList<>();
for (TocEntry e : tocEntries) sections.add(e.title);
metadata.put("sections", sections);
String json = gson.toJson(metadata);
System.out.println("Report metadata:\n" + json);
document.save(outputPath);
document.close();
System.out.println("Report generated: " + outputPath);
}
// -----------------------------------------------------------------------
// TOC entry helper
// -----------------------------------------------------------------------
private static class TocEntry {
final String title;
final PDPage page;
final int level;
TocEntry(String title, PDPage page, int level) {
this.title = title;
this.page = page;
this.level = level;
}
}
// -----------------------------------------------------------------------
// CLI entry point
// -----------------------------------------------------------------------
public static void main(String[] args) throws IOException {
String output = "report.pdf";
String title = "Annual Performance Report";
String author = "PDF Report Generator";
for (int i = 0; i < args.length; i++) {
switch (args[i]) {
case "-o": case "--output":
if (i + 1 < args.length) output = args[++i];
break;
case "-t": case "--title":
if (i + 1 < args.length) title = args[++i];
break;
case "-a": case "--author":
if (i + 1 < args.length) author = args[++i];
break;
case "-h": case "--help":
System.out.println("Usage: PdfReportGenerator [-o output.pdf] [-t title] [-a author]");
return;
}
}
PdfReportGenerator generator = new PdfReportGenerator(title, author);
generator.generate(output);
}
}