Skip to content

Commit

Permalink
add html version
Browse files Browse the repository at this point in the history
  • Loading branch information
kepper committed Nov 18, 2024
1 parent 441e3a3 commit 7e7a02a
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 11 deletions.
7 changes: 5 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { JSDOM } from 'jsdom'
import createVerovioModule from 'verovio/wasm'
import { VerovioToolkit } from 'verovio/esm'

import { walk, getFilesObject, fetchData, writeData } from './src/filehandler.js'
import { walk, getFilesObject, fetchData, writeData, generateHtmlWrapper } from './src/filehandler.js'
import { dir } from './src/config.mjs'
import { prepareDtForRendering, finalizeDiploTrans } from './src/diplomaticTranscripts.js'
import { prepareAtForRendering } from './src/annotatedTranscripts.js'
Expand Down Expand Up @@ -58,7 +58,7 @@ const handleData = async (data, triple, verovio) => {
const dtSvgPath = triple.dt.replace('.xml', '.svg').replace('data/', 'cache/')
const atSvgPath = triple.at.replace('.xml', '.svg').replace('data/', 'cache/')
const ftSvgPath = triple.at.replace('_at.xml', '_ft.svg').replace('data/', 'cache/').replace('/annotatedTranscripts/', '/fluidTranscripts/')

const htmlPath = ftSvgPath.replace('.svg', '.html').replace('/fluidTranscripts/', '/fluidHTML/')
try {
const pageDimensions = getPageDimensions(data.sourceDom, data.dtDom)

Expand All @@ -83,6 +83,9 @@ const handleData = async (data, triple, verovio) => {
writeData(serializer.serializeToString(finalDtDom), dtSvgPath)
writeData(atSvgString, atSvgPath)
writeData(serializer.serializeToString(ftSvgDom), ftSvgPath)

const html = generateHtmlWrapper(ftSvgDom, data.sourceDom, data.dtDom, data.atDom, htmlPath.split('/').pop())
writeData(serializer.serializeToString(html), htmlPath)
// console.log(dtSvgString)
} catch (err) {
console.error('[ERROR]: Unable to process files for ' + dtSvgPath + ': ' + err + '\n\n', err)
Expand Down
172 changes: 171 additions & 1 deletion src/filehandler.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import fs from 'fs'
import path from 'path'
import { readFile } from 'node:fs/promises'
import { annotatedRegex, diplomaticRegex } from './config.mjs'
import { annotatedRegex, diplomaticRegex, verovioPixelDensity } from './config.mjs'
import { getOuterBoundingRect } from './utils.js'
// import { get } from 'http'
// import { DOMParser } from 'xmldom-qsa'
import { JSDOM } from 'jsdom'
const { DOMParser } = new JSDOM().window

const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`)
const serializer = new dom.window.XMLSerializer()

/**
* Walk through the directory and return all files
* @param {*} dir
Expand Down Expand Up @@ -109,4 +113,170 @@ export async function fetchData (triple) {
export async function writeData (content, filePath) {
fs.promises.mkdir(path.dirname(filePath), {recursive: true})
.then(x => fs.promises.writeFile(filePath, content))
}

export function generateHtmlWrapper (svg, meiSourceDom, meiDtDom, meiAtDom, path) {
const dom = new JSDOM(`
<!DOCTYPE html>
<html>
<head>
<title>Liquified Transcription</title>
<style>
body {
margin: 0;
}
.content {
margin: 2rem auto;
position: relative;
}
#facsimile {
width: 1208px;
height: 500px;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
}
#facsimile img {
position: relative;
top: -333px;
}
#transcription {
width: 1008px;
position: absolute;
top: 202px;
left: 0;
}
#controls {
margin: 2rem auto;
position: absolute;
top: 450px;
left: 400px;
}
#controls input {
width: 400px;
display: block;
}
#controls .label {
display: block;
text-align: center;
width: 400px;
padding: .3rem;
font-family: 'Helvetica Neue', 'Arial', sans-serif;
font-weight: 400;
}
</style>
</head>
<body>
<div class="content">
<div id="facsimile">
<img src="https://edirom-images.beethovens-werkstatt.de/Scaler/IIIF/D-BNba_HCB_Mh_60%2FHCB_Mh_60_24.jpg/full/1170,/0/default.jpg" alt="Facsimile"/>
</div>
<div id="transcription">
</div>
</div>
<div id="controls">
<input type="range" min="0" max="9.99" step="any" oninput="setPos(event)" onchange="setPos(event)"/>
<div class="label"></div>
</div>
<script type="text/ecmascript">
function setPos(event) {
const val = event.target.value / 2
document.querySelectorAll('svg').forEach(function(svg) {svg.setCurrentTime(val)})
const facs = document.getElementById('facsimile')
const opacity = 1 / 3.25 * val
facs.style.opacity = 1 - opacity
}
document.querySelectorAll('svg').forEach(function(svg) {svg.pauseAnimations(); svg.setCurrentTime(0);})
</script>
</body>
</html>
`)
const file = dom.window.document
// svg.querySelector('foreignObject').remove()
file.querySelector('#transcription').append(svg.querySelector('svg'))
const label = path.replace('_ft.html' , '').split('_').join(' ').replace(/ p(\d+)/g, ', page $1').replace(/ wz(\d+)/g, ', writing zone $1')
file.querySelector('title').textContent = 'Liquified Transcription: ' + label
file.querySelector('.label').textContent = label

const surfaceId = meiDtDom.querySelector('pb').getAttribute('target').split('#')[1]
const surface = meiSourceDom.querySelector('surface[xml\\:id="' + surfaceId + '"')
const graphic = surface.querySelector('graphic[type="facsimile"]')
const basePath = graphic.getAttribute('target').split('#')[0]
const params = graphic.getAttribute('target').split('#xywh=')[1]
const xywh = params.split('&rotate=')[0]
const pageRotation = params.split('&rotate=')[1] ? params.split('&rotate=')[1] : 0
const imgPath = basePath + '/' + xywh + '/full/0/default.jpg'

file.querySelector('img').remove()
/*
file.querySelector('img').setAttribute('src', imgPath)
const foliumLike = [...meiSourceDom.querySelectorAll('foliaDesc > *')].find(f => {
const ref = '#' + surfaceId
return f.getAttribute('recto') === ref || f.getAttribute('verso') === ref || f.getAttribute('outer.recto') === ref || f.getAttribute('inner.verso') === ref || f.getAttribute('inner.recto') === ref || f.getAttribute('outer.verso') === ref
})
const foliumWidth = parseFloat(foliumLike.getAttribute('width'))
const foliumHeight = parseFloat(foliumLike.getAttribute('height'))
const mediaFragMM = getOuterBoundingRect(0, 0, foliumWidth, foliumHeight, pageRotation)
const scaleFactor = parseFloat(xywh.split(',')[2]) / mediaFragMM.w
const imageX = parseFloat(xywh.split(',')[0]) / scaleFactor * verovioPixelDensity * 10
console.log('left coordinate of image should be ' + imageX)
const renderedStaffLines = [...file.querySelector('svg g.staff:not(.bounding-box)').childNodes].filter(node => node.nodeName === 'path')
const renderedStaffLineHeight = parseFloat(renderedStaffLines[0].getAttribute('d').split(' ')[1]) - parseFloat(renderedStaffLines[4].getAttribute('d').split(' ')[1])
const layout = meiSourceDom.querySelector('layout[xml\\:id="' + surface.getAttribute('decls').substr(1) + '"]')
*/
// 524 system height

/*
const firstNote = meiDtDom.querySelector('note')
if (firstNote) {
try {
const firstNoteDtId = firstNote.getAttribute('xml:id')
const atNote = [...meiAtDom.querySelectorAll('note[corresp]')].find(note => note.getAttribute('corresp').split(' ').some(value => value.endsWith('#' + firstNoteDtId)))
const renderedFirstNote = file.querySelector('svg *[data-id="' + atNote.getAttribute('xml:id') + '"]')
const firstNoteX = parseFloat(firstNote.getAttribute('x'))
const renderedX = parseFloat(renderedFirstNote.querySelector('.notehead use').getAttribute('x'))
const firstNoteDtAnimation = [...renderedFirstNote.children].filter(node => node.nodeName === 'animateTransform')[0]
if (!firstNoteDtAnimation) {
console.warn('No animation found for first note', renderedFirstNote)
console.log('children: ', renderedFirstNote.children.length)
renderedFirstNote.children.forEach(child => {
console.log('child: ', child.nodeName)
})
}
// console.log('firstNoteDtAnimation: ', firstNoteDtAnimation)
const xOff = parseFloat(firstNoteDtAnimation.getAttribute('values').split(';')[0].split(' ')[0])
const fullRenderedX = renderedX + xOff
// console.log('firstNote X: ', firstNoteX, ' fullX: ', fullRenderedX)
const systemOffX = 0
const fullDataX = firstNoteX + systemOffX
const scaledDataX = fullDataX * scaleFactor
console.log('fullRenderedX: ', fullRenderedX, ' scaledDataX: ', scaledDataX)
} catch (err) {
file.querySelector('img').remove()
console.warn('Unable to find a note in transcription, removing facsimile', err)
}
} else {
file.querySelector('img').remove()
console.warn('Unable to find a note in transcription, removing facsimile')
}
*/


return file
}
16 changes: 9 additions & 7 deletions src/fluidTranscripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`)

const duration = '10s'
const repeatCount = 'indefinite'
const reverseAnimations = false
const reverseAnimations = true

/*
ANIMATION PHASES:
Expand Down Expand Up @@ -127,7 +127,7 @@ export const generateFluidTranscription = ({ atSvgDom, dtSvgDom, atOutDom, dtOut

const staves = measure.querySelectorAll('.staff:not(.bounding-box)')

console.log('rotations: (' + rotations.length + ')', rotations, 'staves: ', staves.length)
// console.log('rotations: (' + rotations.length + ')', rotations, 'staves: ', staves.length)

rotations.forEach((rotate, l) => {
const staff = staves[l]
Expand Down Expand Up @@ -179,15 +179,15 @@ export const generateFluidTranscription = ({ atSvgDom, dtSvgDom, atOutDom, dtOut
})
})

const foreignObject = appendNewElement(ftSvgDom.querySelectorAll('svg')[1], 'foreignObject', 'http://www.w3.org/2000/svg')
/* const foreignObject = appendNewElement(ftSvgDom.querySelectorAll('svg')[1], 'foreignObject', 'http://www.w3.org/2000/svg')
foreignObject.setAttribute('x', '20%')
foreignObject.setAttribute('y', '80%')
foreignObject.setAttribute('width', '60%')
foreignObject.setAttribute('height', '15%')
foreignObject.innerHTML = `<div xmlns="http://www.w3.org/1999/xhtml" style="width: 12000px;padding: 100px;"><input type="range" min="0" max="9.99999" style="width: 15%; height: 100px; scale: 5; margin: 5%; padding: 100px; left: 5230px; position: relative;" step="any" oninput="document.querySelectorAll('svg').forEach(svg => svg.setCurrentTime(value))"/></div>`
const script = appendNewElement(ftSvgDom.querySelectorAll('svg')[1], 'script', 'http://www.w3.org/2000/svg')
script.setAttribute('type', 'text/ecmascript')
script.innerHTML = "const innerSvg = document.querySelectorAll('svg').forEach(svg => {svg.pauseAnimations(); svg.setCurrentTime(0);})"
script.innerHTML = "const innerSvg = document.querySelectorAll('svg').forEach(svg => {svg.pauseAnimations(); svg.setCurrentTime(0);})" */

return ftSvgDom
}
Expand All @@ -202,8 +202,11 @@ const generateHideAnimation = (node) => {
hideAnim.setAttribute('dur', duration)
hideAnim.setAttribute('repeatCount', repeatCount)

node.setAttribute('fill', '#999999')
node.setAttribute('stroke', '#999999')
node.setAttribute('fill', '#009900')
node.setAttribute('stroke', '#009900')
//node.setAttribute.add('data-supplied',1)
const existingClasses = node.getAttribute('class') || ''
node.setAttribute('class', `${existingClasses} supplied`.trim())
}

const generateAnimation = (name, ftSvgNode, dtSvgNode, positions) => {
Expand Down Expand Up @@ -363,7 +366,6 @@ const generateAnimation_barLine = (atSvgNode, dtSvgNode, positions) => {
}

const generateAnimation_beam = (atSvgNode, dtSvgNode, positions) => {
console.log('need to animate beam')
const atPolygons = atSvgNode.querySelectorAll('polygon')
const dtPolygons = dtSvgNode.querySelectorAll('polygon')

Expand Down
75 changes: 74 additions & 1 deletion src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,77 @@ export const getPageDimensions = (sourceMEI, transcriptionMEI) => {
export const getAtSystemCenters = (atSvgDom) => {
const arr = []

}
}

// calculates the outer bounding rect of a rotated rectangle
export function getOuterBoundingRect (x, y, w, h, deg) {
const center = {
x: parseFloat(x) + parseFloat(w) / 2,
y: parseFloat(y) + parseFloat(h) / 2
}

if (parseFloat(deg) === 0) {
return { x, y, w, h }
}

const absDeg = Math.abs(deg)
const rad = deg2rad(absDeg)
const newWidth = parseFloat(w) * Math.cos(rad) + parseFloat(h) * Math.sin(rad)
const newHeight = parseFloat(w) * Math.sin(rad) + parseFloat(h) * Math.cos(rad)

const tlUnrotated = {
x: center.x - newWidth / 2,
y: center.y - newHeight / 2
}

const tl = rotatePoint(tlUnrotated, center, deg * -1)
// console.log('x:' + tl.x + ' vs ' + newX + ' / y: ' + tl.y + ' vs ' + newY)
const rect = {
x: tl.x,
y: tl.y,
w: newWidth,
h: newHeight
}

return rect
// (305 * Math.cos(5 * Math.PI / 180)) + (232 * Math.sin(5 * Math.PI / 180))
}

/**
* calculates radians from degrees
* @param {[type]} deg [description]
* @return {[type]} [description]
*/
function deg2rad (deg) {
// console.log('\n\ndeg2rad. deg="' + deg + '", rad="' + deg * (Math.PI / 180) + '"')
return deg * (Math.PI / 180)
}

/**
* rotate point around specified center
* @param {[type]} point a point with x and y props
* @param {[type]} center a point with x and y props
* @param {[type]} deg the rotation in degrees
* @return {[type]} a point with x and y props
*/
function rotatePoint (point, center, deg) {
const xOff = center.x
const yOff = center.y

const x = point.x - xOff
const y = point.y - yOff

const rad = deg2rad(deg)

const x1 = x * Math.cos(rad) - y * Math.sin(rad)
const y1 = x * Math.sin(rad) + y * Math.cos(rad)

const newPoint = {
x: x1 + xOff, // Math.cos(rad) * dist + xOff,
y: y1 + yOff // Math.sin(rad) * dist + xOff
}

// console.log('rotating points', deg, /* point, { x, y }, dist, */ rad, newPoint, center)

return newPoint
}

0 comments on commit 7e7a02a

Please sign in to comment.