text: /*
    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()
  