Переглянути джерело

feat(exportPNG): generate pngs for multiple pages when PageFormat given. add pageWidth/Height parameters (#670, #676)

OpenSheetMusicDisplay: allow parsing of widthxheight (e.g. 800x600) in StringToPageFormat, besides formatId ("A4_P")
make sure GraphicalMusicPage is always saved in backend, to know which page we're drawing from

CanvasVexFlowBackend: give each canvas DOM element a unique id per page/backend

demo/index: add pageWidth/pageHeight parameter to allow custom PageFormats (e.g. 800x600)

tsconfig: update from es5 to es2017 to be able to compile string.includes()

package.json: add generatePNG:paged and generatePNG:paged:debug script for npm run
sschmid 5 роки тому
батько
коміт
0353facfbe

+ 10 - 1
demo/index.js

@@ -117,6 +117,8 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
         var paramPageFormat = findGetParameter('pageFormat');
         var paramPageBackgroundColor = findGetParameter('pageBackgroundColor');
         var paramBackendType = findGetParameter('backendType');
+        var paramPageWidth = findGetParameter('pageWidth');
+        var paramPageHeight = findGetParameter('pageHeight');
 
         showHeader = (paramShowHeader !== '0');
         if (paramEmbedded !== undefined) {
@@ -143,7 +145,10 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
             console.log("[OSMD] warning: measure range end parameter should not be smaller than measure range start. We've set start measure = end measure now.")
             measureRangeStart = measureRangeEnd;
         }
-        var pageFormat = paramPageFormat ? paramPageFormat : "Endless";
+        let pageFormat = paramPageFormat ? paramPageFormat : "Endless";
+        if (paramPageHeight && paramPageWidth) {
+            pageFormat = `${paramPageWidth}x${paramPageHeight}`
+        }
         var pageBackgroundColor = paramPageBackgroundColor ? "#" + paramPageBackgroundColor : undefined; // vexflow format, see OSMDOptions. can't use # in parameters.
         //console.log("demo: osmd pagebgcolor: " + pageBackgroundColor);
         var backendType = (paramBackendType && paramBackendType.toLowerCase) ? paramBackendType : "svg";
@@ -413,6 +418,10 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
             if (openSheetMusicDisplay.getLogLevel() < 2) { // debug or trace
                 console.log("[OSMD] selectSampleOnChange with " + paramOpenUrl);
             }
+            // DEBUG: cause an error for a certain sample, for testing
+            // if (paramOpenUrl.startsWith("Beethoven")) {
+            //     paramOpenUrl.causeError();
+            // }
             selectSampleOnChange(paramOpenUrl);
         } else {
             if (openSheetMusicDisplay.getLogLevel() < 2) { // debug or trace

+ 2 - 0
package.json

@@ -19,6 +19,8 @@
     "build:webpack-sourcemap": "webpack --progress --colors --config webpack.sourcemap.js",
     "start": "webpack-dev-server --progress --colors --config webpack.dev.js",
     "generatePNG": "node ./test/Util/generateDiffImagesPuppeteerLocalhost.js ./test/data/ ./export/",
+    "generatePNG:paged": "node ./test/Util/generateDiffImagesPuppeteerLocalhost.js ./test/data/ ./export 210 297 all --debug",
+    "generatePNG:paged:debug": "node ./test/Util/generateDiffImagesPuppeteerLocalhost.js ./test/data/ ./export 210 297 all --debug 5000",
     "generate:current": "node ./test/Util/generateDiffImagesPuppeteerLocalhost.js ./test/data/ ./visual_regression/current",
     "generate:current:singletest": "node test/Util/generateDiffImagesPuppeteerLocalhost.js ./test/data ./visual_regression/current .*function_test_all.*",
     "generate:blessed": "node ./test/Util/generateDiffImagesPuppeteerLocalhost.js ./test/data/ ./visual_regression/blessed",

+ 11 - 1
src/MusicalScore/Graphical/VexFlow/CanvasVexFlowBackend.ts

@@ -8,6 +8,7 @@ import {PointF2D} from "../../../Common/DataObjects/PointF2D";
 import {VexFlowConverter} from "./VexFlowConverter";
 import {BackendType} from "../../../OpenSheetMusicDisplay";
 import {EngravingRules} from "../EngravingRules";
+import {GraphicalMusicPage} from "../GraphicalMusicPage";
 
 export class CanvasVexFlowBackend extends VexFlowBackend {
     public getVexflowBackendType(): Vex.Flow.Renderer.Backends {
@@ -20,7 +21,11 @@ export class CanvasVexFlowBackend extends VexFlowBackend {
 
     public initialize(container: HTMLElement): void {
         this.canvas = document.createElement("canvas");
-        this.canvas.id = "osmdCanvasVexFlowBackendCanvas"; // needed to extract image buffer from js
+        if (!this.graphicalMusicPage) {
+            this.graphicalMusicPage = new GraphicalMusicPage(undefined);
+            this.graphicalMusicPage.PageNumber = 1;
+        }
+        this.canvas.id = "osmdCanvasVexFlowBackendCanvas" + this.graphicalMusicPage.PageNumber; // needed to extract image buffer from js
         this.inner = document.createElement("div");
         this.inner.style.position = "relative";
         this.canvas.style.zIndex = "0";
@@ -36,6 +41,11 @@ export class CanvasVexFlowBackend extends VexFlowBackend {
      * @param height Height of the canvas
      */
     public initializeHeadless(width: number = 300, height: number = 300): void {
+        if (!this.graphicalMusicPage) {
+            // not needed here yet, but just for future safety, make sure the page isn't undefined
+            this.graphicalMusicPage = new GraphicalMusicPage(undefined);
+            this.graphicalMusicPage.PageNumber = 1;
+        }
         this.canvas = document.createElement("canvas");
         (this.canvas as any).width = width;
         (this.canvas as any).height = height;

+ 27 - 13
src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -19,7 +19,7 @@ import { EngravingRules, PageFormat } from "../MusicalScore/Graphical/EngravingR
 import { AbstractExpression } from "../MusicalScore/VoiceData/Expressions/AbstractExpression";
 import { Dictionary } from "typescript-collections";
 import { NoteEnum } from "..";
-import { AutoColorSet } from "../MusicalScore";
+import { AutoColorSet, GraphicalMusicPage } from "../MusicalScore";
 import jspdf = require("jspdf-yworks/dist/jspdf.min");
 import svg2pdf = require("svg2pdf.js/dist/svg2pdf.min");
 
@@ -88,9 +88,9 @@ export class OpenSheetMusicDisplay {
         this.reset();
         //console.log("typeof content: " + typeof content);
         if (typeof content === "string") {
-
             const str: string = <string>content;
             const self: OpenSheetMusicDisplay = this;
+            // console.log("substring: " + str.substr(0, 5));
             if (str.substr(0, 4) === "\x50\x4b\x03\x04") {
                 log.debug("[OSMD] This is a zip file, unpack it first: " + str);
                 // This is a zip file, unpack it first
@@ -110,7 +110,7 @@ export class OpenSheetMusicDisplay {
                 // UTF with BOM detected, truncate first three bytes and pass along
                 return self.load(str.substr(3));
             }
-            if (str.substr(0, 5) === "<?xml") {
+            if (str.substr(0, 6).includes("<?xml")) { // first character is sometimes null, making first five characters '<?xm'.
                 log.debug("[OSMD] Finally parsing XML content, length: " + str.length);
                 // Parse the string representing an xml file
                 const parser: DOMParser = new DOMParser();
@@ -261,7 +261,7 @@ export class OpenSheetMusicDisplay {
 
         // TODO check if resize is necessary. set needResize or something when size was changed
         for (const page of this.graphic.MusicPages) {
-            const backend: VexFlowBackend = this.createBackend(this.backendType);
+            const backend: VexFlowBackend = this.createBackend(this.backendType, page);
             const sizeWarningPartTwo: string = " exceeds CanvasBackend limit of 32767. Cutting off score.";
             if (backend.getOSMDBackendType() === BackendType.Canvas && width > canvasDimensionsLimit) {
                 console.log("[OSMD] Warning: width of " + width + sizeWarningPartTwo);
@@ -642,13 +642,14 @@ export class OpenSheetMusicDisplay {
         }
     }
 
-    public createBackend(type: BackendType): VexFlowBackend {
+    public createBackend(type: BackendType, page: GraphicalMusicPage): VexFlowBackend {
         let backend: VexFlowBackend;
         if (type === undefined || type === BackendType.SVG) {
             backend = new SvgVexFlowBackend();
         } else {
             backend = new CanvasVexFlowBackend();
         }
+        backend.graphicalMusicPage = page; // the page the backend renders on. needed to identify DOM element to extract image/SVG
         backend.initialize(this.container);
         return backend;
     }
@@ -668,16 +669,29 @@ export class OpenSheetMusicDisplay {
         "Letter_P": new PageFormat(215.9, 279.4, "Letter_P")
     };
 
-    public static StringToPageFormat(formatId: string): PageFormat {
-        formatId = formatId.replace(" ", "_");
-        formatId = formatId.replace("Landscape", "L");
-        formatId = formatId.replace("Portrait", "P");
+    public static StringToPageFormat(pageFormatString: string): PageFormat {
+        let pageFormat: PageFormat = PageFormat.UndefinedPageFormat; // default: 'endless' page height, take canvas/container width
+
+        // check for widthxheight parameter, e.g. "800x600"
+        if (pageFormatString.match("^[0-9]+x[0-9]+$")) {
+            const widthAndHeight: string[] = pageFormatString.split("x");
+            const width: number = Number.parseInt(widthAndHeight[0], 10);
+            const height: number = Number.parseInt(widthAndHeight[1], 10);
+            if (width > 0 && width < 32768 && height > 0 && height < 32768) {
+                pageFormat = new PageFormat(width, height, "customPageFormatWidthHeight");
+            }
+        }
+
+        // check for formatId from OpenSheetMusicDisplay.PageFormatStandards
+        pageFormatString = pageFormatString.replace(" ", "_");
+        pageFormatString = pageFormatString.replace("Landscape", "L");
+        pageFormatString = pageFormatString.replace("Portrait", "P");
         //console.log("change format to: " + formatId);
-        let f: PageFormat = PageFormat.UndefinedPageFormat; // default: 'endless' page height, take canvas/container width
-        if (OpenSheetMusicDisplay.PageFormatStandards.hasOwnProperty(formatId)) {
-            f = OpenSheetMusicDisplay.PageFormatStandards[formatId];
+        if (OpenSheetMusicDisplay.PageFormatStandards.hasOwnProperty(pageFormatString)) {
+            pageFormat = OpenSheetMusicDisplay.PageFormatStandards[pageFormatString];
+            return pageFormat;
         }
-        return f;
+        return pageFormat;
     }
 
     /** Sets page format by string. Alternative to setOptions({pageFormat: PageFormatStandards.Endless}) for example. */

+ 75 - 27
test/Util/generateDiffImagesPuppeteerLocalhost.js

@@ -22,7 +22,25 @@ const osmdPort = 8000 // OSMD webpack server port. OSMD has to be running (npm s
 async function init () {
     console.log('[OSMD.generate] init')
 
-    const [sampleDir, imageDir, filterRegex, debugFlag, debugSleepTimeString] = process.argv.slice(2, 7)
+    let [sampleDir, imageDir, pageWidth, pageHeight, filterRegex, debugFlag, debugSleepTimeString] = process.argv.slice(2, 9)
+    if (!sampleDir || !imageDir) {
+        console.log('usage: node test/Util/generateDiffImagesPuppeteerLocalhost.js sampleDirectory imageDirectory [width|0] [height|0] [filterRegex|all] [--debug] [debugSleepTime]')
+        console.log('  (use "all" to skip filterRegex parameter)')
+        console.log('example: node ./test/Util/generateDiffImagesPuppeteerLocalhost.js ./test/data/ ./export 210 297 all --debug 5000')
+        console.log('Error: need sampleDir and imageDir. Exiting.')
+        process.exit(1)
+    }
+    console.log('sampleDir: ' + sampleDir)
+    console.log('imageDir: ' + imageDir)
+
+    let pageFormatParameter = ''
+    pageHeight = Number.parseInt(pageHeight)
+    pageWidth = Number.parseInt(pageWidth)
+    const endlessPage = !(pageHeight > 0 && pageWidth > 0)
+    if (!endlessPage) {
+        pageFormatParameter = `&pageWidth=${pageWidth}&pageHeight=${pageHeight}`
+    }
+
     const DEBUG = debugFlag === '--debug'
     // const debugSleepTime = Number.parseInt(process.env.GENERATE_DEBUG_SLEEP_TIME) || 0; // 5000 works for me [sschmidTU]
     if (DEBUG) {
@@ -36,15 +54,6 @@ async function init () {
     }
 
     const fs = require('fs')
-    console.log('sampleDir: ' + sampleDir)
-    console.log('imageDir: ' + imageDir)
-    if (!sampleDir || !imageDir) {
-        console.log('usage: node test/Util/generateDiffImagesPuppeteerLocalhost sampleDirectory imageDirectory [filterRegex|all] [--debug] [debugSleepTime]')
-        console.log('  (use "all" to skip filterRegex parameter)')
-        console.log('Error: need sampleDir and imageDir. Exiting.')
-        process.exit(1)
-    }
-
     const sampleDirFilenames = fs.readdirSync(sampleDir)
     let samplesToProcess = [] // samples we want to process/generate pngs of, excluding the filtered out files/filenames
     for (const sampleFilename of sampleDirFilenames) {
@@ -99,24 +108,49 @@ async function init () {
     })
 
     page.on('response', responseHandler)
+    if (DEBUG) {
+        // pipe console output on the page to the console node is running from, otherwise these logs from the headless browser aren't visible
+        page.on('console', msg => console.log(msg.text()))
+    }
+    page.on('error', err => console.log(err))
+    page.on('pageerror', err => console.log(err)) // this one triggers for js errors in index.js, for example
 
     // get image data
-    const getDataUrl = async (page) => {
+    const getDataUrl = async (page, sampleFilename) => {
         return page.evaluate(async () => {
             return new Promise(resolve => {
-                const canvasImage = document.getElementById('osmdCanvasVexFlowBackendCanvas')
-                var imageData = canvasImage.toDataURL()
+                const imageDataArray = []
+                let canvasImage
+
+                for (let pageNumber = 1; pageNumber < 999; pageNumber++) {
+                    canvasImage = document.getElementById('osmdCanvasVexFlowBackendCanvas' + pageNumber)
+                    if (!canvasImage) {
+                        break
+                    }
+                    if (!canvasImage.toDataURL) {
+                        console.log(`error: could not get canvas image for page ${pageNumber} for file: ${sampleFilename}`)
+                        break
+                    }
+                    imageDataArray.push(canvasImage.toDataURL())
+                }
+                // while (canvasImage = document.getElementById('osmdCanvasVexFlowBackendCanvas' + pageNumber)) {
+                //     imageData = canvasImage.toDataURL()
+                //     console.log("got em. " + pageNumber)
+                // }
                 // TODO fetch multiple pages from multiple OSMD backends
-                resolve(imageData)
+                resolve(imageDataArray)
             })
         })
     }
 
     // generate png for all given samples
     for (let i = 0; i < samplesToProcess.length; i++) {
-        const sampleFileName = encodeURIComponent(samplesToProcess[i]) // escape slashes, '&' and so on
-        const sampleParameter = `&openUrl=${sampleFileName}&endUrl`
-        const pageUrl = `http://localhost:${osmdPort}?showHeader=0&debugControls=0&backendType=canvas&pageBackgroundColor=FFFFFF${sampleParameter}`
+        const sampleFilename = encodeURIComponent(samplesToProcess[i]) // escape slashes, '&' and so on
+        const sampleParameter = `&openUrl=${sampleFilename}&endUrl`
+        const pageUrl = `http://localhost:${osmdPort}?showHeader=0&debugControls=0&backendType=canvas&pageBackgroundColor=FFFFFF` +
+            sampleParameter +
+            pageFormatParameter
+
         console.log('puppeteer: page.goto url: ' + pageUrl)
         try {
             await page.goto(pageUrl, { waitUntil: 'networkidle2' })
@@ -131,14 +165,28 @@ async function init () {
         var navigationWatcher = page.waitForNavigation()
         await Promise.race([responseWatcher, navigationWatcher])
         console.log('navigation race done')
-        const dataUrl = await getDataUrl(page)
-        // console.log('dataUrl: ' + dataUrl);
-        const imageData = dataUrl.split(';base64,').pop()
-        const imageBuffer = Buffer.from(imageData, 'base64')
-
-        var fileName = `${imageDir}/${sampleFileName}.png`
-        console.log('got image data, saving to: ' + fileName)
-        fs.writeFileSync(fileName, imageBuffer, { encoding: 'base64' })
+        const dataUrls = await getDataUrl(page, sampleFilename)
+        if (dataUrls.length === 0) {
+            console.log(`error: could not get imageData for sample: ${sampleFilename}`)
+            console.log('   (dataUrls was empty list)')
+            continue
+        }
+        for (let urlIndex = 0; urlIndex < dataUrls.length; urlIndex++) {
+            const pageNumberingString = `_${urlIndex + 1}`
+            // pageNumberingString = dataUrls.length > 0 ? pageNumberingString : '' // don't put '_1' at the end if only one page. though that may cause more work
+            var filename = `${imageDir}/${sampleFilename}${pageNumberingString}.png`
+
+            const dataUrl = dataUrls[urlIndex]
+            if (!dataUrl || !dataUrl.split) {
+                console.log(`error: could not get dataUrl (imageData) for page ${urlIndex + 1} of sample: ${sampleFilename}`)
+                continue
+            }
+            const imageData = dataUrl.split(';base64,').pop()
+            const imageBuffer = Buffer.from(imageData, 'base64')
+
+            console.log('got image data, saving to: ' + filename)
+            fs.writeFileSync(filename, imageBuffer, { encoding: 'base64' })
+        }
     }
 
     // const html = await page.content();
@@ -147,6 +195,8 @@ async function init () {
     console.log('\n[OSMD.generate] Done. Puppeteer browser closed. Exiting.')
 }
 
+init()
+
 // function start() {
 //     // await (async () => {
 //     //     init();
@@ -163,5 +213,3 @@ async function init () {
 //     $('#' + elementId).attr('width', width)
 //     $('#' + elementId).attr('height', height)
 // }
-
-init()

+ 1 - 1
tsconfig.json

@@ -1,6 +1,6 @@
 {
   "compilerOptions": {
-    "target": "es5",
+    "target": "es2017",
     "module": "commonjs",
     "moduleResolution": "node",
     "noUnusedLocals": true,