| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 | /*  Render each OSMD sample, grab the generated images, and  dump them into a local directory as PNG 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  `tools/visual_regression.sh`.  Note: this script needs to "fake" quite a few browser elements, like window, document, and a Canvas HTMLElement.  For that 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,  see the semi-obsolete generateDiffImagesPuppeteerLocalhost.js*/function sleep (ms) {    return new Promise((resolve) => {        setTimeout(resolve, ms)    })}async function init () {    console.log('[OSMD.generate] init')    let [osmdBuildDir, sampleDir, imageDir, pageWidth, pageHeight, filterRegex, debugFlag, debugSleepTimeString] = process.argv.slice(2, 10)    if (!osmdBuildDir || !sampleDir || !imageDir) {        console.log('usage: ' +            'node test/Util/generateImages_browserless.js osmdBuildDir sampleDirectory imageDirectory [width|0] [height|0] [filterRegex|all|allSmall] [--debug] [debugSleepTime]')        console.log('  (use "all" to skip filterRegex parameter. "allSmall" 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 sampleDir and imageDir. Exiting.')        process.exit(1)    }    const DEBUG = debugFlag === '--debug'    // const debugSleepTime = Number.parseInt(process.env.GENERATE_DEBUG_SLEEP_TIME) || 0; // 5000 works for me [sschmidTU]    if (DEBUG) {        console.log(' (note that --debug slows down the script by about 0.3s per file, through logging)')        const debugSleepTimeMs = Number.parseInt(debugSleepTimeString)        if (debugSleepTimeMs > 0) {            console.log('debug sleep time: ' + debugSleepTimeString)            await sleep(Number.parseInt(debugSleepTimeMs))            // [VSCode] apparently this is necessary for the debugger to attach itself in time before the program closes.            // sometimes this is not enough, so you may have to try multiple times or increase the sleep timer. Unfortunately debugging nodejs isn't easy.        }    }    debug('sampleDir: ' + sampleDir, DEBUG)    debug('imageDir: ' + imageDir, DEBUG)    let pageFormat = 'Endless'    pageWidth = Number.parseInt(pageWidth)    pageHeight = Number.parseInt(pageHeight)    const endlessPage = !(pageHeight > 0 && pageWidth > 0)    if (!endlessPage) {        pageFormat = `${pageWidth}x${pageHeight}`    }    // ---- hacks to fake Browser elements OSMD and Vexflow need, like window, document, and a canvas HTMLElement ----    const { JSDOM } = require('jsdom')    const dom = new JSDOM('<!DOCTYPE html></html>')    // eslint-disable-next-line no-global-assign    window = dom.window    // eslint-disable-next-line no-global-assign    document = dom.window.document    // eslint-disable-next-line no-global-assign    global.window = dom.window    // eslint-disable-next-line no-global-assign    global.document = window.document    global.HTMLElement = window.HTMLElement    global.HTMLAnchorElement = window.HTMLAnchorElement    global.XMLHttpRequest = window.XMLHttpRequest    global.DOMParser = window.DOMParser    global.Node = window.Node    global.Canvas = window.Canvas    // fix Blob not found (to support external modules like is-blob)    global.Blob = require('cross-blob')    const div = document.createElement('div')    div.id = 'browserlessDiv'    document.body.appendChild(div)    // const canvas = document.createElement('canvas')    // div.canvas = document.createElement('canvas')    const zoom = 1.0    // width of the div / PNG generated    let width = pageWidth * zoom    // TODO sometimes the width is way too small for the score, may need to adjust zoom.    if (endlessPage) {        width = 1440    }    let height = pageHeight    if (endlessPage) {        height = 32767    }    div.width = width    div.height = height    div.offsetWidth = width // doesn't work, offsetWidth is always 0 from this. see below    div.clientWidth = width    div.clientHeight = height    div.scrollHeight = height    div.scrollWidth = width    div.setAttribute('width', width)    div.setAttribute('height', height)    div.setAttribute('offsetWidth', width)    debug('div.offsetWidth: ' + div.offsetWidth, DEBUG)    debug('div.height: ' + div.height, DEBUG)    // hack: set offsetWidth reliably    Object.defineProperties(window.HTMLElement.prototype, {        offsetLeft: {            get: function () { return parseFloat(window.getComputedStyle(this).marginTop) || 0 }        },        offsetTop: {            get: function () { return parseFloat(window.getComputedStyle(this).marginTop) || 0 }        },        offsetHeight: {            get: function () { return height }        },        offsetWidth: {            get: function () { return width }        }    })    debug('div.offsetWidth: ' + div.offsetWidth, DEBUG)    debug('div.height: ' + div.height, DEBUG)    if (!DEBUG) {        // deactivate console logs (mostly)        console.log = (msg) => {}    }    // ---- end browser hacks (hopefully) ----    const OSMD = require(`${osmdBuildDir}/opensheetmusicdisplay.min.js`)    const fs = require('fs')    // Create the image directory if it doesn't exist.    fs.mkdirSync(imageDir, { recursive: true })    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) {        if (filterRegex === 'allSmall') {            if (sampleFilename.match('^(Actor)|(Gounod)')) { // TODO maybe filter by file size instead                debug('filtering big file: ' + sampleFilename, DEBUG)                continue            }        }        // eslint-disable-next-line no-useless-escape        if (sampleFilename.match('^.*(\.xml)|(\.musicxml)|(\.mxl)$')) {            // console.log('found musicxml/mxl: ' + sampleFilename)            samplesToProcess.push(sampleFilename)        } else {            debug('discarded file/directory: ' + sampleFilename, DEBUG)        }    }    // filter samples to process by regex if given    if (filterRegex && filterRegex !== '' && filterRegex !== 'all' && filterRegex !== 'allSmall') {        debug('filtering samples for regex: ' + filterRegex, DEBUG)        samplesToProcess = samplesToProcess.filter((filename) => filename.match(filterRegex))        debug(`found ${samplesToProcess.length} matches: `, DEBUG)        for (let i = 0; i < samplesToProcess.length; i++) {            debug(samplesToProcess[i], DEBUG)        }    }    const osmdInstance = new OSMD.OpenSheetMusicDisplay(div, {        autoResize: false,        backend: 'canvas',        pageBackgroundColor: '#FFFFFF',        pageFormat: pageFormat        // defaultFontFamily: 'Arial',        // drawTitle: false    })    // for more options check OSMDOptions.ts    // you can set finer-grained rendering/engraving settings in EngravingRules:    osmdInstance.EngravingRules.TitleTopDistance = 5.0 // 9.0 is default    osmdInstance.EngravingRules.PageTopMargin = 5.0 // 5 is default    osmdInstance.EngravingRules.PageBottomMargin = 5.0 // 5 is default. <5 can cut off scores that extend in the last staffline    // note that for now the png and canvas will still have the height given in the script argument,    //   so even with a margin of 0 the image will be filled to the full height.    osmdInstance.EngravingRules.PageLeftMargin = 5.0 // 5 is default    osmdInstance.EngravingRules.PageRightMargin = 5.0 // 5 is default    // osmdInstance.EngravingRules.MetronomeMarkXShift = -8; // -6 is default    // osmdInstance.EngravingRules.DistanceBetweenVerticalSystemLines = 0.15; // 0.35 is default    // for more options check EngravingRules.ts (though not all of these are meant and fully supported to be changed at will)    // await sleep(5000)    if (DEBUG) {        osmdInstance.setLogLevel('debug')        // console.log(`osmd PageFormat: ${osmdInstance.EngravingRules.PageFormat.width}x${osmdInstance.EngravingRules.PageFormat.height}`)        console.log(`osmd PageFormat idString: ${osmdInstance.EngravingRules.PageFormat.idString}`)        console.log('PageHeight: ' + osmdInstance.EngravingRules.PageHeight)    } else {        osmdInstance.setLogLevel('info') // doesn't seem to work, log.debug still logs    }    debug('generateImages', DEBUG)    for (let i = 0; i < samplesToProcess.length; i++) {        var sampleFilename = samplesToProcess[i]        debug('sampleFilename: ' + sampleFilename, DEBUG)        let loadParameter = fs.readFileSync(sampleDir + '/' + sampleFilename)        if (sampleFilename.endsWith('.mxl')) {            loadParameter = await OSMD.MXLHelper.MXLtoXMLstring(loadParameter)        } else {            loadParameter = loadParameter.toString()        }        // console.log('loadParameter: ' + loadParameter)        // console.log('typeof loadParameter: ' + typeof loadParameter)        await osmdInstance.load(loadParameter).then(function () {            debug('xml loaded', DEBUG)            try {                osmdInstance.render()            } catch (ex) {                console.log('renderError: ' + ex)            }            debug('rendered', DEBUG)            const dataUrls = []            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                }                dataUrls.push(canvasImage.toDataURL())            }            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 pageFilename = `${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')                debug('got image data, saving to: ' + pageFilename, DEBUG)                fs.writeFileSync(pageFilename, imageBuffer, { encoding: 'base64' })            }        }) // end render then        //     },        //     function (e) {        //         console.log('error while rendering: ' + e)        //     }) // end load then        // }) // end read file    }    console.log('[OSMD.generate_browserless] exit')}function debug (msg, debugEnabled) {    if (debugEnabled) {        console.log(msg)    }}init()
 |