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;
+ }
+}