lex 1 năm trước cách đây
mục cha
commit
66fe61219b

+ 2 - 2
index.html

@@ -8,11 +8,11 @@
     content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
   <meta http-equiv="Cache-control" content="no-cache">
   <meta http-equiv="Cache" content="no-cache">
-  <!-- <meta http-equiv="Content-Security-Policy" content="
+  <meta http-equiv="Content-Security-Policy" content="
                               default-src * data: blob: ws: wss: gap://ready file://*;
                               style-src * 'unsafe-inline';
                               script-src * 'unsafe-inline' 'unsafe-eval';
-                              connect-src * ws: wss:;"> -->
+                              connect-src * ws: wss:;">
   <meta name="apple-mobile-web-app-capable" content="yes" />
   <!-- 设置苹果工具栏颜色 -->
   <meta name="apple-mobile-web-app-status-bar-style" content="black" />

+ 2 - 3
public/osmd/index.html

@@ -56,8 +56,8 @@
     osmd.EngravingRules.CompactMode = true;
     osmd.EngravingRules.PageTopMarginNarrow = 5.0; // for compact mode
     osmd.EngravingRules.PageBottomMargin = 15.0;
-
-    console.log(osmd, 'osmd.EngravingRules')
+    osmd.EngravingRules.PageLeftMargin = 1.0
+    osmd.EngravingRules.PageRightMargin = 1.0
 
 
 
@@ -124,7 +124,6 @@
         .then(
           function () {
             for (let i = 0; i < osmd.Sheet.Instruments.length; i++) {
-              // console.log(osmd.Sheet.Instruments[i].Name);
               osmd.Sheet.Instruments[i].Visible = i === partIndex;
             }
             osmd.zoom = .5

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
public/osmd/opensheetmusicdisplay.min.js


+ 6 - 0
src/views/accompany/StringUtil.ts

@@ -0,0 +1,6 @@
+export class StringUtil {
+  public static StringContainsSeparatedWord(str: string, wordRegExString: string, ignoreCase = false): boolean {
+    const regExp = new RegExp("( |^)" + wordRegExString + "([ .]|$)", ignoreCase ? "i" : undefined);
+    return regExp.test(str);
+  }
+}

+ 352 - 0
src/views/accompany/instrument.ts

@@ -0,0 +1,352 @@
+import { StringUtil } from "./StringUtil";
+import { isSpecialMark, isSpeedKeyword, isGradientWords, GRADIENT_SPEED_RESET_TAG } from "./speed-tag";
+
+export const formatXML = (xml: string): string => {
+  if (!xml) return "";
+  const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+  const measures: any = xmlParse.getElementsByTagName("measure");
+  // const repeats: any = Array.from(xmlParse.querySelectorAll('repeat'))
+  // 处理重复小节信息
+  // let speed = -1
+  let beats = -1;
+  let beatType = -1;
+  // 小节中如果没有节点默认为休止符
+  for (const measure of measures) {
+    if (beats === -1 && measure.getElementsByTagName("beats").length) {
+      beats = parseInt(measure.getElementsByTagName("beats")[0].textContent || "4");
+    }
+    if (beatType === -1 && measure.getElementsByTagName("beat-type").length) {
+      beatType = parseInt(measure.getElementsByTagName("beat-type")[0].textContent || "4");
+    }
+    // if (speed === -1 && measure.getElementsByTagName('per-minute').length) {
+    //   speed = parseInt(measure.getElementsByTagName('per-minute')[0].textContent || this.firstLib?.speed)
+    // }
+    const divisions = parseInt(measure.getElementsByTagName("divisions")[0]?.textContent || "256");
+    if (measure.getElementsByTagName("note").length === 0) {
+      const forwardTimeElement = measure.getElementsByTagName("forward")[0]?.getElementsByTagName("duration")[0];
+      if (forwardTimeElement) {
+        forwardTimeElement.textContent = "0";
+      }
+      measure.innerHTML =
+        measure.innerHTML +
+        `
+        <note>
+          <rest measure="yes"/>
+          <duration>${divisions * beats}</duration>
+          <voice>1</voice>
+          <type>whole</type>
+        </note>`;
+    }
+  }
+  return new XMLSerializer().serializeToString(xmlParse);
+};
+
+
+
+export const onlyVisible = (xml: string, partIndex: number): string => {
+  if (!xml) return "";
+  const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+  const partList = xmlParse.getElementsByTagName("part-list")?.[0]?.getElementsByTagName("score-part") || [];
+  const partListNames = Array.from(partList).map((item) => item.getElementsByTagName("part-name")?.[0].textContent || "");
+  const parts: any = xmlParse.getElementsByTagName("part");
+  // const firstTimeInfo = parts[0]?.getElementsByTagName('metronome')[0]?.parentElement?.parentElement?.cloneNode(true)
+  const part: any = parts[0]
+  const firstMeasures = [...part.getElementsByTagName("measure")];
+  const metronomes = [...part.getElementsByTagName("metronome")];
+  const words = [...part.getElementsByTagName("words")];
+  const codas = [...part.getElementsByTagName("coda")];
+  const rehearsals = [...part.getElementsByTagName("rehearsal")];
+
+  /** 第一分谱如果是约定的配置分谱则跳过 */
+  if (partListNames[0]?.toLocaleUpperCase?.() === "COMMON") {
+    partIndex++;
+    partListNames.shift();
+  }
+  const visiblePartInfo = partList[partIndex];
+  // console.log(visiblePartInfo, partIndex)
+  if (visiblePartInfo) {
+    const id = visiblePartInfo.getAttribute("id");
+    Array.from(parts).forEach((part: any) => {
+      if (part && part.getAttribute("id") !== id) {
+        part.parentNode?.removeChild(part);
+        // 不等于第一行才添加避免重复添加
+      } else if (part && part.getAttribute("id") !== "P1") {
+        // 速度标记仅保留最后一个
+        const metronomeData: {
+          [key in string]: Element;
+        } = {};
+        for (let i = 0; i < metronomes.length; i++) {
+          const metronome = metronomes[i];
+          const metronomeContainer = metronome.parentElement?.parentElement?.parentElement;
+          if (metronomeContainer) {
+            const index = firstMeasures.indexOf(metronomeContainer);
+            metronomeData[index] = metronome;
+          }
+        }
+        Object.values(metronomeData).forEach((metronome) => {
+          const metronomeContainer: any = metronome.parentElement?.parentElement;
+          const parentMeasure: any = metronomeContainer?.parentElement;
+          const measureMetronomes = [...(parentMeasure?.childNodes || [])];
+          const metronomesIndex = metronomeContainer ? measureMetronomes.indexOf(metronomeContainer) : -1;
+          // console.log(parentMeasure)
+          if (parentMeasure && metronomesIndex > -1) {
+            const index = firstMeasures.indexOf(parentMeasure);
+            const activeMeasure = part.getElementsByTagName("measure")[index];
+            setElementNoteBefore(metronomeContainer, parentMeasure, activeMeasure);
+            //   console.log(measureMetronomes, metronomesIndex, activeMeasure?.childNodes, activeMeasure?.childNodes[metronomesIndex])
+            //   activeMeasure?.insertBefore(metronomeContainer.cloneNode(true), activeMeasure?.childNodes[metronomesIndex])
+            //   // part.getElementsByTagName('measure')[index]?.appendChild(metronomeContainer.cloneNode(true))
+            //   // console.log(index, parentMeasure, firstMeasures.indexOf(parentMeasure))
+          }
+        });
+        /** word比较特殊需要精确到note位置 */
+        words.forEach((word) => {
+          let text = word.textContent || "";
+          text = ["cresc."].includes(text) ? "" : text;
+          if ((isSpecialMark(text) || isSpeedKeyword(text) || isGradientWords(text) || isRepeatWord(text) || GRADIENT_SPEED_RESET_TAG) && text) {
+            const wordContainer = word.parentElement?.parentElement;
+            const parentMeasure = wordContainer?.parentElement;
+            const measureWords = [...(parentMeasure?.childNodes || [])];
+            const wordIndex = wordContainer ? measureWords.indexOf(wordContainer) : -1;
+            if (wordContainer && parentMeasure && wordIndex > -1) {
+              const index = firstMeasures.indexOf(parentMeasure);
+              const activeMeasure = part.getElementsByTagName("measure")[index];
+              // 找当前小节是否包含word标签
+              const _words: any = Array.from(activeMeasure?.getElementsByTagName("words") || []);
+              // 遍历word标签,检查是否和第一小节重复,如果有重复则不平移word
+              const total = _words.reduce((total: any, _word) => {
+                if (_word.textContent?.includes(text)) {
+                  total++;
+                }
+                return total;
+              }, 0);
+              if (total === 0) {
+                setElementNoteBefore(wordContainer, parentMeasure, activeMeasure);
+
+              }
+            }
+          }
+        });
+        /** word比较特殊需要精确到note位置 */
+        codas.forEach((coda) => {
+          const wordContainer = coda.parentElement?.parentElement;
+          const parentMeasure = wordContainer?.parentElement;
+          const measureWords = [...(parentMeasure?.childNodes || [])];
+          const wordIndex = wordContainer ? measureWords.indexOf(wordContainer) : -1;
+          if (wordContainer && parentMeasure && wordIndex > -1) {
+            const index = firstMeasures.indexOf(parentMeasure);
+            const activeMeasure = part.getElementsByTagName("measure")[index];
+
+            setElementNoteBefore(wordContainer, parentMeasure, activeMeasure);
+
+          }
+        });
+        rehearsals.forEach((rehearsal) => {
+          const container = rehearsal.parentElement?.parentElement;
+          const parentMeasure = container?.parentElement;
+          // console.log(rehearsal)
+          if (parentMeasure) {
+            const index = firstMeasures.indexOf(parentMeasure);
+            part.getElementsByTagName("measure")[index]?.appendChild(container.cloneNode(true));
+            // console.log(index, parentMeasure, firstMeasures.indexOf(parentMeasure))
+          }
+        });
+      } else {
+        words.forEach((word) => {
+          const text = word.textContent || "";
+          if (isSpeedKeyword(text) && text) {
+            const wordContainer = word.parentElement?.parentElement?.parentElement;
+            if (wordContainer && wordContainer.firstElementChild && wordContainer.firstElementChild !== word) {
+              const wordParent = word.parentElement?.parentElement;
+              const fisrt = wordContainer.firstElementChild;
+              wordContainer.insertBefore(wordParent, fisrt);
+            }
+          }
+        });
+      }
+
+      // 最后一个小节的结束线元素不在最后 调整
+      if (part && part.getAttribute("id") === id) {
+        const barlines = part.getElementsByTagName("barline");
+        const lastParent = barlines[barlines.length - 1]?.parentElement;
+        if (lastParent?.lastElementChild?.tagName !== "barline") {
+          const children: any = lastParent?.children || [];
+          for (const el of children) {
+            if (el.tagName === "barline") {
+              // 将结束线元素放到最后
+              lastParent?.appendChild(el);
+              break;
+            }
+          }
+        }
+      }
+    });
+    Array.from(partList).forEach((part) => {
+      if (part && part.getAttribute("id") !== id) {
+        part.parentNode?.removeChild(part);
+      }
+    });
+    // 处理装饰音问题
+    const notes = xmlParse.getElementsByTagName("note");
+    const getNextvNoteDuration = (i: number) => {
+      let nextNote = notes[i + 1];
+      // 可能存在多个装饰音问题,取下一个非装饰音时值
+      for (let index = i; index < notes.length; index++) {
+        const note = notes[index];
+        if (!note.getElementsByTagName("grace")?.length) {
+          nextNote = note;
+          break;
+        }
+      }
+      const nextNoteDuration = nextNote?.getElementsByTagName("duration")[0];
+      return nextNoteDuration;
+    };
+    Array.from(notes).forEach((note, i) => {
+      const graces = note.getElementsByTagName("grace");
+      if (graces && graces.length) {
+        // if (i !== 0) {
+        // note.appendChild(getNextvNoteDuration(i)?.cloneNode(true))
+        // }
+      }
+    });
+  }
+  // console.log(xmlParse)
+  return new XMLSerializer().serializeToString(appoggianceFormate(xmlParse));
+};
+
+
+// 倚音后连音线
+export const appoggianceFormate = (xmlParse: Document): Document => {
+  if (!xmlParse) return xmlParse;
+  const graces: any = xmlParse.querySelectorAll("grace");
+  if (!graces.length) return xmlParse;
+  const getNextElement = (el: HTMLElement): HTMLElement => {
+    if (el.querySelector("grace")) {
+      return getNextElement(el?.nextElementSibling as HTMLElement);
+    }
+    return el;
+  };
+  for (const grace of graces) {
+    const notations = grace.parentElement?.querySelector("notations");
+    if (notations && notations.querySelectorAll("slur").length > 1) {
+      const nextEle: Element = getNextElement(grace.parentElement?.nextElementSibling as HTMLElement);
+      if (nextEle && nextEle.querySelectorAll("slur").length > 0) {
+        const slurNumber = Array.from(nextEle.querySelector("notations")?.children || []).map((el: Element) => {
+          return el.getAttribute("number");
+        });
+        const slurs = notations.querySelectorAll("slur");
+        for (const nota of slurs) {
+          if (!slurNumber.includes(nota.getAttribute("number"))) {
+            nextEle.querySelector("notations")?.appendChild(nota);
+          }
+        }
+      }
+    }
+  }
+  return xmlParse;
+};
+
+
+/**
+ * 添加第一分谱信息至当前分谱
+ * @param ele 需要插入的元素
+ * @param fitstParent 合奏谱第一个分谱
+ * @param parent 需要添加的分谱
+ */
+const setElementNoteBefore = (ele: Element, fitstParent: Element, parent?: Element | null) => {
+  let noteIndex = 0;
+  if (!fitstParent) {
+    return;
+  }
+  for (let index = 0; index < fitstParent.childNodes.length; index++) {
+    const element = fitstParent.childNodes[index];
+    if (element.nodeName === "note") {
+      noteIndex++;
+    }
+    if (element === ele) {
+      break;
+    }
+  }
+  if (noteIndex === 0 && parent) {
+    parent.insertBefore(ele, parent.childNodes[0]);
+    return;
+  }
+  if (parent && parent.childNodes.length > 0) {
+    let noteIndex2 = 0;
+    const notes = Array.from(parent.childNodes).filter((child) => child.nodeName === "note");
+    const lastNote = notes[notes.length - 1];
+    if (noteIndex >= notes.length && lastNote) {
+      parent.insertBefore(ele, parent.childNodes[Array.from(parent.childNodes).indexOf(lastNote)]);
+      return;
+    }
+    for (let index = 0; index < notes.length; index++) {
+      const element = notes[index];
+      if (element.nodeName === "note") {
+        noteIndex2 = noteIndex2 + 1;
+        if (noteIndex2 === noteIndex) {
+          parent.insertBefore(ele, element);
+          break;
+        }
+      }
+    }
+  }
+  // console.log(noteIndex, parent)
+};
+
+
+
+
+/**
+ * 检查传入文字是否为重复关键词
+ * @param text 总谱xml
+ * @returns 是否是重复关键词
+ */
+export const isRepeatWord = (text: string): boolean => {
+  if (text) {
+    const innerText = text.toLocaleLowerCase();
+    const dsRegEx = "d\\s?\\.s\\.";
+    const dcRegEx = "d\\.\\s?c\\.";
+
+    return (
+      innerText === "@" ||
+      StringUtil.StringContainsSeparatedWord(innerText, dsRegEx + " al fine", true) ||
+      StringUtil.StringContainsSeparatedWord(innerText, dsRegEx + " al coda", true) ||
+      StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + " al fine", true) ||
+      StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + " al coda", true) ||
+      StringUtil.StringContainsSeparatedWord(innerText, dcRegEx) ||
+      StringUtil.StringContainsSeparatedWord(innerText, "da\\s?capo", true) ||
+      StringUtil.StringContainsSeparatedWord(innerText, dsRegEx, true) ||
+      StringUtil.StringContainsSeparatedWord(innerText, "dal\\s?segno", true) ||
+      StringUtil.StringContainsSeparatedWord(innerText, "al\\s?coda", true) ||
+      StringUtil.StringContainsSeparatedWord(innerText, "to\\s?coda", true) ||
+      StringUtil.StringContainsSeparatedWord(innerText, "a (la )?coda", true) ||
+      StringUtil.StringContainsSeparatedWord(innerText, "fine", true) ||
+      StringUtil.StringContainsSeparatedWord(innerText, "coda", true) ||
+      StringUtil.StringContainsSeparatedWord(innerText, "segno", true)
+    );
+  }
+  return false;
+};
+
+
+
+/** 从xml中获取自定义信息,并删除多余的字符串 */
+export const getCustomInfo = (xml: string): any => {
+  const data = {
+    showSpeed: true,
+    parsedXML: xml,
+  };
+  const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
+  const words: any = xmlParse.getElementsByTagName("words");
+  for (const word of words) {
+    if (word && word.textContent?.trim() === "隐藏速度") {
+      data.showSpeed = false;
+      word.textContent = "";
+    }
+    if (word && word.textContent?.trim() === "@") {
+      word.textContent = "segno";
+    }
+  }
+  data.parsedXML = new XMLSerializer().serializeToString(xmlParse);
+  return data;
+};

+ 5 - 6
src/views/accompany/music-detail.module.less

@@ -45,7 +45,7 @@
   }
 
   .info {
-    padding-left: 12px;
+    width: 100%;
 
     p {
       margin: 0;
@@ -53,17 +53,16 @@
 
     .names {
       padding-top: 8px;
-      display: flex;
-      align-items: center;
       line-height: 25px;
       font-weight: 600;
       font-size: 18px;
       color: #131415;
-      padding-right: 6px;
-      max-width: 180px;
+      max-width: 95%;
       overflow: hidden;
       text-overflow: ellipsis;
       white-space: nowrap;
+      text-align: center;
+      margin: 0 auto;
     }
 
     .author {
@@ -153,4 +152,4 @@
 .staffChange {
   --van-popup-close-icon-color: #333333;
   --van-popup-close-icon-size: 18px;
-}
+}

+ 20 - 5
src/views/accompany/music-detail.tsx

@@ -19,6 +19,7 @@ import Download from './download'
 import { svgtopng } from './formatSvgToImg'
 import requestOrigin from 'umi-request'
 import { getInstrumentName } from '@/constant/instruments'
+import { formatXML, getCustomInfo, onlyVisible } from './instrument'
 
 export default defineComponent({
   name: 'music-detail',
@@ -65,29 +66,42 @@ export default defineComponent({
     const musicIframeLoad = async () => {
       const iframeRef: any = document.getElementById('staffIframeRef')
       if (iframeRef && iframeRef.contentWindow.renderXml) {
-        iframeRef.contentWindow.renderXml(staffData.details.xmlFileUrl, staffData.partXmlIndex)
+        const res = await requestOrigin.get(staffData.details.xmlFileUrl, { mode: 'cors' })
+        const parseXmlInfo = getCustomInfo(res)
+        const xml = formatXML(parseXmlInfo.parsedXML)
+        const currentXml = onlyVisible(xml, staffData.partXmlIndex)
+        iframeRef.contentWindow.renderXml(currentXml, staffData.partXmlIndex)
+        // iframeRef.contentWindow.renderXml(staffData.details.xmlFileUrl, staffData.partXmlIndex)
       }
     }
     const resetRender = async () => {
       const iframeRef: any = document.getElementById('staffIframeRef')
       if (iframeRef && iframeRef.contentWindow.renderXml) {
         loading.value = true
-        iframeRef.contentWindow.resetRender(staffData.partXmlIndex)
+        // iframeRef.contentWindow.resetRender(staffData.partXmlIndex)
+        // const res = await requestOrigin.get(staffData.details.xmlFileUrl, { mode: 'cors' })
+        // const parseXmlInfo = getCustomInfo(res)
+        // const xml = formatXML(parseXmlInfo.parsedXML)
+        // const currentXml = onlyVisible(xml, staffData.selectedPartIndex)
+        // iframeRef.contentWindow.renderXml(currentXml, staffData.selectedPartIndex)
+        const res = await requestOrigin.get(staffData.details.xmlFileUrl, { mode: 'cors' })
+        const parseXmlInfo = getCustomInfo(res)
+        const xml = formatXML(parseXmlInfo.parsedXML)
+        const currentXml = onlyVisible(xml, staffData.partXmlIndex)
+        iframeRef.contentWindow.renderXml(currentXml, 0)
       }
     }
 
     const resetRenderPage = (type: string, xmlUrl: string) => {
-      console.log(type, xmlUrl)
       const iframeRef: any = document.getElementById('staffIframeRef')
       if (iframeRef && iframeRef.contentWindow.renderXml) {
-        console.log('resetRenderPage')
         iframeRef.contentWindow.resetRenderPage(type, xmlUrl)
       }
     }
 
     const renderStaff = async () => {
       try {
-        // staffData.iframeSrc = `${location.origin}/osmd/index.html`
+        // staffData.iframeSrc = `https://mantest.dayaedu.com/accompany/osmd/index.html`
         staffData.iframeSrc = `${location.origin}${location.pathname}osmd/index.html`
       } catch (error) {
         //
@@ -176,6 +190,7 @@ export default defineComponent({
 
       player.value.on('ready', () => {
         staffData.audioReady = true
+        player.value.muted = false
         nextTick(async () => {
           // if (staffData.details.musicSheetType === 'SINGLE') {
           //   loading.value = false

+ 88 - 0
src/views/accompany/speed-tag.ts

@@ -0,0 +1,88 @@
+/**
+  Grava壮板=40
+  Largo广板=46
+  Lento慢板=52
+  Adagio柔板=56
+  Larghetto小广板=60
+  Andante行板=66
+  Anderato/Andantino小行板=69
+  Moderato中板=88
+  Allegretto小快板=108
+  Allegro Moderato=108 // 考级需要
+  Allegro快板=132
+  Vivace快速有生气=152
+  Vivo快速有生气=160
+  Vivacissimo极其活泼的快板=168
+  Presto急板=184
+  Prestissimo最急板=208
+ */
+
+// import {TextAlignmentEnum} from "../../Common/Enums/TextAlignment";
+
+export const SpeedTag: { [key in string]: number } = {
+  "Grava": 40,
+  "Largo": 46,
+  "Lento": 52,
+  "Adagio": 56,
+  "Larghetto": 60,
+  "Andante": 66,
+  "Anderato": 69,
+  "Andantino": 69,
+  "Moderato": 88,
+  "Allegretto": 108,
+  "Allegro Moderato": 108,
+  "Allegro": 132,
+  "Vivace": 152,
+  "Vivo": 160,
+  "Vivacissimo": 168,
+  "Presto": 184,
+  "Prestissimo": 208
+};
+
+export const SpecialMarks: string[] = ["纯律", "纯律结束"];
+
+export const HideWords: string[] = ["跳过下一个", "b", "#", "§", "º", "X"];
+
+export const GradientWords: string[] = ["poco rit.", "rall.", "rit.", "accel.", "molto rit.", "molto rall", "lentando", "poco accel.", "calando"];
+
+export const GRADIENT_SPEED_CLOSE_TAG = "结束范围速度";
+
+export const GRADIENT_SPEED_RESET_TAG = "a tempo";
+
+export const SpecialWords: string[] = [GRADIENT_SPEED_CLOSE_TAG];
+
+export const SpeedKeyword = "速度 ";
+export const SpeedHiddenKeyword = "仅文本速度 ";
+
+/** 是否为速度关键词 */
+export function isSpeedKeyword(str: string): boolean {
+  return str.indexOf(SpeedKeyword) === 0;
+};
+
+/** 是否为速度关键词 */
+export function isSpeedHiddenKeyword(str: string): boolean {
+  return str.indexOf(SpeedHiddenKeyword) === 0;
+};
+
+/** 格式化速度关键词移除前缀 */
+export function formatSpeedKeyword(str: string): string {
+  return str.replace(SpeedHiddenKeyword, "").replace(SpeedKeyword, "");
+};
+
+/** 是否是渐变速度关键词 */
+export function isGradientWords(str: string): boolean {
+  return GradientWords.includes(str);
+};
+
+/**
+ * 是否为特殊标记
+ * 包含中文与英文,中文正则存在部分问题
+ */
+export function isSpecialMark(str: string): boolean {
+  return [...Object.keys(SpeedTag), ...SpecialMarks, ...SpecialWords, ...HideWords]
+    .map((s: string) => s.trim().toLocaleUpperCase()).includes(str.toLocaleUpperCase().trim());
+}
+
+// export function isTopFont(textAlignment): boolean {
+//   return [TextAlignmentEnum.CenterTop, TextAlignmentEnum.RightTop].includes(textAlignment);
+// };

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác