Files
mdg-filius/tools/java-oracle/src/ReportRun.java
T
julian.kiedaischandClaude Opus 5.5 b8b999fd66 feat: F4.8d the documentation mode's report as a PDF, as Java's
ReportRun reads Java's report back with iText: every piece of text with its
place, font, size and colour. The editor lays the report out by iText's
rules (A4, leading 1.5, loose chunks in the current line, tables 80 % wide
with padding 2, fixed traffic columns, page breaks) and draws it with
pdf-lib in the PDF's own Helvetica, widths without kerning as iText measures.
All device pages of Internet_Komplett, the first page and the data exchange
of einfaches_rechnernetz_komplett match Java piece by piece; Java's iText
reads our PDF with the same text in the same places. The sidebar gains
Java's "Report"; the frames come from the room by a short subscription.
Not adopted: true/false for DHCP (Java's own Ja/Nein), hash table orders,
an image running off the page, the report writing back into the model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:35:35 +00:00

238 lines
11 KiB
Java

// FILIUS' „Report“ in the documentation mode measured (ReportGenerator):
// each project opened as the menu opens it, run for a while when asked (its
// frames in FILIUS' packet log, the Lauscher, as after a simulation), then
// the PDF Java writes, read back with iText's own parser: its pages and
// their size, every piece of text with its place, font, size and colour,
// every image with its place and size, the document's title and creator;
// and what the report was made of (the Lauscher's cards, the nodes' cards).
// An empty project too (no file). The reference for the editor's report
// (packages/editor/test/report.test.ts).
//
// Usage: java -cp filius.jar:lib/*:classes ReportRun <out.json> <pdf folder> <file.fls>[:<milliseconds to run>]...
// java -cp filius.jar:lib/*:classes ReportRun --read <out.json> <file.pdf>... (a PDF read back alone, with its SHA-256)
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.parser.GraphicsState;
import com.itextpdf.text.pdf.parser.ImageRenderInfo;
import com.itextpdf.text.pdf.parser.Matrix;
import com.itextpdf.text.pdf.parser.PdfReaderContentParser;
import com.itextpdf.text.pdf.parser.RenderListener;
import com.itextpdf.text.pdf.parser.TextRenderInfo;
import com.itextpdf.text.pdf.parser.Vector;
import filius.gui.GUIContainer;
import filius.gui.documentation.ReportGenerator;
import filius.gui.netzwerksicht.GUIKnotenItem;
import filius.hardware.NetzwerkInterface;
import filius.hardware.knoten.InternetKnoten;
import filius.rahmenprogramm.Information;
import filius.rahmenprogramm.SzenarioVerwaltung;
import filius.rahmenprogramm.nachrichten.Lauscher;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.SwingUtilities;
public class ReportRun {
static float round(float value) {
return Math.round(value * 100) / 100f;
}
static String colour(BaseColor color) {
return color == null ? null : color.getRed() + "," + color.getGreen() + "," + color.getBlue();
}
/** Every piece of text and every image of a page, in the order the page draws them. */
static class Listener implements RenderListener {
final List<Object> texts = new ArrayList<>();
final List<Object> images = new ArrayList<>();
public void beginTextBlock() {}
public void endTextBlock() {}
public void renderText(TextRenderInfo info) {
// Spaces alone (iText writes the ends of lines and Chunk.NEWLINE so) carry nothing.
if (info.getText().trim().isEmpty()) return;
try {
Field field = TextRenderInfo.class.getDeclaredField("gs");
field.setAccessible(true);
GraphicsState state = (GraphicsState) field.get(info);
Vector start = info.getBaseline().getStartPoint();
// x|y|font|size|colour|text, one string a piece, so that the file stays small.
texts.add(round(start.get(Vector.I1)) + "|" + round(start.get(Vector.I2)) + "|" + info.getFont().getPostscriptFontName() + "|"
+ round(state.getFontSize()) + "|" + colour(info.getFillColor()) + "|" + info.getText());
} catch (ReflectiveOperationException error) {
throw new IllegalStateException(error);
}
}
public void renderImage(ImageRenderInfo info) {
Matrix matrix = info.getImageCTM();
Map<String, Object> image = new LinkedHashMap<>();
image.put("x", round(matrix.get(Matrix.I31)));
image.put("y", round(matrix.get(Matrix.I32)));
image.put("width", round(matrix.get(Matrix.I11)));
image.put("height", round(matrix.get(Matrix.I22)));
try {
image.put("pixels", List.of(info.getImage().getBufferedImage().getWidth(), info.getImage().getBufferedImage().getHeight()));
} catch (Exception error) {
image.put("pixels", null);
}
images.add(image);
}
}
static Map<String, Object> read(String pdf) throws Exception {
Map<String, Object> result = new LinkedHashMap<>();
PdfReader reader = new PdfReader(pdf);
Map<String, String> info = reader.getInfo();
result.put("title", info.get("Title"));
result.put("creator", info.get("Creator"));
List<Object> pages = new ArrayList<>();
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
for (int number = 1; number <= reader.getNumberOfPages(); number++) {
Listener listener = parser.processContent(number, new Listener());
Map<String, Object> page = new LinkedHashMap<>();
page.put("size", List.of(round(reader.getPageSize(number).getWidth()), round(reader.getPageSize(number).getHeight())));
page.put("texts", listener.texts);
page.put("images", listener.images);
pages.add(page);
}
result.put("pages", pages);
reader.close();
return result;
}
static Map<String, Object> measure(String name, File pdfFolder) throws Exception {
Map<String, Object> result = new LinkedHashMap<>();
// What the report is made of: the Lauscher's cards and every card of the nodes.
List<Object> packetLogCards = new ArrayList<>();
for (String id : Lauscher.getLauscher().getInterfaceIDs()) {
Map<String, Object> card = new LinkedHashMap<>();
card.put("mac", id);
card.put("rows", Lauscher.getLauscher().getDaten(id, true, 0).length);
packetLogCards.add(card);
}
result.put("packetLogCards", packetLogCards);
List<Object> nodes = new ArrayList<>();
for (GUIKnotenItem item : GUIContainer.getGUIContainer().getKnotenItems()) {
Map<String, Object> node = new LinkedHashMap<>();
node.put("name", item.getKnoten().holeAnzeigeName());
node.put("type", item.getKnoten().getClass().getSimpleName());
List<Object> macs = new ArrayList<>();
if (item.getKnoten() instanceof InternetKnoten) {
for (NetzwerkInterface nic : ((InternetKnoten) item.getKnoten()).getNetzwerkInterfaces()) macs.add(nic.getMac());
}
node.put("macs", macs);
nodes.add(node);
}
result.put("nodes", nodes);
String pdf = new File(pdfFolder, name.replace(".fls", "") + ".pdf").getPath();
ReportGenerator.getInstance().generateReport(pdf);
result.put("pdf", read(pdf));
return result;
}
public static void main(String[] args) throws Exception {
// FILIUS' window keeps the JVM alive: end it on any failure too.
try {
run(args);
} catch (Throwable error) {
error.printStackTrace();
System.exit(1);
}
System.exit(0);
}
interface Step {
void run() throws Exception;
}
static void onEdt(Step step) throws Exception {
Exception[] failed = new Exception[1];
SwingUtilities.invokeAndWait(() -> {
try {
step.run();
} catch (Exception error) {
failed[0] = error;
}
});
if (failed[0] != null) throw failed[0];
}
static void run(String[] args) throws Exception {
if (args[0].equals("--read")) {
Map<String, Object> read = new LinkedHashMap<>();
for (int index = 2; index < args.length; index++) {
Map<String, Object> pdf = read(args[index]);
byte[] digest = java.security.MessageDigest.getInstance("SHA-256").digest(java.nio.file.Files.readAllBytes(Path.of(args[index])));
StringBuilder hex = new StringBuilder();
for (byte each : digest) hex.append(String.format("%02x", each));
pdf.put("sha256", hex.toString());
read.put(Path.of(args[index]).getFileName().toString(), pdf);
}
try (FileOutputStream out = new FileOutputStream(args[1])) {
out.write((FlsOracle.Json.write(read, "") + "\n").getBytes(StandardCharsets.UTF_8));
}
return;
}
GUIContainer.getGUIContainer();
File pdfFolder = new File(args[1]);
pdfFolder.mkdirs();
Map<String, Object> output = new LinkedHashMap<>();
Map<String, Object> projects = new LinkedHashMap<>();
for (int index = 2; index < args.length; index++) {
String[] parts = args[index].split(":");
String file = parts[0];
long runFor = parts.length > 1 ? Long.parseLong(parts[1]) : 0;
String name = Path.of(file).getFileName().toString();
onEdt(() -> {
// As GUIMainMenu opens a file; the packet log starts empty, as after Lauscher.reset.
Information.getInformation().reset();
Lauscher.getLauscher().reset();
SzenarioVerwaltung.getInstance().laden(file, GUIContainer.getGUIContainer().getKnotenItems(),
GUIContainer.getGUIContainer().getCableItems(), GUIContainer.getGUIContainer().getDocuItems());
GUIContainer.getGUIContainer().setProperty(null);
GUIContainer.getGUIContainer().updateViewport();
GUIContainer.getGUIContainer().updateCables();
});
if (runFor > 0) {
// As GUIMainMenu.selectMode starts and ends a simulation: every node in the window's order.
for (GUIKnotenItem item : GUIContainer.getGUIContainer().getKnotenItems()) item.getKnoten().getSystemSoftware().starten();
Thread.sleep(runFor);
for (GUIKnotenItem item : GUIContainer.getGUIContainer().getKnotenItems()) item.getKnoten().getSystemSoftware().beenden();
Thread.sleep(500);
}
Map<String, Object>[] measured = new Map[1];
onEdt(() -> measured[0] = measure(name, pdfFolder));
projects.put(name, measured[0]);
}
output.put("projects", projects);
Map<String, Object>[] empty = new Map[1];
onEdt(() -> {
GUIContainer container = GUIContainer.getGUIContainer();
container.getKnotenItems().clear();
container.getCableItems().clear();
container.getDocuItems().clear();
SzenarioVerwaltung.getInstance().reset();
Lauscher.getLauscher().reset();
container.updateViewport();
empty[0] = measure("empty.fls", pdfFolder);
});
output.put("empty", empty[0]);
try (FileOutputStream out = new FileOutputStream(args[0])) {
out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8));
}
}
}