Przeglądaj źródła

feat(Export): Add SVG export to OSMD and generateImages_browserless.js -> png|svg option (#670)

integration of PR #932, SVG export code kindly contributed by @hallvors

in PR #932 SVG export was a separate generateSVG script,
but it was mostly identical to generateImages,
only changing some canvas/png elements to SVG,
so it made more sense to integrate the functionality into generateImages_browserless.js,
adding a new command line option png|svg.
sschmid 4 lat temu
rodzic
commit
8cf4567e19

+ 12 - 10
package.json

@@ -20,16 +20,18 @@
     "build:webpack-sourcemap": "webpack --progress --colors --config webpack.sourcemap.js",
     "start": "webpack-dev-server --progress --colors --config webpack.dev.js",
     "start:local": "webpack-dev-server --progress --colors --config webpack.local.js",
-    "generatePNG": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./export 1000 1600 allSmall --debug",
-    "generatePNG:single": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./export 0 0 ^Beethoven",
-    "generatePNG:legacyslow": "node ./test/Util/generateDiffImagesPuppeteerLocalhost.js ./test/data ./export 0 0 all",
-    "generatePNG:paged": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./export 210 297 allSmall",
-    "generatePNG:paged:debug": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./export 210 297 all --debug 5000",
-    "generatePNG:paged:single": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./export 0 0 ^Beethoven",
-    "generate:current": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./visual_regression/current 0 0 allSmall --osmdtesting",
-    "generate:current:debug": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./visual_regression/current 0 0 allSmall --debugosmdtesting",
-    "generate:current:singletest": "node test/Util/generateImages_browserless.js ../../build ./test/data ./visual_regression/current 0 0 ^Beethoven --osmdtestingsingle",
-    "generate:blessed": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./visual_regression/blessed 0 0 allSmall --osmdtesting",
+    "generatePNG": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./export png 0 0 allSmall --osmdtesting",
+    "generateSVG": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./export/svg svg 0 0 allSmall --osmdtesting",
+    "generatePNG:debug": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./export png 0 0 allSmall --debugosmdtesting",
+    "generatePNG:single": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./export png 0 0 ^Beethoven",
+    "generatePNG:legacyslow": "node ./test/Util/generateDiffImagesPuppeteerLocalhost.js ./test/data ./export png 0 0 all",
+    "generatePNG:paged": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./export png 210 297 allSmall",
+    "generatePNG:paged:debug": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./export png 210 297 all --debug 5000",
+    "generatePNG:paged:single": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./export png 0 0 ^Beethoven",
+    "generate:current": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./visual_regression/current png 0 0 allSmall --osmdtesting",
+    "generate:current:debug": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./visual_regression/current png 0 0 allSmall --debugosmdtesting",
+    "generate:current:singletest": "node test/Util/generateImages_browserless.js ../../build ./test/data ./visual_regression/current png 0 0 ^Beethoven --osmdtestingsingle",
+    "generate:blessed": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./visual_regression/blessed png 0 0 allSmall --osmdtesting",
     "test:visual": "bash ./test/Util/visual_regression.sh ./visual_regression",
     "test:visual:singletest": "sh ./test/Util/visual_regression.sh ./visual_regression Beethoven",
     "fix-memory-limit": "cross-env NODE_OPTIONS=--max_old_space_size=4096"

+ 7 - 3
src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -317,9 +317,13 @@ export class OpenSheetMusicDisplay {
         }
     }
 
-    public export(): void {
-        if (this.backend instanceof SvgVexFlowBackend) {
-            (this.backend as SvgVexFlowBackend).export();
+    // for now SVG only, see generateImages_browserless (PNG/SVG)
+    public exportSVG(): void {
+        for (const backend of this.drawer?.Backends) {
+            if (backend instanceof SvgVexFlowBackend) {
+                (backend as SvgVexFlowBackend).export();
+            }
+            // if we add CanvasVexFlowBackend exporting, rename function to export() or exportImages() again
         }
     }
 

+ 66 - 34
test/Util/generateImages_browserless.js

@@ -1,17 +1,18 @@
 /*
   Render each OSMD sample, grab the generated images, and
-  dump them into a local directory as PNG files.
+  dump them into a local directory as PNG or SVG files.
 
   inspired by Vexflow's generate_png_images and vexflow-tests.js
 
-  This can be used to generate PNGs from OSMD without a browser.
-  It's also used with the visual regression test system in
+  This can be used to generate PNGs or SVGs from OSMD without a browser.
+  It's also used with the visual regression test system (using PNGs) in
   `tools/visual_regression.sh`
   (see package.json, used with npm run generate:blessed and generate:current, then test:visual).
 
-  Note: this script needs to "fake" quite a few browser elements, like window, document, and a Canvas HTMLElement,
+  Note: this script needs to "fake" quite a few browser elements, like window, document,
+  and a Canvas HTMLElement (for PNG) or the DOM (for SVG)   ,
   which otherwise are missing in pure nodejs, causing errors in OSMD.
-  For that it needs the canvas package installed.
+  For PNG it needs the canvas package installed.
   There are also some hacks needed to set the container size (offsetWidth) correctly.
 
   Otherwise you'd need to run a headless browser, which is way slower,
@@ -26,14 +27,14 @@ function sleep (ms) {
 
 // global variables
 //   (without these being global, we'd have to pass many of these values to the generateSampleImage function)
-let [osmdBuildDir, sampleDir, imageDir, pageWidth, pageHeight, filterRegex, mode, debugSleepTimeString] = process.argv.slice(2, 10)
-if (!osmdBuildDir || !sampleDir || !imageDir) {
+let [osmdBuildDir, sampleDir, imageDir, imageFormat, pageWidth, pageHeight, filterRegex, mode, debugSleepTimeString] = process.argv.slice(2, 10)
+if (!osmdBuildDir || !sampleDir || !imageDir || (imageFormat !== 'png' && imageFormat !== 'svg')) {
     console.log('usage: ' +
-        'node test/Util/generateImages_browserless.js osmdBuildDir sampleDirectory imageDirectory [width|0] [height|0] [filterRegex|all|allSmall] [--debug|--osmdtesting] [debugSleepTime]')
+        'node test/Util/generateImages_browserless.js osmdBuildDir sampleDirectory imageDirectory svg|png [width|0] [height|0] [filterRegex|all|allSmall] [--debug|--osmdtesting] [debugSleepTime]')
     console.log('  (use pageWidth and pageHeight 0 to not divide the rendering into pages (endless page))')
     console.log('  (use "all" to skip filterRegex parameter. "allSmall" with --osmdtesting skips two huge OSMD samples that take forever to render)')
-    console.log('example: node test/Util/generateImages_browserless.js ../../build ./test/data/ ./export 210 297 allSmall --debug 5000')
-    console.log('Error: need osmdBuildDir, sampleDir and imageDir. Exiting.')
+    console.log('example: node test/Util/generateImages_browserless.js ../../build ./test/data/ ./export png 210 297 allSmall --debug 5000')
+    console.log('Error: need osmdBuildDir, sampleDir, imageDir and svg|png arguments. Exiting.')
     process.exit(1)
 }
 let pageFormat
@@ -41,6 +42,9 @@ let pageFormat
 if (!mode) {
     mode = ''
 }
+if (imageFormat !== 'svg') {
+    imageFormat = 'png'
+}
 
 let OSMD // can only be required once window was simulated
 const FS = require('fs')
@@ -64,6 +68,7 @@ async function init () {
     }
     debug('sampleDir: ' + sampleDir, DEBUG)
     debug('imageDir: ' + imageDir, DEBUG)
+    debug('imageFormat: ' + imageFormat, DEBUG)
 
     pageFormat = 'Endless'
     pageWidth = Number.parseInt(pageWidth)
@@ -91,7 +96,9 @@ async function init () {
     global.XMLHttpRequest = window.XMLHttpRequest
     global.DOMParser = window.DOMParser
     global.Node = window.Node
-    global.Canvas = window.Canvas
+    if (imageFormat === 'png') {
+        global.Canvas = window.Canvas
+    }
 
     // fix Blob not found (to support external modules like is-blob)
     global.Blob = require('cross-blob')
@@ -179,9 +186,10 @@ async function init () {
         }
     }
 
+    const backend = imageFormat === 'png' ? 'canvas' : 'svg'
     const osmdInstance = new OSMD.OpenSheetMusicDisplay(div, {
         autoResize: false,
-        backend: 'canvas',
+        backend: backend,
         pageBackgroundColor: '#FFFFFF',
         pageFormat: pageFormat
         // defaultFontFamily: 'Arial',
@@ -279,41 +287,65 @@ async function generateSampleImage (sampleFilename, directory, osmdInstance, osm
     debug('xml loaded', DEBUG)
     try {
         osmdInstance.render()
+        // there were reports that await could help here, but render isn't a synchronous function, and it seems to work. see #932
     } catch (ex) {
         console.log('renderError: ' + ex)
     }
     debug('rendered', DEBUG)
 
-    const dataUrls = []
+    const markupStrings = [] // svg
+    const dataUrls = [] // png
     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
+    for (let pageNumber = 1; pageNumber < Number.POSITIVE_INFINITY; pageNumber++) {
+        if (imageFormat === 'png') {
+            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
+            }
+            dataUrls.push(canvasImage.toDataURL())
+        } else if (imageFormat === 'svg') {
+            let svgElement = document.getElementById('osmdSvgPage' + pageNumber)
+            if (!svgElement) {
+                break
+            }
+            // The important xmlns attribute is not serialized unless we set it here
+            svgElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
+            markupStrings.push(svgElement.outerHTML)
         }
-        dataUrls.push(canvasImage.toDataURL())
     }
-    for (let urlIndex = 0; urlIndex < dataUrls.length; urlIndex++) {
-        const pageNumberingString = `${urlIndex + 1}`
+
+    for (let pageIndex = 0; pageIndex < Math.max(dataUrls.length, markupStrings.length); pageIndex++) {
+        const pageNumberingString = `${pageIndex + 1}`
         const skybottomlineString = includeSkyBottomLine ? 'skybottomline_' : ''
         // pageNumberingString = dataUrls.length > 0 ? pageNumberingString : '' // don't put '_1' at the end if only one page. though that may cause more work
-        var pageFilename = `${imageDir}/${sampleFilename}_${skybottomlineString}${pageNumberingString}.png`
+        var pageFilename = `${imageDir}/${sampleFilename}_${skybottomlineString}${pageNumberingString}.${imageFormat}`
 
-        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')
+        if (imageFormat === 'png') {
+            const dataUrl = dataUrls[pageIndex]
+            if (!dataUrl || !dataUrl.split) {
+                console.log(`error: could not get dataUrl (imageData) for page ${pageIndex + 1} of sample: ${sampleFilename}`)
+                continue
+            }
+            const imageData = dataUrl.split(';base64,').pop()
+            const imageBuffer = Buffer.from(imageData, 'base64')
+
+            debug('got image data, saving to: ' + pageFilename, DEBUG)
+            FS.writeFileSync(pageFilename, imageBuffer, { encoding: 'base64' })
+        } else if (imageFormat === 'svg') {
+            const markup = markupStrings[pageIndex]
+            if (!markup) {
+                console.log(`error: could not get markup (SVG data) for page ${pageIndex + 1} of sample: ${sampleFilename}`)
+                continue
+            }
 
-        debug('got image data, saving to: ' + pageFilename, DEBUG)
-        FS.writeFileSync(pageFilename, imageBuffer, { encoding: 'base64' })
+            debug('got svg markup data, saving to: ' + pageFilename, DEBUG)
+            FS.writeFileSync(pageFilename, markup, { encoding: 'utf-8' })
+        }
 
         // debug: log memory usage
         // const usage = process.memoryUsage()