// FILIUS' „Exportieren“ in the documentation mode measured // (GUIContainer.createNetworkImage, GUIPrintPanel): each project opened as // the menu opens it, then the print panel Java paints — the bounds of every // device, cable and documentation element in it, the clip bounds of its // network, documentation and footer panels, the footer's text, place and // size, the rectangle cut out of the panel — and the image itself: its size, // whether its background is transparent, the colours of its footer, the fill // of a rectangle in it. An empty project too (no element at all). The // reference for the editor's image export (packages/editor/test/networkImage.test.ts). // // Usage: java -cp filius.jar:lib/*:classes ExportRun ... import filius.gui.GUIContainer; import filius.gui.netzwerksicht.GUIDocuItem; import filius.gui.netzwerksicht.GUIKabelItem; import filius.gui.netzwerksicht.GUIKnotenItem; import filius.gui.netzwerksicht.GUIMainArea; import filius.gui.netzwerksicht.GUIPrintPanel; import filius.rahmenprogramm.Information; import filius.rahmenprogramm.SzenarioVerwaltung; import java.awt.Component; import java.awt.FontMetrics; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileOutputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; 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 java.util.TreeMap; import javax.imageio.ImageIO; import javax.swing.JLabel; import javax.swing.SwingUtilities; public class ExportRun { static Object get(Object owner, Class type, String name) throws Exception { Field field = type.getDeclaredField(name); field.setAccessible(true); return field.get(owner); } static List bounds(Component component) { Rectangle rect = component.getBounds(); return List.of(rect.x, rect.y, rect.width, rect.height); } static Map area(GUIMainArea panel) throws Exception { Map shown = new LinkedHashMap<>(); for (String name : new String[] { "minX", "maxX", "minY", "maxY" }) { shown.put(name, get(panel, GUIMainArea.class, name)); } return shown; } static String rgba(int argb) { return ((argb >> 16) & 255) + "," + ((argb >> 8) & 255) + "," + (argb & 255) + "," + ((argb >>> 24) & 255); } /** The colours inside a rectangle of the image (in image coordinates), most frequent first, at most five. */ static List colours(BufferedImage image, int x, int y, int width, int height) { Map counts = new TreeMap<>(); for (int row = Math.max(0, y); row < Math.min(image.getHeight(), y + height); row++) { for (int column = Math.max(0, x); column < Math.min(image.getWidth(), x + width); column++) { counts.merge(rgba(image.getRGB(column, row)), 1, Integer::sum); } } List> sorted = new ArrayList<>(counts.entrySet()); sorted.sort((lhs, rhs) -> rhs.getValue() - lhs.getValue()); List shown = new ArrayList<>(); for (Map.Entry entry : sorted.subList(0, Math.min(5, sorted.size()))) { shown.add(List.of(entry.getKey(), entry.getValue())); } return shown; } static Map measure(String name, File pngFolder) throws Exception { Map result = new LinkedHashMap<>(); GUIContainer container = GUIContainer.getGUIContainer(); List nodes = new ArrayList<>(); for (GUIKnotenItem item : container.getKnotenItems()) { Map node = new LinkedHashMap<>(); node.put("name", item.getKnoten().holeAnzeigeName()); node.put("bounds", bounds(item.getImageLabel())); nodes.add(node); } result.put("nodes", nodes); List cables = new ArrayList<>(); for (GUIKabelItem item : container.getCableItems()) cables.add(bounds(item.getKabelpanel())); result.put("cables", cables); List docs = new ArrayList<>(); for (GUIDocuItem item : container.getDocuItems()) { Map doc = new LinkedHashMap<>(); doc.put("type", item.getType()); doc.put("bounds", bounds(item.asDocuElement())); doc.put("color", item.getColor() == null ? null : rgba(item.getColor().getRGB())); docs.add(doc); } result.put("docs", docs); result.put("path", SzenarioVerwaltung.getInstance().holePfad() == null ? null : name); Method prepare = GUIContainer.class.getDeclaredMethod("prepareExportPanel"); prepare.setAccessible(true); GUIPrintPanel panel = (GUIPrintPanel) prepare.invoke(container); result.put("panelSize", List.of(panel.getWidth(), panel.getHeight())); result.put("network", area((GUIMainArea) get(panel, GUIPrintPanel.class, "networkPanel"))); result.put("documentation", area((GUIMainArea) get(panel, GUIPrintPanel.class, "docuPanel"))); GUIMainArea footer = (GUIMainArea) get(panel, GUIPrintPanel.class, "footer"); result.put("footerArea", area(footer)); JLabel label = (JLabel) get(footer, footer.getClass(), "footer"); Map footerLabel = new LinkedHashMap<>(); FontMetrics metrics = label.getFontMetrics(label.getFont()); footerLabel.put("text", label.getText().equals(SzenarioVerwaltung.getInstance().holePfad()) ? "" : label.getText()); footerLabel.put("bounds", bounds(label)); footerLabel.put("stringWidth", metrics.stringWidth(label.getText())); footerLabel.put("fontHeight", metrics.getHeight()); footerLabel.put("foreground", rgba(label.getForeground().getRGB())); result.put("footer", footerLabel); try { int clipX = panel.getClipX(), clipY = panel.getClipY(); result.put("clip", List.of(clipX, clipY, panel.getClipWidth(), panel.getClipHeight())); container.updateViewport(); BufferedImage image = container.createNetworkImage(); result.put("imageSize", List.of(image.getWidth(), image.getHeight())); result.put("cornerPixel", rgba(image.getRGB(0, 0))); // The footer in image coordinates: the panel's own minus the clip's origin. Rectangle at = label.getBounds(); result.put("footerColours", colours(image, at.x - clipX, at.y - clipY, at.width, at.height)); for (GUIDocuItem item : container.getDocuItems()) { if (item.getType() != GUIDocuItem.RECT) continue; Rectangle rect = item.asDocuElement().getBounds(); // Inside the panel the elements lie 10 to the right and down (EMPTY_BORDER). result.put("rectangleCentre", colours(image, rect.x + rect.width / 2 + 10 - clipX, rect.y + 4 + 10 - clipY, 1, 1)); result.put("rectangleCentreColor", rgba(item.getColor().getRGB())); break; } ImageIO.write(image, "png", new File(pngFolder, name.replace(".fls", "") + ".png")); } catch (RuntimeException error) { result.put("error", error.getClass().getSimpleName()); } container.updateViewport(); 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); } static void run(String[] args) throws Exception { GUIContainer.getGUIContainer(); File pngFolder = new File(args[1]); pngFolder.mkdirs(); Map output = new LinkedHashMap<>(); Map projects = new LinkedHashMap<>(); for (int index = 2; index < args.length; index++) { String file = args[index]; String name = Path.of(file).getFileName().toString(); Object[] measured = new Object[1]; Exception[] failed = new Exception[1]; SwingUtilities.invokeAndWait(() -> { try { // As GUIMainMenu opens a file. Information.getInformation().reset(); SzenarioVerwaltung.getInstance().laden(file, GUIContainer.getGUIContainer().getKnotenItems(), GUIContainer.getGUIContainer().getCableItems(), GUIContainer.getGUIContainer().getDocuItems()); GUIContainer.getGUIContainer().setProperty(null); GUIContainer.getGUIContainer().updateViewport(); GUIContainer.getGUIContainer().updateCables(); measured[0] = measure(name, pngFolder); } catch (Exception error) { failed[0] = error; } }); if (failed[0] != null) throw failed[0]; projects.put(name, measured[0]); } output.put("projects", projects); // An empty project: nothing on the surface, no file. Object[] empty = new Object[1]; Exception[] failed = new Exception[1]; SwingUtilities.invokeAndWait(() -> { try { GUIContainer container = GUIContainer.getGUIContainer(); container.getKnotenItems().clear(); container.getCableItems().clear(); container.getDocuItems().clear(); SzenarioVerwaltung.getInstance().reset(); container.updateViewport(); empty[0] = measure("empty.fls", pngFolder); } catch (Exception error) { failed[0] = error; } }); if (failed[0] != null) throw failed[0]; output.put("empty", empty[0]); try (FileOutputStream out = new FileOutputStream(args[0])) { out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8)); } } }