From dfc5a0103dd6e4f063fa2da50d1372bd3276c4f0 Mon Sep 17 00:00:00 2001 From: Kei Yamashita <44686790+kei95@users.noreply.github.com> Date: Mon, 2 Sep 2024 10:49:08 +0900 Subject: [PATCH] fix: Apply temporary logic to apply missing fonts in the current excalidraw package (#56) * fix: Apply missing fonts and logic to adopt new fonts in svg loader * fix: Remove accidentally added tailing comma * fix: Gracefully check textElement's fontFamily in case it's missing * fix: Remove tailing comma * fix: Apply prettier * fix: replace textPath to text element to draw the same animation * fix: Update elements check to by group --- public/index.html | 24 +++++++++++ src/animate.ts | 20 +++++----- src/useLoadSvg.ts | 100 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 9 deletions(-) diff --git a/public/index.html b/public/index.html index f2e6800..72c31cf 100644 --- a/public/index.html +++ b/public/index.html @@ -25,6 +25,30 @@ Learn how to configure a non-root public URL by running `npm run build`. --> Excalidraw Animate + + + + + diff --git a/src/animate.ts b/src/animate.ts index 33862ba..4e0478f 100644 --- a/src/animate.ts +++ b/src/animate.ts @@ -290,20 +290,22 @@ const animateText = ( pathForTextIndex += 1; const path = svg.ownerDocument.createElementNS(SVG_NS, "path"); path.setAttribute("id", "pathForText" + pathForTextIndex); + + const textPath = svg.ownerDocument.createTextNode(ele.textContent ?? ""); + ele.textContent = " "; // HACK for Firebox as `null` does not work + ele.appendChild(textPath); + ele.setAttribute("opacity", "0.0"); + const animate = svg.ownerDocument.createElementNS(SVG_NS, "animate"); - animate.setAttribute("attributeName", "d"); - animate.setAttribute("from", `m${x} ${y} h0`); - animate.setAttribute("to", `m${x} ${y} h${width}`); + animate.setAttribute("attributeName", "opacity"); + animate.setAttribute("from", `0.0`); + animate.setAttribute("to", `1.0`); animate.setAttribute("begin", `${currentMs}ms`); animate.setAttribute("dur", `${durationMs}ms`); animate.setAttribute("fill", "freeze"); - path.appendChild(animate); - const textPath = svg.ownerDocument.createElementNS(SVG_NS, "textPath"); - textPath.setAttribute("href", "#pathForText" + pathForTextIndex); - textPath.textContent = ele.textContent; - ele.textContent = " "; // HACK for Firebox as `null` does not work + ele.appendChild(animate); + findNode(svg, "defs")?.appendChild(path); - ele.appendChild(textPath); animatePointer( svg, ele, diff --git a/src/useLoadSvg.ts b/src/useLoadSvg.ts index fb42464..bcbd283 100644 --- a/src/useLoadSvg.ts +++ b/src/useLoadSvg.ts @@ -9,6 +9,7 @@ import { import type { BinaryFiles } from "@excalidraw/excalidraw/types/types"; import type { ExcalidrawElement, + ExcalidrawTextElement, NonDeletedExcalidrawElement, } from "@excalidraw/excalidraw/types/element/types"; @@ -71,6 +72,11 @@ export const useLoadSvg = () => { appState: data.appState, exportPadding: 30, }); + + // This is a patch up function to apply new fonts that are not part of Excalidraw package + // Remove this function once Excalidraw package is updated (v0.17.6 as of now) + applyNewFontsToSvg(svg, elements); + const result = animateSvg(svg, elements, options); console.log(svg); if (inSequence) { @@ -122,3 +128,97 @@ export const useLoadSvg = () => { return { loading, loadedSvgList, loadDataList }; }; + +// Change below are to apply new fonts that are not part of current version of Excalidraw package +// Remove them all below once Excalidraw is updated (v0.17.6 as of now) +// ================================================ +const DEFAULT_FONT = "Segoe UI Emoji"; +/** Up to date version of font family. It's brought from the latest version of Excalidraw repo */ +export const FONT_FAMILY = { + Virgil: 1, + Helvetica: 2, + Cascadia: 3, + // leave 4 unused as it was historically used for Assistant (which we don't use anymore) or custom font (Obsidian) + Excalifont: 5, + Nunito: 6, + "Lilita One": 7, + "Comic Shanns": 8, + "Liberation Sans": 9, +} as const; + +/** + * Recursively apply new fonts to all text elements in the given SVG. + * `exportToSvg()` is not compatible with new fonts due to a discrepancy between package and release excalidraw. + * This function patches up the fonts resulting in a default font family. + * + * issue link: https://github.com/dai-shi/excalidraw-animate/issues/55 + * */ +function applyNewFontsToSvg(svg: SVGSVGElement, elements: ExcalidrawElement[]) { + const textElements: ExcalidrawTextElement[] = elements.filter( + (element): element is ExcalidrawTextElement => + element.type === "text" && !!element.fontFamily + ) as ExcalidrawTextElement[]; + + /** index to keep track of block of text elements */ + let currentTextElementIndex = 0; + + // Since text element is represented in a group in given svg + // apply font family based on the group that contains the text elements + svg.querySelectorAll("g").forEach((svgGroup) => { + // It indicates the group is not for text - thus skip it + if (svgGroup.hasAttribute("stroke-linecap")) return; + + const fontFamily = textElements[currentTextElementIndex]?.fontFamily; + svgGroup.querySelectorAll("text").forEach((svgText) => { + convertFontFamily(svgText, fontFamily); + }); + + currentTextElementIndex += 1; + }); +} + +function convertFontFamily( + textElement: SVGTextElement, + fontFamilyNumber: number | undefined +) { + switch (fontFamilyNumber) { + case FONT_FAMILY.Virgil: + textElement.setAttribute("font-family", `Virgil, ${DEFAULT_FONT}`); + break; + + case FONT_FAMILY.Helvetica: + textElement.setAttribute("font-family", `Helvetica, ${DEFAULT_FONT}`); + break; + + case FONT_FAMILY.Cascadia: + textElement.setAttribute("font-family", `Cascadia, ${DEFAULT_FONT}`); + break; + + case FONT_FAMILY.Excalifont: + textElement.setAttribute("font-family", `Excalifont, ${DEFAULT_FONT}`); + break; + + case FONT_FAMILY.Nunito: + textElement.setAttribute("font-family", `Nunito, ${DEFAULT_FONT}`); + break; + + case FONT_FAMILY["Lilita One"]: + textElement.setAttribute("font-family", `Lilita One, ${DEFAULT_FONT}`); + break; + + case FONT_FAMILY["Comic Shanns"]: + textElement.setAttribute("font-family", `Comic Shanns, ${DEFAULT_FONT}`); + break; + + case FONT_FAMILY["Liberation Sans"]: + textElement.setAttribute( + "font-family", + `Liberation Sans, ${DEFAULT_FONT}` + ); + break; + + default: + textElement.setAttribute("font-family", DEFAULT_FONT); + break; + } +}