diff --git a/assets/Bravura.zip b/assets/Bravura.zip new file mode 100644 index 00000000..e7c4ab08 Binary files /dev/null and b/assets/Bravura.zip differ diff --git a/assets/Hufnagel.zip b/assets/Hufnagel.zip new file mode 100644 index 00000000..25f162cd Binary files /dev/null and b/assets/Hufnagel.zip differ diff --git a/assets/fonts/Bravura.woff2 b/assets/fonts/Bravura.woff2 new file mode 100644 index 00000000..2ff46681 Binary files /dev/null and b/assets/fonts/Bravura.woff2 differ diff --git a/assets/fonts/Bravura.xml b/assets/fonts/Bravura.xml new file mode 100644 index 00000000..5eff2763 --- /dev/null +++ b/assets/fonts/Bravura.xml @@ -0,0 +1,1311 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/fonts/Hufnagel.woff2 b/assets/fonts/Hufnagel.woff2 new file mode 100644 index 00000000..42d5ea14 Binary files /dev/null and b/assets/fonts/Hufnagel.woff2 differ diff --git a/assets/fonts/Hufnagel.xml b/assets/fonts/Hufnagel.xml new file mode 100644 index 00000000..70c3afc6 --- /dev/null +++ b/assets/fonts/Hufnagel.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/img/hufnagel/climacus.png b/assets/img/hufnagel/climacus.png new file mode 100644 index 00000000..c2612d93 Binary files /dev/null and b/assets/img/hufnagel/climacus.png differ diff --git a/assets/img/hufnagel/clivis.png b/assets/img/hufnagel/clivis.png new file mode 100644 index 00000000..0eaa3e37 Binary files /dev/null and b/assets/img/hufnagel/clivis.png differ diff --git a/assets/img/hufnagel/distropha.png b/assets/img/hufnagel/distropha.png new file mode 100644 index 00000000..3cda313f Binary files /dev/null and b/assets/img/hufnagel/distropha.png differ diff --git a/assets/img/hufnagel/pes.png b/assets/img/hufnagel/pes.png new file mode 100644 index 00000000..30a78f83 Binary files /dev/null and b/assets/img/hufnagel/pes.png differ diff --git a/assets/img/hufnagel/porrectus.png b/assets/img/hufnagel/porrectus.png new file mode 100644 index 00000000..3e737833 Binary files /dev/null and b/assets/img/hufnagel/porrectus.png differ diff --git a/assets/img/hufnagel/pressus.png b/assets/img/hufnagel/pressus.png new file mode 100644 index 00000000..472a4906 Binary files /dev/null and b/assets/img/hufnagel/pressus.png differ diff --git a/assets/img/hufnagel/scandicus.png b/assets/img/hufnagel/scandicus.png new file mode 100644 index 00000000..ea999218 Binary files /dev/null and b/assets/img/hufnagel/scandicus.png differ diff --git a/assets/img/hufnagel/torculus.png b/assets/img/hufnagel/torculus.png new file mode 100644 index 00000000..b376f220 Binary files /dev/null and b/assets/img/hufnagel/torculus.png differ diff --git a/assets/style.css b/assets/style.css index 02aef2d0..6c11a030 100644 --- a/assets/style.css +++ b/assets/style.css @@ -6,6 +6,20 @@ @import url('https://unpkg.com/diva.js@6.0.2/build/diva.css'); +@font-face { + font-family: 'Bravura'; + src: url('./fonts/Bravura.woff2') format('woff2'); + font-style: normal; + font-weight: normal; +} + +@font-face { + font-family: 'Hufnagel'; + src: url('./fonts/Hufnagel.woff2') format('woff2'); + font-style: normal; + font-weight: normal; +} + /* ----------------------- */ /* General styles /* ----------------------- */ @@ -807,6 +821,23 @@ input[type='checkbox'].debug-mode-toggle:checked + label:before { width: 42px; height: 42px; } +.glyph-icon { + align-items: center; + display: flex; + height: 24px; + justify-content: center; + line-height: 0; + pointer-events: none; + width: 24px; +} +.glyph-icon svg { + display: block; + fill: currentColor; + height: 100%; + overflow: visible; + pointer-events: none; + width: 100%; +} .longel { width: 100px; height: 42px; diff --git a/deployment/server/css/dashboard.css b/deployment/server/css/dashboard.css index c526567f..20fea773 100644 --- a/deployment/server/css/dashboard.css +++ b/deployment/server/css/dashboard.css @@ -617,7 +617,7 @@ input.filename-input, select.file-input, textarea.file-input { } .folio_list { - height: 80%; + height: 65%; width: 100%; overflow: scroll; } @@ -647,6 +647,39 @@ input.filename-input, select.file-input, textarea.file-input { opacity: 0.4; } +#notation_type_container { + height: 15%; + width: 100%; + border-top: 1px solid #dfdfdf; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +/* .file-container-title normally sizes to 10% of a much taller panel; + inside this short container that shrinks it below its own text height, + so it visually overlaps the radio row underneath. Override to auto. */ +#notation_type_container .file-container-title { + height: auto; + border-bottom: none; + margin-bottom: 4px; +} + +.notation_type_options { + display: flex; + align-items: center; + justify-content: center; +} + +.notation_type_option { + font-size: 12px; + cursor: pointer; + display: inline-flex; + align-items: center; + margin: 0 6px; +} + #upload_button_container { float: right; height: 10%; diff --git a/package.json b/package.json index a7acae1c..d2f1fb37 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ }, "dependencies": { "d3": "^5.11.0", - "diva.js": "github:DDMAL/diva.js#master", + "diva.js": "github:DDMAL/diva.js#5f530483f653db68af1cbb92bc8cc967b51763c3", "http-server": "^14.1.1", "jsonschema": "^1.2.4", "pouchdb": "^7.1.1", diff --git a/src/Dashboard/DashboardContent.ts b/src/Dashboard/DashboardContent.ts index 9299f81d..0376dba8 100644 --- a/src/Dashboard/DashboardContent.ts +++ b/src/Dashboard/DashboardContent.ts @@ -56,6 +56,20 @@ export const uploadAreaHTML = `
+
+
Notation Type
+
+ + +
+
+
Upload diff --git a/src/Dashboard/Storage.ts b/src/Dashboard/Storage.ts index 3b2053fb..6e017de7 100644 --- a/src/Dashboard/Storage.ts +++ b/src/Dashboard/Storage.ts @@ -2,6 +2,7 @@ import { v4 as uuidv4 } from 'uuid'; import PouchDB from 'pouchdb'; import { AllDocs, Doc, uploadsInfo } from '../Types'; import * as localManifest from '../../assets/manifest.json'; +import { restoreHufnagelForStorage } from '../utils/ConvertMei'; export const db = new PouchDB('Neon-User-Storage'); @@ -31,6 +32,7 @@ export function createManifest( title: string, mei: File, bg: File, + notationType: string, ): Promise { return new Promise(async (resolve) => { const manifest = JSON.parse(JSON.stringify(localManifest)); @@ -38,12 +40,12 @@ export function createManifest( manifest['title'] = title; manifest['timestamp'] = new Date().toISOString(); - const meiPromise = new Promise((resolve) => { + const meiTextPromise = new Promise((resolve) => { const meiReader = new FileReader(); meiReader.addEventListener('load', () => { - resolve(meiReader.result); + resolve(meiReader.result as string); }); - meiReader.readAsDataURL(mei); + meiReader.readAsText(mei); }); const bgPromise = new Promise((resolve) => { @@ -54,9 +56,22 @@ export function createManifest( bgReader.readAsDataURL(bg); }); - const meiUri = await meiPromise; + const meiText = await meiTextPromise; + const meiUri = + 'data:application/mei+xml;base64,' + + window.btoa(restoreHufnagelForStorage(meiText, notationType)); const bgUri = await bgPromise; + // Pre-seed this folio's LocalSettings entry (keyed by id, same format + // LocalSettings itself writes) so the Editing page's notation type + // dropdown reflects this choice on first open. The Editing page never + // reads @notationtype back out of the MEI to initialize the dropdown - + // it relies solely on this per-folio localStorage entry - and by the + // time it's loaded there, the MEI's real notationtype has already been + // stripped to a generic value for Verovio (see ConvertMei.ts), so it + // can't be recovered from the MEI at that point either. + window.localStorage.setItem(id, JSON.stringify({ notationType })); + manifest['image'] = bgUri; manifest['mei_annotations'] = [ { diff --git a/src/Dashboard/UploadArea.ts b/src/Dashboard/UploadArea.ts index e564c6f1..c26fa1fc 100644 --- a/src/Dashboard/UploadArea.ts +++ b/src/Dashboard/UploadArea.ts @@ -15,7 +15,12 @@ async function handleUploadUpdate( const spinner = document.querySelector('#uploading_spinner'); spinner.classList.add('visible'); - handleUploadAllDocuments(currentFolder) + const selectedNotationType: HTMLInputElement = document.querySelector( + 'input[name="upload_notation_type"]:checked', + ); + const notationType = selectedNotationType?.value ?? 'square'; + + handleUploadAllDocuments(currentFolder, notationType) .then((results) => { setTimeout(async () => { await updateDashboard(); diff --git a/src/Dashboard/UploadTools.ts b/src/Dashboard/UploadTools.ts index f5f5829d..28502390 100644 --- a/src/Dashboard/UploadTools.ts +++ b/src/Dashboard/UploadTools.ts @@ -178,12 +178,13 @@ function createPairedFolio( export async function handleUploadAllDocuments( currentFolder: IFolder, + notationType: string, ): Promise<{ status: string; value?: string; reason?: any }[]> { const folioPromises = fm .getFolios() .map(async ([name, mei, image]: [string, File, File]) => { const id = uuidv4(); - return await uploadFolio(id, name, mei, image, currentFolder); + return await uploadFolio(id, name, mei, image, currentFolder, notationType); }); const manuscriptPromises = []; //fm.getManuscripts() @@ -208,13 +209,14 @@ async function uploadFolio( mei: File, image: File, currentFolder: IFolder, + notationType: string, ): Promise { const newName = fnConflictHandler( name, FileSystemTools.getAllNames(currentFolder), ); return ( - createManifest(id, newName, mei, image) + createManifest(id, newName, mei, image, notationType) .then((manifest) => { const manifestBlob = new Blob([JSON.stringify(manifest, null, 2)], { type: 'application/ld+json', diff --git a/src/DisplayPanel/DisplayControls.ts b/src/DisplayPanel/DisplayControls.ts index 50791b34..c3d9e79b 100644 --- a/src/DisplayPanel/DisplayControls.ts +++ b/src/DisplayPanel/DisplayControls.ts @@ -491,6 +491,48 @@ function setBurgerControls(): void { }); } +/** + * Set click listeners for Notation Type dropdown (Square / Hufnagel). + * Update the stored setting and notify the active view when the type changes. + */ +export function setNotationTypeControls(): void { + const dropdown = document.getElementById('notation-type-dropdown'); + const label = document.getElementById('notation-type-label'); + + function applyNotationType(type: string): void { + label.textContent = `\xA0- ${type[0].toUpperCase() + type.slice(1)}`; + setSettings({ notationType: type }); + document.dispatchEvent( + new CustomEvent('notationtypechange', { detail: { type } }), + ); + } + + function notationTypeClickaway(): void { + document.body.removeEventListener('click', notationTypeClickaway); + dropdown.classList.remove('is-active'); + } + + document.getElementById('notation-type-button').addEventListener('click', (evt) => { + evt.stopPropagation(); + dropdown.classList.toggle('is-active'); + if (dropdown.classList.contains('is-active')) { + document.body.addEventListener('click', notationTypeClickaway); + } else { + document.body.removeEventListener('click', notationTypeClickaway); + } + }); + + ['square', 'hufnagel'].forEach((type) => { + document.getElementById(`notation-type-${type}`).addEventListener('click', () => { + dropdown.classList.remove('is-active'); + document.body.removeEventListener('click', notationTypeClickaway); + applyNotationType(type); + }); + }); + + applyNotationType(getSettings().notationType ?? 'square'); +} + /** * Set listener for "Display All" button in Display panel. */ @@ -583,6 +625,7 @@ export function initDisplayControls( setHighlightKeyControls(); setDisplayAllListener(); loadHighlightSettings(); + setNotationTypeControls(); const displayContents = document.getElementById('displayContents'); const toggleDisplay = document.getElementById('toggleDisplay'); diff --git a/src/DisplayPanel/DisplayPanel.ts b/src/DisplayPanel/DisplayPanel.ts index aa6096c2..6470766e 100644 --- a/src/DisplayPanel/DisplayPanel.ts +++ b/src/DisplayPanel/DisplayPanel.ts @@ -135,7 +135,30 @@ function displayControlsPanel(handleZoom: ZoomHandler): string {
- + +
+
Notation Type:
+
+
+ `; return contents; diff --git a/src/DivaView.ts b/src/DivaView.ts index 6e76f0d2..992818cc 100644 --- a/src/DivaView.ts +++ b/src/DivaView.ts @@ -33,6 +33,9 @@ class DivaView implements ViewInterface { manifest: string, ) { this.neonView = neonView; + document.addEventListener('notationtypechange', (evt: CustomEvent) => { + this.neonView.setNotationType(evt.detail.type); + }); this.updateCallbacks = []; this.divaReady = false; this.diva = new Diva('container', { diff --git a/src/NeonCore.ts b/src/NeonCore.ts index 6a6285ff..94fdbea7 100644 --- a/src/NeonCore.ts +++ b/src/NeonCore.ts @@ -2,6 +2,8 @@ import { checkOutOfBoundsGlyphs, convertToVerovio, removeColumnLabel, + restoreHufnagelForStorage, + stripHufnagelForVerovio, } from './utils/ConvertMei'; import * as Validation from './Validation'; import VerovioWrapper from './VerovioWrapper'; @@ -268,6 +270,11 @@ class NeonCore { */ loadData(pageURI: string, data: string, dirty = false): Promise { Validation.sendForValidation(removeColumnLabel(data)); + // TEMPORARY Verovio compatibility shim (see ConvertMei.ts) - every path + // into the worker funnels through here, so this is the one place that + // unconditionally keeps notationtype="neume.hufnagel"/"neume.square" + // and @con from ever reaching Verovio. + data = stripHufnagelForVerovio(data); this.lastPageLoaded = pageURI; /* A promise is returned that will resolve to the result of the action. * However the value that is must return comes from the Web Worker and @@ -304,6 +311,25 @@ class NeonCore { }); } + setNotationFont(type: string): Promise { + return new Promise((resolve): void => { + const message: VerovioMessage = { + id: uuidv4(), + action: 'setFont', + fontType: type, + }; + function handle(evt: MessageEvent): void { + if (evt.data.id === message.id) { + evt.target.removeEventListener('message', handle); + this.lastPageLoaded = ''; + resolve(); + } + } + this.verovioWrapper.addEventListener('message', handle.bind(this)); + this.verovioWrapper.postMessage(message); + }); + } + /** * Get the SVG for a specific page. * @param pageURI - The URI of the selected page. @@ -556,7 +582,7 @@ class NeonCore { * This is based on the data stored in the cache. To save time, * only entries marked as dirty will be updated. */ - async updateDatabase(): Promise { + async updateDatabase(notationType?: string): Promise { type Doc = PouchDB.Core.GetMeta & PouchDB.Core.IdMeta & { body: string; timestamp: string }; let updateTimestamp = false; @@ -568,6 +594,14 @@ class NeonCore { const index = this.annotations.findIndex((elem) => { return elem.target === key; }); + // TEMPORARY Verovio compatibility shim (see ConvertMei.ts) - restore + // the canonical notationtype/@con encoding before persisting outside + // of Verovio's control. Does not mutate value.mei: the live cache + // entry must stay in Verovio-safe form for subsequent reloads/edits. + const meiForStorage = restoreHufnagelForStorage( + value.mei, + notationType, + ); // try to update server with PUT request (if applicable) // this is simpler than expecting a specific API on the server // and using POST requests, although that would be better if there @@ -580,23 +614,25 @@ class NeonCore { .fetch(this.annotations[index].body, { method: 'PUT', headers: { 'Content-Type': 'application/mei+xml' }, - body: value.mei, + body: meiForStorage, }) .then((response) => { if (response.ok) { uri = this.annotations[index].body; } else { uri = - 'data:application/mei+xml;base64,' + window.btoa(value.mei); + 'data:application/mei+xml;base64,' + + window.btoa(meiForStorage); } }) .catch((err) => { console.error(err); console.warn('Falling back to data URI'); - uri = 'data:application/mei+xml;base64,' + window.btoa(value.mei); + uri = + 'data:application/mei+xml;base64,' + window.btoa(meiForStorage); }); } else { - uri = 'data:application/mei+xml;base64,' + window.btoa(value.mei); + uri = 'data:application/mei+xml;base64,' + window.btoa(meiForStorage); } // Update URI in annotations, database this.annotations[index].body = uri; diff --git a/src/NeonView.ts b/src/NeonView.ts index 6688b8f8..7dccd591 100644 --- a/src/NeonView.ts +++ b/src/NeonView.ts @@ -72,15 +72,15 @@ class NeonView { this.NeumeEdit = new params.NeumeEdit(this); this.textView.updateBBoxVisibility(); const { userMode, insertMode, selectionMode } = getSettings(); - document.getElementById(insertMode).classList.add('is-active'); - document.getElementById(selectionMode).classList.add('is-active'); + document.getElementById(insertMode)?.classList.add('is-active'); + document.getElementById(selectionMode)?.classList.add('is-active'); switch (userMode) { case 'insert': - document.getElementById(insertMode).click(); + document.getElementById(insertMode)?.click(); break; case 'edit': default: - document.getElementById(selectionMode).click(); + document.getElementById(selectionMode)?.click(); break; } } @@ -187,7 +187,7 @@ class NeonView { export(): Promise { return new Promise((resolve, reject): void => { this.core - .updateDatabase() + .updateDatabase(getSettings().notationType) .then(() => { this.manifest['mei_annotations'] = this.core.getAnnotations(); this.manifest.timestamp = new Date().toISOString(); @@ -212,7 +212,7 @@ class NeonView { */ save(): Promise { setSavedStatus(true); - return this.core.updateDatabase(); + return this.core.updateDatabase(getSettings().notationType); } /** @@ -240,6 +240,12 @@ class NeonView { }); } + setNotationType(type: string): void { + this.core.setNotationFont(type).then(() => { + this.updateForCurrentPage(); + }); + } + /** * Get the page's MEI file as a string. * @param pageNo - The identifying URI of the page. diff --git a/src/SingleView/SingleView.ts b/src/SingleView/SingleView.ts index d9af3ea0..362f8600 100644 --- a/src/SingleView/SingleView.ts +++ b/src/SingleView/SingleView.ts @@ -31,6 +31,9 @@ class SingleView implements ViewInterface { */ constructor(neonView: NeonView, panel: DisplayConstructable, image: string) { this.neonView = neonView; + document.addEventListener('notationtypechange', (evt: CustomEvent) => { + this.neonView.setNotationType(evt.detail.type); + }); this.container = document.getElementById('container'); this.updateCallbacks = new Array(0); // Group will contain the image and the rendered SVG diff --git a/src/SquareEdit/Contents.ts b/src/SquareEdit/Contents.ts index 026ff3d6..8f55a507 100644 --- a/src/SquareEdit/Contents.ts +++ b/src/SquareEdit/Contents.ts @@ -1,3 +1,95 @@ +import { getFontData, buildGlyphIcon } from '../utils/GlyphIcons'; + +export interface PrimitiveEntry { + id: string; + codepoint: string; + title: string; + iconDy?: number; + pngFallback?: string; +} + +export const SQUARE_PRIMITIVES: PrimitiveEntry[] = [ + { id: 'punctum', codepoint: 'E990', title: 'punctum' }, + { id: 'virga', codepoint: 'E996', title: 'virga', iconDy: -9 }, + { id: 'virgaReversed', codepoint: 'E997', title: 'Reversed Virga', iconDy: -9, pngFallback: 'virga_reversed' }, + { id: 'diamond', codepoint: 'E991', title: 'inclinatum' }, + { id: 'custos', codepoint: 'EA06', title: 'custos', iconDy: 9 }, + { id: 'cClef', codepoint: 'E906', title: 'C Clef' }, + { id: 'fClef', codepoint: 'E902', title: 'F Clef' }, + { id: 'gClef', codepoint: 'E900', title: 'G Clef' }, + { id: 'liquescentA', codepoint: 'E994', title: 'Liquescent A' }, + { id: 'liquescentC', codepoint: 'E995', title: 'Liquescent C' }, + { id: 'quilisma', codepoint: 'E99B', title: 'quilisma' }, + { id: 'oriscus', codepoint: 'E99E', title: 'oriscus' }, + { id: 'flat', codepoint: 'E260', title: 'Flat', iconDy: 9, pngFallback: 'accidFlat' }, + { id: 'natural', codepoint: 'E261', title: 'Natural', pngFallback: 'accidNatural' }, + { id: 'divLineMaxima', codepoint: 'E8F5', title: 'DivLine Maxima', pngFallback: 'divisio' }, +]; + +export const HUFNAGEL_PRIMITIVES: PrimitiveEntry[] = [ + { id: 'punctum', codepoint: 'E990', title: 'punctum' }, + { id: 'virga', codepoint: 'E996', title: 'virga', iconDy: -13 }, + { id: 'virgaReversed', codepoint: 'E997', title: 'UDV', iconDy: 13 }, + { id: 'distropha', codepoint: 'E99F', title: 'Distropha', pngFallback: 'hufnagel/distropha' }, + { id: 'custos', codepoint: 'EA06', title: 'custos', iconDy: 6 }, + { id: 'cClef', codepoint: 'E906', title: 'C Clef' }, + { id: 'fClef', codepoint: 'E902', title: 'F Clef' }, + { id: 'liquescentA', codepoint: 'E994', title: 'Liquescent up' }, + { id: 'flat', codepoint: 'E260', title: 'Flat', iconDy: 9, pngFallback: 'accidFlat' }, + { id: 'natural', codepoint: 'E261', title: 'Natural', pngFallback: 'accidNatural' }, + { id: 'divLineMaxima', codepoint: 'E8F5', title: 'DivLine Maxima', pngFallback: 'divisio' }, +]; + +export async function buildPrimitiveTabHtml(notationType: string): Promise { + const isHufnagel = notationType === 'hufnagel'; + const fontName = isHufnagel ? 'Hufnagel' : 'Bravura'; + const primitives = isHufnagel ? HUFNAGEL_PRIMITIVES : SQUARE_PRIMITIVES; + + const fontData = await getFontData( + fontName, + `${__ASSET_PREFIX__}assets/fonts/${fontName}.xml`, + ); + const fallbackFontData = isHufnagel + ? await getFontData( + 'Bravura', + `${__ASSET_PREFIX__}assets/fonts/Bravura.xml`, + ) + : null; + + return primitives.map(({ id, codepoint, title, iconDy, pngFallback }) => { + const bbox = fontData.bboxMap.get(codepoint); + const fallbackBBox = fallbackFontData?.bboxMap.get(codepoint); + const fallbackPng = pngFallback ?? id; + const iconStyle = iconDy ? ` style="transform: translateY(${iconDy}px)"` : ''; + // If the primary font lacks this glyph and there's a font-specific PNG override, use that + // before falling back to Bravura (which would show a Square notation glyph). + const useOverridePng = !bbox && pngFallback?.startsWith('hufnagel/'); + const icon = !useOverridePng && (bbox || fallbackBBox) + ? `${buildGlyphIcon( + codepoint, + bbox ? fontData.fontName : fallbackFontData.fontName, + bbox ?? fallbackBBox, + )}` + : ``; + return `

+ +

`; + }).join('\n'); +} + +const GROUPINGS = ['pes', 'clivis', 'scandicus', 'climacus', 'torculus', 'porrectus', 'pressus'] as const; + +export function buildGroupingTabHtml(notationType: string): string { + const imgDir = notationType === 'hufnagel' + ? `${__ASSET_PREFIX__}assets/img/hufnagel/` + : `${__ASSET_PREFIX__}assets/img/`; + return GROUPINGS.map(id => + `

+ +

` + ).join('\n'); +} + /** * HTML for each insert tab (neume, grouping, clef, system, and division). */ @@ -47,27 +139,7 @@ export const insertTabHtml: Record = {

`, - groupingTab: `

- -

-

- -

-

- -

-

- -

-

- -

-

- -

-

- -

`, + groupingTab: '', // unused — call buildGroupingTabHtml(notationType) instead systemTab: `

diff --git a/src/SquareEdit/Controls.ts b/src/SquareEdit/Controls.ts index e380656e..c9da05e8 100644 --- a/src/SquareEdit/Controls.ts +++ b/src/SquareEdit/Controls.ts @@ -36,21 +36,82 @@ export function bindInsertTabs(insertHandler: InsertHandler): void { deactivate('.insertTab'); activate(tab, insertHandler); setSettings({ insertTab: tab as InsertTabType }); - document.getElementById('insert_data').innerHTML = - Contents.insertTabHtml[tab]; - bindElements(insertHandler); - deactivate('.insertel'); - try { - const { insertMode } = getSettings(); - activate(insertMode, insertHandler); - } catch (e) { - const firstOption = document.getElementsByClassName('insertel')[0]; - activate(firstOption.id, insertHandler); + if (tab === 'primitiveTab') { + const { notationType } = getSettings(); + setPrimitiveFallback(insertHandler); + renderPrimitiveTab(notationType ?? 'square', insertHandler); + } else if (tab === 'groupingTab') { + const { notationType } = getSettings(); + document.getElementById('insert_data').innerHTML = + Contents.buildGroupingTabHtml(notationType ?? 'square'); + bindElements(insertHandler); + deactivate('.insertel'); + activateCurrentOrFirst(insertHandler); + } else { + document.getElementById('insert_data').innerHTML = + Contents.insertTabHtml[tab]; + bindElements(insertHandler); + deactivate('.insertel'); + activateCurrentOrFirst(insertHandler); } }); }); } +export async function updatePrimitiveTab(notationType: string, insertHandler: InsertHandler): Promise { + const insertData = document.getElementById('insert_data'); + if (!insertData) return; + const { insertTab } = getSettings(); + if (insertTab !== 'primitiveTab') return; + setPrimitiveFallback(insertHandler); + await renderPrimitiveTab(notationType, insertHandler); +} + +export function updateGroupingTab(notationType: string, insertHandler: InsertHandler): void { + const insertData = document.getElementById('insert_data'); + if (!insertData) return; + const { insertTab } = getSettings(); + if (insertTab !== 'groupingTab') return; + insertData.innerHTML = Contents.buildGroupingTabHtml(notationType); + bindElements(insertHandler); + deactivate('.insertel'); + activateCurrentOrFirst(insertHandler); +} + +function setPrimitiveFallback(insertHandler: InsertHandler): void { + const insertData = document.getElementById('insert_data'); + if (!insertData) return; + insertData.innerHTML = Contents.insertTabHtml['primitiveTab']; + bindElements(insertHandler); + deactivate('.insertel'); + activateCurrentOrFirst(insertHandler); +} + +async function renderPrimitiveTab(notationType: string, insertHandler: InsertHandler): Promise { + try { + const html = await Contents.buildPrimitiveTabHtml(notationType); + const insertData = document.getElementById('insert_data'); + if (!insertData) return; + const { insertTab } = getSettings(); + if (insertTab !== 'primitiveTab') return; + insertData.innerHTML = html; + bindElements(insertHandler); + deactivate('.insertel'); + activateCurrentOrFirst(insertHandler); + } catch (err) { + console.error('[GlyphIcons] Failed to load glyphs, using PNG fallback:', err); + } +} + +function activateCurrentOrFirst(insertHandler: InsertHandler): void { + const { insertMode } = getSettings(); + const currentOption = document.getElementById(insertMode); + const firstOption = document.getElementsByClassName('insertel')[0]; + const option = currentOption ?? firstOption; + if (!option) return; + activate(option.id, insertHandler); +} + /** * Initialize Edit and Insert control panels. */ @@ -77,13 +138,14 @@ export function initInsertEditControls(): void { setSettings({ userMode: 'insert' }); - (document.querySelector('.insertel.is-active')).click(); + const activeInsert = document.querySelector('.insertel.is-active'); + activeInsert?.click(); editPanel .querySelector('.side-panel-btn.sel-by.is-active') - .classList.add('unfocused'); + ?.classList.add('unfocused'); insertPanel .querySelector('.side-panel-btn.insertel.is-active') - .classList.remove('unfocused'); + ?.classList.remove('unfocused'); }); // event listener for when user clicks inside Edit panel @@ -96,10 +158,10 @@ export function initInsertEditControls(): void { insertPanel .querySelector('.side-panel-btn.insertel.is-active') - .classList.add('unfocused'); + ?.classList.add('unfocused'); editPanel .querySelector('.side-panel-btn.sel-by.is-active') - .classList.remove('unfocused'); + ?.classList.remove('unfocused'); }); insertHeading.addEventListener('click', (e) => { @@ -171,8 +233,9 @@ export function initInsertEditControls(): void { */ function activate(id: string, insertHandler: InsertHandler): void { const selectedTab = document.getElementById(id); + if (!selectedTab) return; selectedTab.classList.add('is-active'); - if (document.getElementById(id).classList.contains('insertel')) { + if (selectedTab.classList.contains('insertel')) { insertHandler.insertActive(id); setSettings({ insertMode: id as InsertType }); } diff --git a/src/SquareEdit/DivaEditMode.ts b/src/SquareEdit/DivaEditMode.ts index e707661a..ee0f6cf2 100644 --- a/src/SquareEdit/DivaEditMode.ts +++ b/src/SquareEdit/DivaEditMode.ts @@ -2,6 +2,8 @@ import { bindInsertTabs, initInsertEditControls, initSelectionButtons, + updatePrimitiveTab, + updateGroupingTab, } from './Controls'; import * as Select from '../utils/Select'; import InsertHandler from './InsertHandler'; @@ -36,6 +38,11 @@ class DivaEdit implements NeumeEditInterface { bindInsertTabs(this.insertHandler); const { insertTab } = getSettings(); document.getElementById(insertTab).click(); + document.addEventListener('notationtypechange', (evt: CustomEvent) => { + updatePrimitiveTab(evt.detail.type, this.insertHandler); + updateGroupingTab(evt.detail.type, this.insertHandler); + }); + updatePrimitiveTab(getSettings().notationType ?? 'square', this.insertHandler); Select.setSelectHelperObjects(this.neonView, this.dragHandler); this.setSelectListeners(); SelectOptions.initNeonView(this.neonView); diff --git a/src/SquareEdit/InsertHandler.ts b/src/SquareEdit/InsertHandler.ts index 9f9d717f..f6ab4838 100644 --- a/src/SquareEdit/InsertHandler.ts +++ b/src/SquareEdit/InsertHandler.ts @@ -73,6 +73,7 @@ class InsertHandler { case 'torculus': case 'porrectus': case 'pressus': + case 'distropha': const contour = this.neonView.info.getContourByValue( buttonId.charAt(0).toUpperCase() + buttonId.slice(1), ); diff --git a/src/SquareEdit/SingleEditMode.ts b/src/SquareEdit/SingleEditMode.ts index c2b13cd0..e2f744cc 100644 --- a/src/SquareEdit/SingleEditMode.ts +++ b/src/SquareEdit/SingleEditMode.ts @@ -2,6 +2,8 @@ import { bindInsertTabs, initInsertEditControls, initSelectionButtons, + updatePrimitiveTab, + updateGroupingTab, } from './Controls'; import { setHighlightOption } from '../DisplayPanel/DisplayControls'; import { initUndoRedoPanel } from '../utils/EditControls'; @@ -50,6 +52,11 @@ class SingleEditMode implements NeumeEditInterface { bindInsertTabs(this.insertHandler); const { insertTab } = getSettings(); document.getElementById(insertTab).click(); + document.addEventListener('notationtypechange', (evt: CustomEvent) => { + updatePrimitiveTab(evt.detail.type, this.insertHandler); + updateGroupingTab(evt.detail.type, this.insertHandler); + }); + updatePrimitiveTab(getSettings().notationType ?? 'square', this.insertHandler); Select.setSelectHelperObjects(this.neonView, this.dragHandler); this.setSelectListeners(); diff --git a/src/Types.ts b/src/Types.ts index 6fb7f885..c93b25f8 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -272,6 +272,7 @@ export type VerovioMessage = { mei?: string; elementId?: string; editorAction?: EditorAction; + fontType?: string; }; export type VerovioResponse = { @@ -394,6 +395,7 @@ export type InsertType = | 'gClef' | 'liquescentA' | 'liquescentC' + | 'distropha' | 'quilisma' | 'orcisus' | 'flat' diff --git a/src/utils/ConvertMei.ts b/src/utils/ConvertMei.ts index 1cc338e1..96dfaf39 100644 --- a/src/utils/ConvertMei.ts +++ b/src/utils/ConvertMei.ts @@ -24,7 +24,110 @@ function copyAttributes(src: Element, dst: Element): void { } } -export function convertToNeon(staffBasedMei: string): string { +// --- BEGIN TEMPORARY Verovio compatibility shim (see issue #1360) --- +// +// Verovio segfaults on notationtype="neume.hufnagel" on , and +// notationtype="neume.square" has never been exercised either (every +// existing file/fixture only uses bare "neume"). So Neon's canonical MEI +// (what gets downloaded/saved) keeps the correct staffDef notationtype +// ("neume.hufnagel" / "neume.square"), but whatever is actually handed to +// the Verovio worker must always use bare notationtype="neume" (the +// Hufnagel/Square look is forced via font substitution instead - see +// setNotationFont / VerovioWorker-dev.js, which this shim does not touch). +// +// @con/@ligated on are NOT touched on the way INTO Verovio, for either +// notation type. Square's connections are Square's own real @ligated +// (needed for Verovio's ligature-merge glyph rendering - that mechanism is +// real and unrelated to this shim). Hufnagel's connections are rendered +// from each note's own individual glyph (e.g. virga + UDV are two separate +// primitives, not a merged glyph) - @con is pure semantic metadata Verovio +// does not need for rendering Hufnagel at all. An earlier version of this +// shim auto-derived @ligated from @con before Verovio to try to force the +// same merge-rendering Square uses, and that was simply wrong: it made +// Verovio invoke the Square ligature-glyph codepath, which the Hufnagel +// font substitution does not cover, corrupting the render (UDV disappeared, +// its partner note misshapen). Removed for good - do not reintroduce. +// +// The one place @con/@ligated IS still touched: if Verovio's own +// toggleLigature editor action is used on a Hufnagel document (reusing +// Square's button, planned but not yet wired up) and leaves @ligated="true" +// behind after live editing, that must be converted back to @con="e" before +// download/save, since "ligature" must never appear in Hufnagel's canonical +// MEI. This is a no-op for documents that never went through that action. +// +// This entire block can be deleted once Verovio natively supports +// notationtype="neume.hufnagel"/"neume.square". +const HUFNAGEL_NOTATIONTYPE = 'neume.hufnagel'; +const SQUARE_NOTATIONTYPE = 'neume.square'; +const HUFNAGEL_CON_VALUE = 'e'; + +// Hufnagel only: reverse of Verovio's own toggleLigature editor action. For +// each adjacent pair of @ligated s, remove @ligated from both and +// restore @con="e" on the second one only. +function convertLigatedToCon(mei: Element): void { + const ncs = Array.from(mei.getElementsByTagName('nc')); + let idx = 0; + while (idx < ncs.length) { + if ( + ncs[idx].getAttribute('ligated') && + idx + 1 < ncs.length && + ncs[idx + 1].getAttribute('ligated') + ) { + ncs[idx].removeAttribute('ligated'); + ncs[idx + 1].removeAttribute('ligated'); + ncs[idx + 1].setAttribute('con', HUFNAGEL_CON_VALUE); + idx += 2; + } else { + idx += 1; + } + } +} + +// String-in/string-out wrappers for callers (NeonCore.ts) that only have +// the MEI as a string, not an already-parsed DOM. +export function stripHufnagelForVerovio(meiString: string): string { + const parser = new DOMParser(); + const meiDoc = parser.parseFromString(meiString, 'text/xml'); + const staffDef = meiDoc.documentElement.querySelector('staffDef'); + const notationType = staffDef?.getAttribute('notationtype'); + + if ( + notationType === HUFNAGEL_NOTATIONTYPE || + notationType === SQUARE_NOTATIONTYPE + ) { + staffDef.setAttribute('notationtype', 'neume'); + } + + return vkbeautify.xml(new XMLSerializer().serializeToString(meiDoc)); +} + +// Used by Save (NeonCore.updateDatabase) - this is Neon's own internal +// working copy, which gets fed straight back into Verovio on next load, so +// it must faithfully keep whatever Verovio's live in-memory document +// actually has (including @ligated from a real toggleLigature action, and +// whatever else that action changed, e.g. clearing @tilt) rather than +// "fixing" it to the canonical @con-only form. Only @notationtype is +// restored here, since Verovio itself never remembers that. +export function restoreHufnagelForStorage( + meiString: string, + notationType?: string, +): string { + const parser = new DOMParser(); + const meiDoc = parser.parseFromString(meiString, 'text/xml'); + if (notationType === 'hufnagel' || notationType === 'square') { + meiDoc.documentElement + .querySelector('staffDef') + ?.setAttribute('notationtype', `neume.${notationType}`); + } + + return vkbeautify.xml(new XMLSerializer().serializeToString(meiDoc)); +} +// --- END TEMPORARY Verovio compatibility shim --- + +export function convertToNeon( + staffBasedMei: string, + notationType?: string, +): string { const parser = new DOMParser(); const serializer = new XMLSerializer(); const meiDoc = parser.parseFromString(staffBasedMei, 'text/xml'); @@ -148,6 +251,16 @@ export function convertToNeon(staffBasedMei: string): string { scoreDef.insertAdjacentElement('afterend', colLayout); } + if (notationType === 'hufnagel' || notationType === 'square') { + mei + .querySelector('staffDef') + ?.setAttribute('notationtype', `neume.${notationType}`); + } + + if (notationType === 'hufnagel') { + convertLigatedToCon(mei); + } + return vkbeautify.xml(serializer.serializeToString(meiDoc)); function calculateAndAddZone( diff --git a/src/utils/EditControls.ts b/src/utils/EditControls.ts index c052352b..39046a03 100644 --- a/src/utils/EditControls.ts +++ b/src/utils/EditControls.ts @@ -2,6 +2,7 @@ import * as Notification from './Notification'; import NeonView from '../NeonView'; import { convertToNeon } from './ConvertMei'; import { EditorAction } from '../Types'; +import { getSettings } from './LocalSettings'; /** * Set top navbar event listeners. @@ -49,7 +50,8 @@ export function initNavbar(neonView: NeonView): void { const uri = neonView.view.getCurrentPageURI(); neonView.getPageMEI(uri).then((mei) => { const data = - 'data:application/mei+xml;base64,' + window.btoa(convertToNeon(mei)); + 'data:application/mei+xml;base64,' + + window.btoa(convertToNeon(mei, getSettings().notationType)); document.getElementById('getmei').setAttribute('href', data); document .getElementById('getmei') diff --git a/src/utils/GlyphIcons.ts b/src/utils/GlyphIcons.ts new file mode 100644 index 00000000..c6952d23 --- /dev/null +++ b/src/utils/GlyphIcons.ts @@ -0,0 +1,58 @@ +interface BBox { x: number; y: number; w: number; h: number; } + +interface FontData { + fontName: string; + bboxMap: Map; +} + +const ICON_SIZE = 24; + +const cache = new Map>(); + +async function loadFontData( + fontName: string, + bboxUrl: string, +): Promise { + const response = await fetch(bboxUrl); + if (!response.ok) throw new Error(`Unable to load ${bboxUrl}`); + const xml = await response.text(); + const bboxMap = new Map(); + const parser = new DOMParser(); + const doc = parser.parseFromString(xml, 'text/xml'); + doc.querySelectorAll('g[c]').forEach(el => { + const c = el.getAttribute('c'); + const x = parseFloat(el.getAttribute('x') ?? '0'); + const y = parseFloat(el.getAttribute('y') ?? '0'); + const w = parseFloat(el.getAttribute('w') ?? '0'); + const h = parseFloat(el.getAttribute('h') ?? '0'); + if (c && w > 0 && h > 0) bboxMap.set(c, { x, y, w, h }); + }); + + await document.fonts.load(`1000px "${fontName}"`); + + return { fontName, bboxMap }; +} + +export function getFontData( + fontName: string, + bboxUrl: string, +): Promise { + if (!cache.has(fontName)) { + cache.set(fontName, loadFontData(fontName, bboxUrl)); + } + return cache.get(fontName)!; +} + +export function buildGlyphIcon(codepoint: string, fontName: string, bbox: BBox): string { + const pad = bbox.h * 0.08; + const vx = bbox.x - pad; + const vy = bbox.y - pad; + const vw = bbox.w + pad * 2; + const vh = bbox.h + pad * 2; + const cp = parseInt(codepoint, 16); + const char = String.fromCodePoint(cp); + // font-size 1000 matches units-per-em=1000, so bbox coords directly frame the glyph + return ``; +} diff --git a/src/utils/LocalSettings.ts b/src/utils/LocalSettings.ts index d4be1d2c..700881e4 100644 --- a/src/utils/LocalSettings.ts +++ b/src/utils/LocalSettings.ts @@ -31,6 +31,7 @@ export interface Settings { userMode: UserType; insertMode: InsertType; insertTab: InsertTabType; + notationType: string; selectionMode: SelectionType; debugMode: boolean; displayBBox: boolean; @@ -56,6 +57,7 @@ const DEFAULT_SETTINGS: Settings = { userMode: 'edit', insertMode: 'punctum', insertTab: 'primitiveTab', + notationType: 'square', selectionMode: 'selBySyllable', debugMode: false, displayBBox: false, diff --git a/src/workers/FontLoader.js b/src/workers/FontLoader.js new file mode 100644 index 00000000..493d3672 --- /dev/null +++ b/src/workers/FontLoader.js @@ -0,0 +1,16 @@ +const fontBase64Promises = new Map(); + +function fetchFontBase64(url) { + if (!fontBase64Promises.has(url)) { + const base64Promise = fetch(url) + .then(response => response.arrayBuffer()) + .then(buffer => { + const bytes = new Uint8Array(buffer); + let binary = ''; + for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]); + return btoa(binary); + }); + fontBase64Promises.set(url, base64Promise); + } + return fontBase64Promises.get(url); +} diff --git a/src/workers/VerovioWorker-dev.js b/src/workers/VerovioWorker-dev.js index 8e6472ac..0e5aa207 100644 --- a/src/workers/VerovioWorker-dev.js +++ b/src/workers/VerovioWorker-dev.js @@ -1,4 +1,5 @@ importScripts('../assets/js/verovio-toolkit-wasm.js'); +importScripts('./FontLoader.js'); let toolkit; const backlog = []; @@ -7,7 +8,7 @@ const backlog = []; * Parse and respond to messages sent by NeonCore. * @param {MessageEvent} evt */ -function handleNeonEvent(evt) { +async function handleNeonEvent(evt) { const data = evt.data; const result = { id: data.id, @@ -35,6 +36,33 @@ function handleNeonEvent(evt) { case 'renderToSVG': result.svg = toolkit.renderToSVG(1); break; + case 'setFont': { + const baseOptions = { + inputFrom: 'mei', + footer: 'none', + header: 'none', + pageMarginLeft: 0, + pageMarginTop: 0, + useFacsimile: false, + svgAdditionalAttribute: ['syllable@precedes', 'syllable@follows'], + svgCss: 'g.nc, g.custos, g.clef, g.accid, g.divLine {stroke: currentColor; stroke-width: 30px;}', + }; + try { + if (data.fontType === 'hufnagel') { + const b64 = await fetchFontBase64('../assets/Hufnagel.zip'); + console.log('[VerovioWorker] b64 length:', b64.length); + toolkit.setOptions({ ...baseOptions, font: 'Hufnagel', fontAddCustom: [b64], fontFallback: 'Bravura' }); + console.log('[VerovioWorker] setOptions Hufnagel done'); + } else { + toolkit.setOptions({ ...baseOptions, font: 'Bravura', fontAddCustom: '' }); + console.log('[VerovioWorker] setOptions Bravura done'); + } + console.log('[VerovioWorker] setFont done:', data.fontType); + } catch (e) { + console.error('[VerovioWorker] setFont ERROR:', e); + } + break; + } default: break; } diff --git a/src/workers/VerovioWorker.js b/src/workers/VerovioWorker.js index f8c63b3e..03446ba0 100644 --- a/src/workers/VerovioWorker.js +++ b/src/workers/VerovioWorker.js @@ -1,3 +1,5 @@ +importScripts('./FontLoader.js'); + let toolkit; const backlog = []; @@ -5,7 +7,7 @@ const backlog = []; * Parse and respond to messages sent by NeonCore. * @param {MessageEvent} evt */ -function handleNeonEvent(evt) { +async function handleNeonEvent(evt) { const data = evt.data; const result = { id: data.id, @@ -33,6 +35,33 @@ function handleNeonEvent(evt) { case 'renderToSVG': result.svg = toolkit.renderToSVG(1); break; + case 'setFont': { + const baseOptions = { + inputFrom: 'mei', + footer: 'none', + header: 'none', + pageMarginLeft: 0, + pageMarginTop: 0, + useFacsimile: false, + svgAdditionalAttribute: ['syllable@precedes', 'syllable@follows'], + svgCss: 'g.nc, g.custos, g.clef, g.accid, g.divLine {stroke: currentColor; stroke-width: 30px;}', + }; + try { + if (data.fontType === 'hufnagel') { + const b64 = await fetchFontBase64('../assets/Hufnagel.zip'); + console.log('[VerovioWorker] b64 length:', b64.length); + toolkit.setOptions({ ...baseOptions, font: 'Hufnagel', fontAddCustom: [b64], fontFallback: 'Bravura' }); + console.log('[VerovioWorker] setOptions Hufnagel done'); + } else { + toolkit.setOptions({ ...baseOptions, font: 'Bravura', fontAddCustom: '' }); + console.log('[VerovioWorker] setOptions Bravura done'); + } + console.log('[VerovioWorker] setFont done:', data.fontType); + } catch (e) { + console.error('[VerovioWorker] setFont ERROR:', e); + } + break; + } default: break; } diff --git a/yarn.lock b/yarn.lock index b9309f99..71aaf316 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3519,7 +3519,7 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -"diva.js@github:DDMAL/diva.js#master": +"diva.js@github:DDMAL/diva.js#5f530483f653db68af1cbb92bc8cc967b51763c3": version "6.0.2" resolved "https://codeload.github.com/DDMAL/diva.js/tar.gz/5f530483f653db68af1cbb92bc8cc967b51763c3" dependencies: