feat: add Java JRXML-to-PNG rendering pipeline with pixel-level SSIM comparison
- lib/java/: Java renderer (JrxmlRenderer) using JasperReports 6.21.0 - JrxmlDebug for diagnostics, JrxmlGen for format reference - download_jars.sh for one-time dependency setup - agent/nodes.py: _render_jrxml_to_png() and _compute_pixel_similarity() - Pixel comparison integrates into validate node (SSIM < 0.4 fails) - Pixel fidelity context injected into correct_jrxml for targeted fixes - tests/test_pixel_comparison.py: 15 unit tests (render, SSIM, integration) - .gitignore: exclude lib/java/*.jar, lib/java/*.class, tmp/ - CLAUDE.md: v11 changelog documenting the rendering pipeline - All non-LLM tests pass (97/97)
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import net.sf.jasperreports.engine.*;
|
||||
import net.sf.jasperreports.engine.export.*;
|
||||
import net.sf.jasperreports.export.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.awt.image.*;
|
||||
import javax.imageio.*;
|
||||
|
||||
/**
|
||||
* Minimal JRXML → PNG renderer for pixel-level fidelity comparison.
|
||||
* Usage: java JrxmlRenderer input.jrxml output.png [scale]
|
||||
* scale: optional zoom factor (default 2.0 for readable text)
|
||||
*
|
||||
* Uses JasperReports 7.x exporter API (SimpleExporterInput / SimpleGraphics2DExporterOutput).
|
||||
*/
|
||||
public class JrxmlRenderer {
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length < 2) {
|
||||
System.err.println("Usage: java JrxmlRenderer <input.jrxml> <output.png> [scale]");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
String jrxmlPath = args[0];
|
||||
String outputPath = args[1];
|
||||
float scale = args.length >= 3 ? Float.parseFloat(args[2]) : 2.0f;
|
||||
|
||||
File jrxmlFile = new File(jrxmlPath);
|
||||
if (!jrxmlFile.exists()) {
|
||||
System.err.println("ERROR: JRXML file not found: " + jrxmlPath);
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Compile JRXML → JasperReport
|
||||
JasperReport report = JasperCompileManager.compileReport(jrxmlPath);
|
||||
|
||||
// 2. Fill with empty data source
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
JasperPrint print = JasperFillManager.fillReport(report, params, new JREmptyDataSource());
|
||||
|
||||
int pages = print.getPages().size();
|
||||
if (pages == 0) {
|
||||
System.err.println("ERROR: Report has 0 pages after filling");
|
||||
System.exit(4);
|
||||
}
|
||||
|
||||
// 3. Calculate image dimensions from page size
|
||||
// JasperReports uses 72 DPI, convert to pixels with scale
|
||||
int pageWidth = (int) Math.ceil(report.getPageWidth() * scale);
|
||||
int pageHeight = (int) Math.ceil(report.getPageHeight() * scale);
|
||||
|
||||
// 4. Render each page to a BufferedImage
|
||||
java.util.List<BufferedImage> pageImages = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < pages; i++) {
|
||||
BufferedImage pageImage = new BufferedImage(pageWidth, pageHeight, BufferedImage.TYPE_INT_RGB);
|
||||
java.awt.Graphics2D g2d = pageImage.createGraphics();
|
||||
g2d.setColor(java.awt.Color.WHITE);
|
||||
g2d.fillRect(0, 0, pageWidth, pageHeight);
|
||||
// Scale the graphics context
|
||||
g2d.scale(scale, scale);
|
||||
|
||||
JRGraphics2DExporter exporter = new JRGraphics2DExporter();
|
||||
exporter.setExporterInput(new SimpleExporterInput(print));
|
||||
|
||||
SimpleGraphics2DExporterOutput output = new SimpleGraphics2DExporterOutput();
|
||||
output.setGraphics2D(g2d);
|
||||
exporter.setExporterOutput(output);
|
||||
|
||||
SimpleGraphics2DReportConfiguration config = new SimpleGraphics2DReportConfiguration();
|
||||
config.setPageIndex(i);
|
||||
exporter.setConfiguration(config);
|
||||
|
||||
exporter.exportReport();
|
||||
g2d.dispose();
|
||||
pageImages.add(pageImage);
|
||||
}
|
||||
|
||||
// 5. Combine pages into single tall image
|
||||
int totalHeight = 0;
|
||||
for (BufferedImage img : pageImages) {
|
||||
totalHeight += img.getHeight();
|
||||
}
|
||||
|
||||
BufferedImage combined = new BufferedImage(pageWidth, totalHeight, BufferedImage.TYPE_INT_RGB);
|
||||
java.awt.Graphics2D g = combined.createGraphics();
|
||||
g.setColor(java.awt.Color.WHITE);
|
||||
g.fillRect(0, 0, pageWidth, totalHeight);
|
||||
|
||||
int yOffset = 0;
|
||||
for (BufferedImage img : pageImages) {
|
||||
g.drawImage(img, 0, yOffset, null);
|
||||
yOffset += img.getHeight();
|
||||
}
|
||||
g.dispose();
|
||||
|
||||
// 6. Write PNG
|
||||
ImageIO.write(combined, "png", new File(outputPath));
|
||||
|
||||
System.out.println("OK: " + outputPath
|
||||
+ " (pages=" + pages
|
||||
+ ", size=" + pageWidth + "x" + totalHeight
|
||||
+ ", scale=" + scale + ")");
|
||||
} catch (JRException e) {
|
||||
System.err.println("JASPER_ERROR: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
System.exit(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user