package musicxmltestsuite.report; import com.xenoage.utils.jse.io.JseFileUtils; import com.xenoage.utils.jse.io.JseStreamUtils; import com.xenoage.utils.jse.reflection.InterfaceInstance; import com.xenoage.zong.desktop.io.DocumentIO; import com.xenoage.zong.desktop.io.musicxml.in.MusicXmlScoreDocFileInput; import com.xenoage.zong.desktop.io.png.out.PngScoreDocFileOutput; import com.xenoage.zong.documents.ScoreDoc; import musicxmltestsuite.MusicXmlTestSuiteHtmlReport; import musicxmltestsuite.tests.base.Base; import musicxmltestsuite.tests.utils.ErroneousScore; import org.reflections.Reflections; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.time.Instant; import java.util.List; import static com.xenoage.utils.collections.CollectionUtils.alist; import static com.xenoage.utils.jse.JsePlatformUtils.io; import static java.util.Comparator.comparing; import static musicxmltestsuite.MusicXmlTestSuiteHtmlReport.dirReport; /** * Creates an HTML page, which brings the MusicXML test files * rendered with Lilypond face to face with their respective * Zong! renderings. * * Can be started with the main method or by running the tests * in {@link MusicXmlTestSuiteHtmlReport}. * * ImageMagick should be installed to crop the resulting images. * * @author Andreas Wenger */ public class VisualHtmlReport { private static final String appDir = "visual/app/"; private static final String lilypondDir = "visual/lilypond/"; private List<Base> tests; public static void run() throws Throwable { new VisualHtmlReport().start(); } private void start() throws Throwable { collectTests(); prepareDirectories(); for (Base test : tests) { System.out.println("Processing " + test.getFileName() + " ..."); File scoreFile = io().findNormalFile(Base.dirPath + test.getFileName()); try { renderScore(scoreFile); copyLilypondImage(scoreFile); } catch (Exception ex) { throw new Exception("Error with " + scoreFile.getName(), ex); } } writeToHtmlFile(); } /** * Gets all {@link Base} tests, except those who contain invalid scores (see {@link ErroneousScore}), * sorted by name. */ private void collectTests() throws Exception { List<Base> tests = alist(); Reflections reflections = new Reflections("musicxmltestsuite.tests.base"); for (Class<? extends Base> testClass : reflections.getSubTypesOf(Base.class)) { //ignore erroneous scores if (false == testClass.isAnnotationPresent(ErroneousScore.class)) { //easyMock library fails; it does not recognize the default methods; so we //use our own solution to instantiate the interface tests.add((Base) new InterfaceInstance(testClass).defineClass().newInstance()); } } tests.sort(comparing(Base::getFileName)); this.tests = tests; } private static void prepareDirectories() { for (String subdir : new String[]{appDir, lilypondDir}) { File dir = new File(dirReport, subdir); JseFileUtils.deleteDirectory(dir); dir.mkdirs(); } } /** * Renders the given score into an image file. */ private void renderScore(File scoreFile) throws IOException { ScoreDoc doc = DocumentIO.read(scoreFile, new MusicXmlScoreDocFileInput()); File pngFile = new File(new File(dirReport, appDir), scoreFile.getName() + ".png"); DocumentIO.write(doc, pngFile, new PngScoreDocFileOutput()); cropScoreImage(pngFile); } /** * Crops the empty margins of the given PNG file. * ImageMagick (mogrify) needs to be installed, otherwise the file will not be changed. */ private void cropScoreImage(File outFile) { try { ProcessBuilder imageMagick = new ProcessBuilder("mogrify", "-trim", "-crop", "+0-80", "+repage", "-trim", outFile.getAbsolutePath()); imageMagick.start(); } catch (Exception ex) { } } /** * Copies the lilypond rendering of the given score file into the output directory. * The lilypond rendering is expected at the path of the score file with ".png" added. * @param scoreFile */ private void copyLilypondImage(File scoreFile) throws IOException { File lilypondImage = new File(scoreFile.getAbsolutePath() + ".png"); Files.copy(lilypondImage.toPath(), new File(new File(dirReport, lilypondDir), scoreFile.getName() + ".png").toPath()); } public void writeToHtmlFile() { String html = createHtmlReport(); JseFileUtils.writeFile(html, getHtmlFilePath()); } private String getHtmlFilePath() { return dirReport + "visual.html"; } private String createHtmlReport() { String html = loadHtmlTemplate("template"); html = html.replace("[[date]]", Instant.now().toString()); html = html.replace("[[rows]]", createHtmlRows()); return html; } private String loadHtmlTemplate(String name) { return JseStreamUtils.readToString( getClass().getResourceAsStream("templates/visual/" + name + ".html")); } private String createHtmlRows() { StringBuilder ret = new StringBuilder(); for (Base test : tests) ret.append(createHtmlRow(test)); return ret.toString(); } private String createHtmlRow(Base test) { String template = loadHtmlTemplate("row"); template = template.replace("[[test-name]]", test.getFileName()); template = template.replace("[[lilypond-score]]", createHtmlCell(test, lilypondDir)); template = template.replace("[[app-score]]", createHtmlCell(test, appDir)); return template; } private String createHtmlCell(Base test, String dir) { String template = loadHtmlTemplate("cell"); String file = new File(dir, test.getFileName() + ".png").getPath(); template = template.replace("[[score-path]]", ""+file); return template; } }