|
@@ -0,0 +1,526 @@
|
|
|
|
+import { ref } from "vue";
|
|
|
|
+import state from "../state"
|
|
|
|
+import { getQuery } from "/src/utils/queryString";
|
|
|
|
+const query: any = getQuery();
|
|
|
|
+
|
|
|
|
+interface IItem {
|
|
|
|
+ id?: string
|
|
|
|
+ y?: number
|
|
|
|
+ isLast?: boolean
|
|
|
|
+ childIndex?: number[]
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+interface IItemList {
|
|
|
|
+ parts: string[],
|
|
|
|
+ tieId?: string[],
|
|
|
|
+ staveSection?: IItem[],
|
|
|
|
+ vfmodifiers?: IItem[],
|
|
|
|
+ voltas?: number
|
|
|
|
+ vfcurve?: IItem[]
|
|
|
|
+ stavenote?: IItem[]
|
|
|
|
+}
|
|
|
|
+interface IMusicList {
|
|
|
|
+ [_key: string]: IItemList[]
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const container = ref();
|
|
|
|
+
|
|
|
|
+/** 曲谱配置: 重叠 */
|
|
|
|
+const resetGivenFormate = () => {
|
|
|
|
+ interface IItem {
|
|
|
|
+ id?: string
|
|
|
|
+ y?: number
|
|
|
|
+ isLast?: boolean
|
|
|
|
+ childIndex?: number[]
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ interface IItemList {
|
|
|
|
+ parts: string[],
|
|
|
|
+ tieId?: string[],
|
|
|
|
+ staveSection?: IItem[],
|
|
|
|
+ vfmodifiers?: IItem[],
|
|
|
|
+ voltas?: number
|
|
|
|
+ vfcurve?: IItem[]
|
|
|
|
+ stavenote?: IItem[]
|
|
|
|
+ }
|
|
|
|
+ interface IMusicList {
|
|
|
|
+ [_key: string]: IItemList[]
|
|
|
|
+ }
|
|
|
|
+ const musicList: IMusicList = {
|
|
|
|
+ '12200': [
|
|
|
|
+ {parts: ['0', '1'], tieId: ['1483']},
|
|
|
|
+ {parts: ['2'], tieId: ['1463']},
|
|
|
|
+ {parts: ['10'], tieId: ['1246']},
|
|
|
|
+ {parts: ['11'], tieId: ['2455']},
|
|
|
|
+ {parts: ['13'], tieId: ['1488', '1688']},
|
|
|
|
+ {parts: ['14', '15'], tieId: ['1272']},
|
|
|
|
+ {parts: ['16'], tieId: ['1264', '1368'], staveSection: [{id: 'section-0', y: -10}]},
|
|
|
|
+ ],
|
|
|
|
+ '12420': [
|
|
|
|
+ {parts: ['0'], tieId: ['1298', '1405', '1998', '2598', '3229', '2731', '2617']}
|
|
|
|
+ ],
|
|
|
|
+ '7729': [
|
|
|
|
+ {parts: ['3'], tieId: ['1498', '1660']}
|
|
|
|
+ ],
|
|
|
|
+ '7439': [
|
|
|
|
+ {parts: ['23'], vfmodifiers: [{id: 'modifiers-130', y: -18, isLast: true}]}
|
|
|
|
+ ],
|
|
|
|
+ '12711': [
|
|
|
|
+ { parts: ['0'], voltas: -12},
|
|
|
|
+ { parts: ['4'],voltas: -8},
|
|
|
|
+ ],
|
|
|
|
+ '3581': [
|
|
|
|
+ { parts: ['0'], voltas: -8},
|
|
|
|
+ ],
|
|
|
|
+ '6244': [
|
|
|
|
+ { parts: ['15'], stavenote: [{id: 'vf-auto1608', y: -15}]},
|
|
|
|
+ ],
|
|
|
|
+ '7473': [
|
|
|
|
+ { parts: ['0'], voltas: -8},
|
|
|
|
+ ]
|
|
|
|
+ }
|
|
|
|
+ const tieList = musicList[state.cbsExamSongId as string]
|
|
|
|
+ if (tieList) {
|
|
|
|
+ const tie = tieList.find((item) => item.parts.includes(query["part-index"] as string))
|
|
|
|
+ if (!tie) return
|
|
|
|
+ // 延音线和连线重叠
|
|
|
|
+ if (tie.tieId && tie.tieId.length) {
|
|
|
|
+ for(let tieIndex = 0; tieIndex < tie.tieId.length; tieIndex++){
|
|
|
|
+ const vftie: any = document.querySelector(`#vf-auto${tie.tieId[tieIndex]}-tie`)
|
|
|
|
+ const vfcurve = vftie?.parentNode?.parentNode?.querySelectorAll('.vf-curve')
|
|
|
|
+ if (vfcurve && vfcurve.length){
|
|
|
|
+ for(let i = 0; i < vfcurve.length; i++){
|
|
|
|
+ const result = collisionDetection(vftie, vfcurve[i])
|
|
|
|
+ if (result.isCollision){
|
|
|
|
+ vfcurve[i].style.transform = `translateY(-8px)`;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 小节数字
|
|
|
|
+ if (tie.staveSection && tie.staveSection.length) {
|
|
|
|
+ const sectionList = document.querySelectorAll('.vf-StaveSection')
|
|
|
|
+ sectionList.forEach((node, index) => {
|
|
|
|
+ node.classList.add(`section-${index}`)
|
|
|
|
+ })
|
|
|
|
+ for(let i = 0; i < tie.staveSection.length; i++){
|
|
|
|
+ const item: any = document.querySelector( '.' + tie.staveSection[i].id)
|
|
|
|
+ if (item){
|
|
|
|
+ item.style.transform = `translateY(${tie.staveSection[i].y}px)`;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // modifiers 里面的符号
|
|
|
|
+ if(tie.vfmodifiers && tie.vfmodifiers.length){
|
|
|
|
+ const modifierList = document.querySelectorAll('.vf-modifiers')
|
|
|
|
+ modifierList.forEach((node, index) => {
|
|
|
|
+ node.classList.add(`modifiers-${index}`)
|
|
|
|
+ })
|
|
|
|
+ for(let i = 0; i < tie.vfmodifiers.length; i++){
|
|
|
|
+ const modifier = tie.vfmodifiers[i]
|
|
|
|
+ const item: SVGAElement = document.querySelector( '.' + modifier.id)!
|
|
|
|
+ if (item){
|
|
|
|
+ if (modifier.isLast){
|
|
|
|
+ const lastEle: any = Array.from(item.childNodes).at(-1)
|
|
|
|
+ if (lastEle){
|
|
|
|
+ lastEle.style.transform = `translateY(${modifier.y}px)`;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 房子
|
|
|
|
+ if (tie.voltas){
|
|
|
|
+ const modifierList = document.querySelectorAll('.vf-Volta') as unknown as HTMLElement[]
|
|
|
|
+ modifierList.forEach((node, index) => {
|
|
|
|
+ node.style.transform = `translateY(${tie.voltas}px)`;
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 单个音符
|
|
|
|
+ if (tie.stavenote && tie.stavenote.length) {
|
|
|
|
+ for(let i = 0; i < tie.stavenote.length; i++){
|
|
|
|
+ const item = tie.stavenote[i]
|
|
|
|
+ const ele = document.querySelector('#' + item.id)! as unknown as HTMLElement
|
|
|
|
+ ele && (ele.style.transform = `translateY(${item.y}px)`)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 谱面优化
|
|
|
|
+const resetFormate = () => {
|
|
|
|
+ container.value = document.getElementById('scrollContainer')
|
|
|
|
+ if (state.extStyleConfigJson || !container.value) return;
|
|
|
|
+ const stafflines: SVGAElement[] = Array.from((container.value as HTMLElement).querySelectorAll(".staffline"));
|
|
|
|
+ const baseStep = 4; // 两个元素相间,的间距
|
|
|
|
+ const musicalDistance = 28; // 音阶与第一条线谱的间距,默认设置为28
|
|
|
|
+ for (let i = 0, len = stafflines.length; i < len; i++) {
|
|
|
|
+ const staffline = stafflines[i];
|
|
|
|
+ const stafflineBox = staffline.getBBox();
|
|
|
|
+ const stafflineCenter = stafflineBox.y + stafflineBox.height / 2;
|
|
|
|
+ const vfmeasures: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure"));
|
|
|
|
+ const vfcurve: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-curve"));
|
|
|
|
+ const vfvoices: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-voices"));
|
|
|
|
+ const vfbeams: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-beams"));
|
|
|
|
+ const vfties: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-ties"));
|
|
|
|
+ const vflines: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-line"));
|
|
|
|
+ const texts: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave text"));
|
|
|
|
+ const rects: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave rect[fill=none]"));
|
|
|
|
+ const staveSection: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure .vf-staveSection"));
|
|
|
|
+ const paths: SVGAElement[] = Array.from(staffline.querySelectorAll(".vf-measure > .vf-stave path"));
|
|
|
|
+ // 获取第一个线谱的y轴坐标
|
|
|
|
+ const firstLinePathY = paths[0]?.getBBox().y || 0
|
|
|
|
+ // 反复标记 和 小节碰撞
|
|
|
|
+ const repetWord = ["To Coda", "D.S. al Coda", "Coda"];
|
|
|
|
+ texts
|
|
|
|
+ .filter((n) => repetWord.includes(n.textContent || ""))
|
|
|
|
+ .forEach((t) => {
|
|
|
|
+ vfbeams.forEach((curve) => {
|
|
|
|
+ const result = collisionDetection(t, curve);
|
|
|
|
+ const prePath: SVGAElement = t?.previousSibling as unknown as SVGAElement;
|
|
|
|
+ if (result.isCollision) {
|
|
|
|
+ const shift_y = Number(t.getAttribute("y")) - (result.b1 - result.t2) - baseStep + "";
|
|
|
|
+ t.setAttribute("y", shift_y);
|
|
|
|
+ // console.log('音阶间距',shift_y)
|
|
|
|
+ if (prePath && prePath.getAttribute("stroke-width") === "0.3" && prePath.getAttribute("stroke") === "none" && (prePath.getAttribute("d")?.length || 0) > 3000) {
|
|
|
|
+ prePath.style.transform = `translateY(${-(result.b1 - result.t2 + baseStep)}px)`;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ vfvoices.forEach((curve) => {
|
|
|
|
+ const result = collisionDetection(t, curve);
|
|
|
|
+ const prePath: SVGAElement = t?.previousSibling as unknown as SVGAElement;
|
|
|
|
+ if (result.isCollision) {
|
|
|
|
+ const shift_y = Number(t.getAttribute("y")) - (result.b1 - result.t2) - baseStep + "";
|
|
|
|
+ t.setAttribute("y", shift_y);
|
|
|
|
+ // console.log('音阶间距',shift_y)
|
|
|
|
+ if (prePath && prePath.getAttribute("stroke-width") === "0.3" && prePath.getAttribute("stroke") === "none" && (prePath.getAttribute("d")?.length || 0) > 3000) {
|
|
|
|
+ prePath.style.transform = `translateY(${-(result.b1 - result.t2 + baseStep)}px)`;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ // 文字方框和飞线碰撞
|
|
|
|
+ staveSection.forEach((t) => {
|
|
|
|
+ let shift_y = 0;
|
|
|
|
+ [...vfcurve, ...vfties, ...vfvoices].forEach((curve) => {
|
|
|
|
+ const result = collisionDetection(t, curve);
|
|
|
|
+ if (result.isCollision) {
|
|
|
|
+ shift_y = Math.min(shift_y, result.t2 - result.b1 - baseStep);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ t.style.transform = `translateY(${shift_y}px)`;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 文字和小节碰撞
|
|
|
|
+ let vftexts = Array.from(staffline.querySelectorAll(".vf-text > text")).filter((n: any) => n.getBBox().y < stafflineCenter);
|
|
|
|
+ for (let i = 0; i < vftexts.length; i++) {
|
|
|
|
+ const _text = vftexts[i];
|
|
|
|
+ for (let j = 0; j < vftexts.length; j++) {
|
|
|
|
+ if (_text.parentNode === vftexts[j].parentNode) continue;
|
|
|
|
+ const result = collisionDetection(_text as SVGAElement, vftexts[j] as SVGAElement);
|
|
|
|
+ if (result.isCollision) {
|
|
|
|
+ if (_text.textContent === vftexts[j].textContent) {
|
|
|
|
+ vftexts[j].parentNode?.removeChild(vftexts[j]);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ vftexts = Array.from(staffline.querySelectorAll(".vf-text > text")).filter((n: any) => n.getBBox().y < stafflineCenter);
|
|
|
|
+ let maxY = 0;
|
|
|
|
+ let _vftexts: SVGAElement[] = [];
|
|
|
|
+
|
|
|
|
+ vftexts.forEach((vftext: any) => {
|
|
|
|
+ const textBox = vftext.getBBox();
|
|
|
|
+ if (textBox.y < stafflineCenter) {
|
|
|
|
+ maxY = Math.max(maxY, textBox.y + textBox.height);
|
|
|
|
+ //console.log('音阶间距',textBox.y, textBox.height)
|
|
|
|
+ _vftexts.push(vftext as SVGAElement);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ if (maxY !== 0 && _vftexts.length > 1) {
|
|
|
|
+ _vftexts.forEach((vftext) => {
|
|
|
|
+ vftext.setAttribute("y", maxY + "");
|
|
|
|
+ //console.log('音阶间距',maxY)
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ vftexts.forEach((vftext) => {
|
|
|
|
+ [...vfcurve, ...vfmeasures, ...vflines].forEach((vfmeasure) => {
|
|
|
|
+ let result = collisionDetection(vftext as SVGAElement, vfmeasure);
|
|
|
|
+ if (result.isCollision && result.b1 < result.b2 && result.t1 < result.b2 - (result.b2 - result.t2) / 2) {
|
|
|
|
+ const shift_y = Number(vftext.getAttribute("y")) - (result.b1 - result.t2) - baseStep + "";
|
|
|
|
+ vftext.setAttribute("y", shift_y);
|
|
|
|
+ //console.log('音阶间距',shift_y)
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ vftexts.forEach((vftext) => {
|
|
|
|
+ vftexts.forEach((text) => {
|
|
|
|
+ if (vftext.parentNode !== text.parentNode && !["marcato", "legato"].includes(vftext.textContent as string)) {
|
|
|
|
+ if (["marcato", "legato"].includes(text.textContent as string)) {
|
|
|
|
+ const result = collisionDetection(vftext as SVGAElement, text as SVGAElement, 30, 30);
|
|
|
|
+ if (result.isCollision) {
|
|
|
|
+ const textBBox = (vftext as SVGAElement).getBBox();
|
|
|
|
+ text.setAttribute("x", textBBox.x + textBBox.width + 5 + "");
|
|
|
|
+ text.setAttribute("y", textBBox.y + textBBox.height - 5 + "");
|
|
|
|
+ //console.log('音阶间距',textBBox.y + textBBox.height - 5 + "")
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ const result = collisionDetection(vftext as SVGAElement, text as SVGAElement);
|
|
|
|
+ if (result.isCollision) {
|
|
|
|
+ const _y = Number(vftext.getAttribute("y"));
|
|
|
|
+ const shift_y = result.b2 - result.t2 < 24 ? 24 : result.b2 - result.t2;
|
|
|
|
+ text.setAttribute("y", _y - shift_y - 0.5 + "");
|
|
|
|
+ //console.log('音阶间距',_y - shift_y - 0.5 + "")
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ // 修改音阶和线谱的间距
|
|
|
|
+ const clefList = ['C', 'G', 'D', 'A', 'E', 'B', 'F#', 'C#', 'G#', 'F', 'Bb', 'Eb', 'Ab', 'Db', 'Gb', 'Cb', 'Fb', 'D#', 'A#', 'E#']
|
|
|
|
+ const btransList = ['Bb', 'Eb', 'Ab', 'Db', 'Gb', 'Cb', 'Fb']
|
|
|
|
+ const jtrsnsList = ['F#', 'C#', 'G#', 'D#', 'A#', 'E#', 'B#']
|
|
|
|
+ vftexts.forEach((label: any) => {
|
|
|
|
+ const labelText = label.textContent as string
|
|
|
|
+ if (clefList.includes(labelText)){
|
|
|
|
+ const _y = Number(label.getAttribute("y"))
|
|
|
|
+ const endY = firstLinePathY ? firstLinePathY - musicalDistance : _y
|
|
|
|
+ label.setAttribute("y", endY)
|
|
|
|
+ }
|
|
|
|
+ if (btransList.includes(labelText)) {
|
|
|
|
+ label.textContent = labelText.replace('b','♭')
|
|
|
|
+ }
|
|
|
|
+ if (jtrsnsList.includes(labelText)) {
|
|
|
|
+ label.textContent = labelText.replace('#','♯')
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ const vftextBottom = Array.from(staffline.querySelectorAll(".vf-text > text")).filter((n: any) => n.getBBox().y > stafflineCenter);
|
|
|
|
+ const vflineBottom = Array.from(staffline.querySelectorAll(".vf-line")).filter((n: any) => n.getBBox().y > stafflineCenter);
|
|
|
|
+ // 去重
|
|
|
|
+ for (let i = 0; i < vftextBottom.length; i++) {
|
|
|
|
+ const _text = vftextBottom[i];
|
|
|
|
+ for (let j = 0; j < vftextBottom.length; j++) {
|
|
|
|
+ if (_text.parentNode === vftextBottom[j].parentNode) continue;
|
|
|
|
+ const result = collisionDetection(_text as SVGAElement, vftextBottom[j] as SVGAElement);
|
|
|
|
+ if (result.isCollision) {
|
|
|
|
+ if (_text.textContent === vftextBottom[j].textContent) {
|
|
|
|
+ vftextBottom[j].parentNode?.removeChild(vftextBottom[j]);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // 1,2线谱底部文字重叠问题
|
|
|
|
+ vftextBottom.forEach((vftext) => {
|
|
|
|
+ [...vfmeasures].forEach((n) => {
|
|
|
|
+ let result = collisionDetection(vftext as SVGAElement, n);
|
|
|
|
+ if (result.isCollision) {
|
|
|
|
+ vftext.setAttribute("y", result.b2 + Math.abs(result.t1 - Number(vftext.getAttribute("y"))) + "");
|
|
|
|
+ //console.log('音阶间距', result.b2 + Math.abs(result.t1 - Number(vftext.getAttribute("y"))) + "")
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ // 如果渐弱渐强有平行的文字
|
|
|
|
+ vflineBottom.forEach((line) => {
|
|
|
|
+ const texts: any[] = [];
|
|
|
|
+ if (line.nextElementSibling?.classList.contains("vf-line")) {
|
|
|
|
+ vftextBottom.forEach((text) => {
|
|
|
|
+ let result = collisionDetection(line as SVGAElement, text as SVGAElement, 20, 20);
|
|
|
|
+ if (result.isCollision) {
|
|
|
|
+ texts.push({
|
|
|
|
+ text: text as SVGAElement,
|
|
|
|
+ result,
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ if (texts.length === 1) {
|
|
|
|
+ const result = texts[0].result;
|
|
|
|
+ const text = texts[0].text;
|
|
|
|
+ if (result.x2 + result.w2 < result.x1) {
|
|
|
|
+ // 左
|
|
|
|
+ if (Math.abs(result.y2 - result.y1) > 10) {
|
|
|
|
+ text.setAttribute("y", result.y1 + result.h2 / 2 + "");
|
|
|
|
+ //console.log('音阶间距', result.y1 + result.h2 / 2 + "")
|
|
|
|
+ }
|
|
|
|
+ } else if (result.x2 > result.x1 + result.w1) {
|
|
|
|
+ // 右
|
|
|
|
+ if (Math.abs(result.y2 - result.y1) > 10) {
|
|
|
|
+ text.setAttribute("y", result.y1 + result.h2 / 2 + "");
|
|
|
|
+ //console.log('音阶间距', result.y1 + result.h2 / 2 + "")
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ if (Math.abs(result.x2 - result.x1) < Math.abs(result.x2 + result.w2 - result.x1 - result.w1)) {
|
|
|
|
+ // console.log(text, '有交集', '靠左')
|
|
|
|
+ text.setAttribute("x", result.x1 - result.w2 - 5 + "");
|
|
|
|
+ if (Math.abs(result.y2 - result.y1) > 10) {
|
|
|
|
+ text.setAttribute("y", result.y1 + result.h2 / 2 + "");
|
|
|
|
+ //console.log('音阶间距', result.y1 + result.h2 / 2 + "")
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ // console.log(text, '有交集', '靠右')
|
|
|
|
+ text.setAttribute("x", result.x1 + result.w1 + 5 + "");
|
|
|
|
+ if (Math.abs(result.y2 - result.y1) > 10) {
|
|
|
|
+ text.setAttribute("y", result.y1 + result.h2 / 2 + "");
|
|
|
|
+ //console.log('音阶间距', result.y1 + result.h2 / 2 + "")
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else if (texts.length === 2) {
|
|
|
|
+ const result1 = texts[0].result;
|
|
|
|
+ const text1 = texts[0].text;
|
|
|
|
+ const result2 = texts[1].result;
|
|
|
|
+ const text2 = texts[1].text;
|
|
|
|
+ text1.setAttribute("x", result1.x1 - result1.w2 - 5 + "");
|
|
|
|
+ if (Math.abs(result1.y2 - result1.y1) > 10) {
|
|
|
|
+ text1.setAttribute("y", result1.y1 + result1.h2 / 2 + "");
|
|
|
|
+ //console.log('音阶间距', result1.y1 + result1.h2 / 2 + "")
|
|
|
|
+ }
|
|
|
|
+ text2.setAttribute("x", result2.x1 + result2.w1 + 5 + "");
|
|
|
|
+ if (Math.abs(result2.y2 - result2.y1) > 10) {
|
|
|
|
+ text2.setAttribute("y", result2.y1 + result2.h2 / 2 + "");
|
|
|
|
+ //console.log('音阶间距', result2.y1 + result2.h2 / 2 + "")
|
|
|
|
+ }
|
|
|
|
+ } else if (texts.length === 3) {
|
|
|
|
+ // console.log(texts)
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ vftextBottom.forEach((vftext) => {
|
|
|
|
+ vftextBottom.forEach((text) => {
|
|
|
|
+ if (vftext.parentNode !== text.parentNode && !["marcato", "legato", "cresc.", "Cantabile"].includes(vftext.textContent as string)) {
|
|
|
|
+ if (["marcato", "legato", "cresc.", "Cantabile"].includes(text.textContent as string)) {
|
|
|
|
+ const result = collisionDetection(vftext as SVGAElement, text as SVGAElement, 30, 30);
|
|
|
|
+ if (result.isCollision) {
|
|
|
|
+ const textBBox = (vftext as SVGAElement).getBBox();
|
|
|
|
+ text.setAttribute("x", textBBox.x + textBBox.width + 5 + "");
|
|
|
|
+ text.setAttribute("y", textBBox.y + textBBox.height - 5 + "");
|
|
|
|
+ //console.log('音阶间距', textBBox.y + textBBox.height - 5 + "")
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ const result = collisionDetection(vftext as SVGAElement, text as SVGAElement);
|
|
|
|
+ if (result.isCollision) {
|
|
|
|
+ text.setAttribute("y", result.y1 + result.h1 + result.h2 + "");
|
|
|
|
+ //console.log('音阶间距', result.y1 + result.h1 + result.h2 + "")
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // setTimeout(() => this.resetGlobalText());
|
|
|
|
+};
|
|
|
|
+// 技巧文本
|
|
|
|
+const resetGlobalText = () => {
|
|
|
|
+ const svg = container.value.querySelector("svg");
|
|
|
|
+ if (!svg) return;
|
|
|
|
+ const svgBBox = svg.getBBox();
|
|
|
|
+ let vfstavetempo: SVGAElement[] = Array.from(container.value.querySelectorAll(".vf-stavetempo")).reduce((eles: SVGAElement[], value: any) => {
|
|
|
|
+ if (eles.find((n) => n.outerHTML === value.outerHTML)) value?.parentNode?.removeChild(value);
|
|
|
|
+ else eles.push(value);
|
|
|
|
+ return eles;
|
|
|
|
+ }, []);
|
|
|
|
+ const staffline: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline"));
|
|
|
|
+ const vfmeasures: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline > .vf-measure"));
|
|
|
|
+ const vftexts: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline > .vf-text"));
|
|
|
|
+ const vfcurves: SVGAElement[] = Array.from(container.value.querySelectorAll(".staffline > .vf-curve"));
|
|
|
|
+
|
|
|
|
+ vfstavetempo.forEach((child: SVGAElement) => {
|
|
|
|
+ let _y = 0;
|
|
|
|
+ [...vfmeasures, ...vftexts, ...vfcurves].forEach((ele) => {
|
|
|
|
+ const result = collisionDetection(child as SVGAElement, ele);
|
|
|
|
+ if (result.isCollision && (result.b1 < result.b2 || result.r1 > result.l2 || result.l1 < result.r2)) {
|
|
|
|
+ _y = Math.min(_y, result.t2 - result.b1);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ if (_y !== 0) {
|
|
|
|
+ child.style.transform = `translateY(${_y}px)`;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const childBBox = child.getBBox();
|
|
|
|
+ const rightY = (childBBox.x + childBBox.width) * 0.7 - Number(svg.getAttribute("width"));
|
|
|
|
+ if (rightY > 0) {
|
|
|
|
+ [...staffline, ...vfstavetempo].forEach((tempo) => {
|
|
|
|
+ if (child != tempo) {
|
|
|
|
+ const result = collisionDetection(child as SVGAElement, tempo, Math.abs(rightY), Math.abs(_y));
|
|
|
|
+ if (result.isCollision) {
|
|
|
|
+ _y = result.t2 - result.b1;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ child.style.transform = `translate(-${rightY / 0.7}px,${_y}px)`;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ if (svgBBox.y < 0) {
|
|
|
|
+ svg.setAttribute("height", Number(svg.getAttribute("height")) - svgBBox.y + 10);
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 碰撞检测
|
|
|
|
+const collisionDetection = (a: SVGAElement, b: SVGAElement, distance: number = 0, distance_y: number = 0) => {
|
|
|
|
+ const abbox = a.getBBox();
|
|
|
|
+ const bbbox = b.getBBox();
|
|
|
|
+ let t1 = abbox.y - distance_y;
|
|
|
|
+ let l1 = abbox.x - distance;
|
|
|
|
+ let r1 = abbox.x + abbox.width + distance;
|
|
|
|
+ let b1 = abbox.y + abbox.height + distance_y;
|
|
|
|
+
|
|
|
|
+ let t2 = bbbox.y;
|
|
|
|
+ let l2 = bbbox.x;
|
|
|
|
+ let r2 = bbbox.x + bbbox.width;
|
|
|
|
+ let b2 = bbbox.y + bbbox.height;
|
|
|
|
+ if (b1 < t2 || l1 > r2 || t1 > b2 || r1 < l2) {
|
|
|
|
+ // 表示没碰上
|
|
|
|
+ return {
|
|
|
|
+ isCollision: false,
|
|
|
|
+ t1,
|
|
|
|
+ l1,
|
|
|
|
+ r1,
|
|
|
|
+ b1,
|
|
|
|
+ t2,
|
|
|
|
+ l2,
|
|
|
|
+ r2,
|
|
|
|
+ b2,
|
|
|
|
+ x1: abbox.x,
|
|
|
|
+ y1: abbox.y,
|
|
|
|
+ x2: bbbox.x,
|
|
|
|
+ y2: bbbox.y,
|
|
|
|
+ h1: abbox.height,
|
|
|
|
+ h2: bbbox.height,
|
|
|
|
+ w1: abbox.width,
|
|
|
|
+ w2: bbbox.width,
|
|
|
|
+ };
|
|
|
|
+ } else {
|
|
|
|
+ return {
|
|
|
|
+ isCollision: true,
|
|
|
|
+ t1,
|
|
|
|
+ l1,
|
|
|
|
+ r1,
|
|
|
|
+ b1,
|
|
|
|
+ t2,
|
|
|
|
+ l2,
|
|
|
|
+ r2,
|
|
|
|
+ b2,
|
|
|
|
+ x1: abbox.x,
|
|
|
|
+ y1: abbox.y,
|
|
|
|
+ x2: bbbox.x,
|
|
|
|
+ y2: bbbox.y,
|
|
|
|
+ h1: abbox.height,
|
|
|
|
+ h2: bbbox.height,
|
|
|
|
+ w1: abbox.width,
|
|
|
|
+ w2: bbbox.width,
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+};
|