Skip to content

Commit

Permalink
change mergeChordLineWithLyrics helper to renderChordLyricLine
Browse files Browse the repository at this point in the history
  • Loading branch information
no-chris committed Dec 21, 2023
1 parent 36e371e commit b146ee0
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 8 deletions.
196 changes: 196 additions & 0 deletions packages/chord-mark/src/renderer/components/renderChordLyricLine.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import chordLyricLineTpl from './tpl/chordLyricLine';

import _intersection from 'lodash/intersection';
import stripTags from '../../core/dom/stripTags';

/**
* This is the most complex renderer because it works from the previously rendered
* chords and lyrics markup, combining them into a merged chord/lyrics markup suitable for small screen,
* instead of rendering directly from the model.
* This is to avoid duplicating the significant rendering business logic of chord lines,
* and also to avoid a too big refactoring in order to implement the small screen renderer
* (e.g. wrappable chord/lyric lines).
* @param {String} chordLine - html of a rendered chord line
* @param {String} lyricLine - html of a rendered lyric line
* @returns {String} rendered html
*/
export default function renderChordLyricLine(chordLine, lyricLine) {
const allChordTokens = getAllChordTokens(chordLine);
const allLyricTokens = getAllLyricTokens(lyricLine);

const allBreakPoints = getAllBreakpoints(allChordTokens, allLyricTokens);

const chordLyricsPairs = getChordLyricsPairs(
allBreakPoints,
allChordTokens,
allLyricTokens
);

return chordLyricLineTpl({ chordLyricsPairs });
}

// eslint-disable-next-line max-lines-per-function
function getAllChordTokens(chordLine) {
const breakPointsClasses = [
'cmBarSeparator', //fixme:
'cmChordSymbol',
'cmTimeSignature',
'cmSubBeatGroupOpener', //fixme:
'cmSubBeatGroupCloser', //fixme:
];

const allChordTokens = [];
// disbaling eslint warning since a chordLine is only made of ChordMark-generated html
// eslint-disable-next-line no-unsanitized/method
const chordLineNodes = document
.createRange()
.createContextualFragment(chordLine);

let textIndex = 0;
walkDom(chordLineNodes, allChordTokens, 0);

allChordTokens.push({
tokenText: '',
tokenTextIndex: stripTags(chordLine).length,
});

function walkDom(startNode, allNodes) {
startNode.childNodes.forEach((childNode) => {
if (childNode.nodeType === Node.TEXT_NODE) {
const textContent = childNode.textContent;
if (isOnlySpaces(textContent)) {
for (let i = 0; i < textContent.length; i++) {
allNodes.push({
tokenText: ' ',
tokenTextIndex: textIndex,
});
textIndex++;
}
} else {
//fixme: is this needed at all? yes, for sub beat group delimiters?
allNodes.push({
tokenText: childNode.textContent,
tokenTextIndex: textIndex,
});
textIndex += childNode.textContent.length;
}
} else {
if (breakPointsClasses.includes(childNode.classList.value)) {
allNodes.push({
tokenText: childNode.textContent,
tokenTextIndex: textIndex,
tokenHtml: childNode.outerHTML,
});
textIndex += childNode.textContent.length;
} else {
walkDom(childNode, allNodes);
}
}
});
}

function isOnlySpaces(string) {
return Array.from(string).every((char) => char === ' ');
}

return allChordTokens;
}

function getAllLyricTokens(lyricLine) {
const allTextNodes = [];
const textLyricLine = stripTags(lyricLine);

let textToken = '';

Array.from(textLyricLine).forEach((char, charIndex, allChars) => {
if (char === ' ') {
if (textToken) {
allTextNodes.push({
tokenText: textToken,
tokenTextIndex: charIndex - textToken.length,
});
textToken = '';
}
allTextNodes.push({
tokenText: ' ',
tokenTextIndex: charIndex,
});
} else {
textToken += char;
}
if (!allChars[charIndex + 1]) {
allTextNodes.push({
tokenText: textToken,
tokenTextIndex: charIndex - textToken.length + 1,
});
}
});
allTextNodes.push({
tokenText: '',
tokenTextIndex: textLyricLine.length,
});
return allTextNodes;
}

function getAllBreakpoints(allChordTokens, allLyricTokens) {
const chordLineBreakPoints = allChordTokens.map(
(token) => token.tokenTextIndex
);
const lyricLineBreakPoints = allLyricTokens.map(
(token) => token.tokenTextIndex
);
const allBreakpoints = _intersection(
chordLineBreakPoints,
lyricLineBreakPoints
);

const longestLine =
chordLineBreakPoints[chordLineBreakPoints.length - 1] >
lyricLineBreakPoints[lyricLineBreakPoints.length - 1]
? chordLineBreakPoints
: lyricLineBreakPoints;

const lastBreakpoint = allBreakpoints[allBreakpoints.length - 1];
const remainingBreakpoints = longestLine.slice(
longestLine.indexOf(lastBreakpoint) + 1
);
if (remainingBreakpoints.length) {
allBreakpoints.push(...remainingBreakpoints);
}
allBreakpoints.shift();

return allBreakpoints;
}

function getChordLyricsPairs(allBreakpoints, allChordTokens, allLyricTokens) {
const chordLyricsPairs = [];

allBreakpoints.forEach((breakpoint) => {
let chordLineFragment = '';
let textLineFragment = '';
while (
allChordTokens.length &&
allChordTokens[0].tokenTextIndex < breakpoint
) {
const currentNode = allChordTokens.shift();
chordLineFragment += currentNode.tokenHtml
? currentNode.tokenHtml
: currentNode.tokenText;
}

while (
allLyricTokens.length &&
allLyricTokens[0].tokenTextIndex < breakpoint
) {
const currentNode = allLyricTokens.shift();
textLineFragment += currentNode.tokenText;
}

chordLyricsPairs.push({
index: breakpoint,
chords: chordLineFragment,
lyrics: textLineFragment,
});
});
return chordLyricsPairs;
}
6 changes: 3 additions & 3 deletions packages/chord-mark/src/renderer/components/renderSong.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import alignedChordSpacer from '../spacers/chord/aligned';
import chordLyricsSpacer from '../spacers/chord/chordLyrics';

import renderChordLineModel from './renderChordLine';
import renderChordLyricLine from './renderChordLyricLine';
import renderEmptyLine from './renderEmptyLine';
import renderKeyDeclaration from './renderKeyDeclaration';
import renderLine from './renderLine';
Expand All @@ -15,7 +16,6 @@ import renderTimeSignature from './renderTimeSignature';
import songTpl from './tpl/song.js';
import renderAllSectionsLabels from '../helpers/renderAllSectionLabels';
import renderAllChords from '../helpers/renderAllChords';
import { mergeChordLineWithLyrics } from '../helpers/mergeChordLineWithLyrics';

import lineTypes from '../../parser/lineTypes';
import replaceRepeatedBars from '../replaceRepeatedBars';
Expand Down Expand Up @@ -69,7 +69,7 @@ export default function renderSong(
symbolType = 'chord',
transposeValue = 0,
useShortNamings = true,
chordLineRendering = 'merged',
chordLineRendering = 'discrete',
} = {}
) {
let { allLines, allKeys } = parsedSong;
Expand Down Expand Up @@ -283,7 +283,7 @@ export default function renderSong(
chartType,
});
if (chordLineToMerge) {
rendered = mergeChordLineWithLyrics(
rendered = renderChordLyricLine(
chordLineToMerge,
rendered
);
Expand Down
16 changes: 16 additions & 0 deletions packages/chord-mark/src/renderer/components/tpl/chordLyricLine.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const render = ({ chordLyricsPairs }) => {
let chordLyricLine = '<span class="cmChordLyricLine">';

chordLyricsPairs.forEach((pair) => {
chordLyricLine +=
'<span class="cmChordLyricPair">' +
`<span class="cmChordLine">${pair.chords}</span>` +
`<span class="cmLyricLine">${pair.lyrics}</span>` +
'</span>';
});

chordLyricLine += '</span>';

return chordLyricLine;
};
export default render;
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { mergeChordLineWithLyrics } from '../../../../src/renderer/helpers/mergeChordLineWithLyrics';
import { parseSong, renderSong } from '../../../../src/chordMark';
import stripTags from '../../../../src/core/dom/stripTags';

describe('mergeChordLineWithLyrics', () => {
test('Module', () => {
expect(mergeChordLineWithLyrics).toBeInstanceOf(Function);
});

describe('should properly split the chord lyrics lines into breakable blocs', () => {
describe.each([
/**/
Expand Down

0 comments on commit b146ee0

Please sign in to comment.