浏览代码

fix: font not rendered correctly on init (#8002)

David Luzar 1 年之前
父节点
当前提交
273ba803d9

+ 15 - 17
packages/excalidraw/components/App.tsx

@@ -714,10 +714,7 @@ class App extends React.Component<AppProps, AppState> {
       id: this.id,
     };
 
-    this.fonts = new Fonts({
-      scene: this.scene,
-      onSceneUpdated: this.onSceneUpdated,
-    });
+    this.fonts = new Fonts({ scene: this.scene });
     this.history = new History();
 
     this.actionManager.registerAll(actions);
@@ -940,7 +937,7 @@ class App extends React.Component<AppProps, AppState> {
     });
 
     if (updated) {
-      this.scene.informMutation();
+      this.scene.triggerUpdate();
     }
 
     // GC
@@ -1452,10 +1449,10 @@ class App extends React.Component<AppProps, AppState> {
     const selectedElements = this.scene.getSelectedElements(this.state);
     const { renderTopRightUI, renderCustomStats } = this.props;
 
-    const versionNonce = this.scene.getVersionNonce();
+    const sceneNonce = this.scene.getSceneNonce();
     const { elementsMap, visibleElements } =
       this.renderer.getRenderableElements({
-        versionNonce,
+        sceneNonce,
         zoom: this.state.zoom,
         offsetLeft: this.state.offsetLeft,
         offsetTop: this.state.offsetTop,
@@ -1673,7 +1670,7 @@ class App extends React.Component<AppProps, AppState> {
                           elementsMap={elementsMap}
                           allElementsMap={allElementsMap}
                           visibleElements={visibleElements}
-                          versionNonce={versionNonce}
+                          sceneNonce={sceneNonce}
                           selectionNonce={
                             this.state.selectionElement?.versionNonce
                           }
@@ -1695,7 +1692,7 @@ class App extends React.Component<AppProps, AppState> {
                           elementsMap={elementsMap}
                           visibleElements={visibleElements}
                           selectedElements={selectedElements}
-                          versionNonce={versionNonce}
+                          sceneNonce={sceneNonce}
                           selectionNonce={
                             this.state.selectionElement?.versionNonce
                           }
@@ -1819,7 +1816,7 @@ class App extends React.Component<AppProps, AppState> {
       );
     }
     this.magicGenerations.set(frameElement.id, data);
-    this.onSceneUpdated();
+    this.triggerRender();
   };
 
   private getTextFromElements(elements: readonly ExcalidrawElement[]) {
@@ -2444,7 +2441,7 @@ class App extends React.Component<AppProps, AppState> {
       this.history.record(increment.elementsChange, increment.appStateChange);
     });
 
-    this.scene.addCallback(this.onSceneUpdated);
+    this.scene.onUpdate(this.triggerRender);
     this.addEventListeners();
 
     if (this.props.autoFocus && this.excalidrawContainerRef.current) {
@@ -2489,6 +2486,7 @@ class App extends React.Component<AppProps, AppState> {
   public componentWillUnmount() {
     this.renderer.destroy();
     this.scene = new Scene();
+    this.fonts = new Fonts({ scene: this.scene });
     this.renderer = new Renderer(this.scene);
     this.files = {};
     this.imageCache.clear();
@@ -3670,7 +3668,7 @@ class App extends React.Component<AppProps, AppState> {
           ShapeCache.delete(element);
         }
       });
-      this.scene.informMutation();
+      this.scene.triggerUpdate();
 
       this.addNewImagesToImageCache();
     },
@@ -3730,7 +3728,7 @@ class App extends React.Component<AppProps, AppState> {
     },
   );
 
-  private onSceneUpdated = () => {
+  private triggerRender = () => {
     this.setState({});
   };
 
@@ -5577,7 +5575,7 @@ class App extends React.Component<AppProps, AppState> {
       }
 
       this.elementsPendingErasure = new Set(this.elementsPendingErasure);
-      this.onSceneUpdated();
+      this.triggerRender();
     }
   };
 
@@ -8069,7 +8067,7 @@ class App extends React.Component<AppProps, AppState> {
                     this.scene.getNonDeletedElementsMap(),
                   );
 
-                  this.scene.informMutation();
+                  this.scene.triggerUpdate();
                 }
               }
             }
@@ -8564,7 +8562,7 @@ class App extends React.Component<AppProps, AppState> {
 
   private restoreReadyToEraseElements = () => {
     this.elementsPendingErasure = new Set();
-    this.onSceneUpdated();
+    this.triggerRender();
   };
 
   private eraseElements = () => {
@@ -8978,7 +8976,7 @@ class App extends React.Component<AppProps, AppState> {
         files,
       );
       if (updatedFiles.size) {
-        this.scene.informMutation();
+        this.scene.triggerUpdate();
       }
     }
   };

+ 1 - 1
packages/excalidraw/components/LayerUI.tsx

@@ -444,7 +444,7 @@ const LayerUI = ({
                 );
                 ShapeCache.delete(element);
               }
-              Scene.getScene(selectedElements[0])?.informMutation();
+              Scene.getScene(selectedElements[0])?.triggerUpdate();
             } else if (colorPickerType === "elementBackground") {
               setAppState({
                 currentItemBackgroundColor: color,

+ 3 - 3
packages/excalidraw/components/canvases/InteractiveCanvas.tsx

@@ -19,7 +19,7 @@ type InteractiveCanvasProps = {
   elementsMap: RenderableElementsMap;
   visibleElements: readonly NonDeletedExcalidrawElement[];
   selectedElements: readonly NonDeletedExcalidrawElement[];
-  versionNonce: number | undefined;
+  sceneNonce: number | undefined;
   selectionNonce: number | undefined;
   scale: number;
   appState: InteractiveCanvasAppState;
@@ -206,10 +206,10 @@ const areEqual = (
   // This could be further optimised if needed, as we don't have to render interactive canvas on each scene mutation
   if (
     prevProps.selectionNonce !== nextProps.selectionNonce ||
-    prevProps.versionNonce !== nextProps.versionNonce ||
+    prevProps.sceneNonce !== nextProps.sceneNonce ||
     prevProps.scale !== nextProps.scale ||
     // we need to memoize on elementsMap because they may have renewed
-    // even if versionNonce didn't change (e.g. we filter elements out based
+    // even if sceneNonce didn't change (e.g. we filter elements out based
     // on appState)
     prevProps.elementsMap !== nextProps.elementsMap ||
     prevProps.visibleElements !== nextProps.visibleElements ||

+ 3 - 3
packages/excalidraw/components/canvases/StaticCanvas.tsx

@@ -19,7 +19,7 @@ type StaticCanvasProps = {
   elementsMap: RenderableElementsMap;
   allElementsMap: NonDeletedSceneElementsMap;
   visibleElements: readonly NonDeletedExcalidrawElement[];
-  versionNonce: number | undefined;
+  sceneNonce: number | undefined;
   selectionNonce: number | undefined;
   scale: number;
   appState: StaticCanvasAppState;
@@ -112,10 +112,10 @@ const areEqual = (
   nextProps: StaticCanvasProps,
 ) => {
   if (
-    prevProps.versionNonce !== nextProps.versionNonce ||
+    prevProps.sceneNonce !== nextProps.sceneNonce ||
     prevProps.scale !== nextProps.scale ||
     // we need to memoize on elementsMap because they may have renewed
-    // even if versionNonce didn't change (e.g. we filter elements out based
+    // even if sceneNonce didn't change (e.g. we filter elements out based
     // on appState)
     prevProps.elementsMap !== nextProps.elementsMap ||
     prevProps.visibleElements !== nextProps.visibleElements

+ 4 - 2
packages/excalidraw/element/mutateElement.ts

@@ -98,7 +98,7 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
   element.updated = getUpdatedTimestamp();
 
   if (informMutation) {
-    Scene.getScene(element)?.informMutation();
+    Scene.getScene(element)?.triggerUpdate();
   }
 
   return element;
@@ -107,6 +107,8 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
 export const newElementWith = <TElement extends ExcalidrawElement>(
   element: TElement,
   updates: ElementUpdate<TElement>,
+  /** pass `true` to always regenerate */
+  force = false,
 ): TElement => {
   let didChange = false;
   for (const key in updates) {
@@ -123,7 +125,7 @@ export const newElementWith = <TElement extends ExcalidrawElement>(
     }
   }
 
-  if (!didChange) {
+  if (!didChange && !force) {
     return element;
   }
 

+ 2 - 2
packages/excalidraw/element/resizeElements.ts

@@ -876,7 +876,7 @@ export const resizeMultipleElements = (
     }
   }
 
-  Scene.getScene(elementsAndUpdates[0].element)?.informMutation();
+  Scene.getScene(elementsAndUpdates[0].element)?.triggerUpdate();
 };
 
 const rotateMultipleElements = (
@@ -938,7 +938,7 @@ const rotateMultipleElements = (
       }
     });
 
-  Scene.getScene(elements[0])?.informMutation();
+  Scene.getScene(elements[0])?.triggerUpdate();
 };
 
 export const getResizeOffsetXY = (

+ 1 - 1
packages/excalidraw/element/textWysiwyg.tsx

@@ -644,7 +644,7 @@ export const textWysiwyg = ({
   };
 
   // handle updates of textElement properties of editing element
-  const unbindUpdate = Scene.getScene(element)!.addCallback(() => {
+  const unbindUpdate = Scene.getScene(element)!.onUpdate(() => {
     updateWysiwygStyle();
     const isColorPickerActive = !!document.activeElement?.closest(
       ".color-picker-content",

+ 6 - 22
packages/excalidraw/scene/Fonts.ts

@@ -1,7 +1,5 @@
-import { isTextElement, refreshTextDimensions } from "../element";
+import { isTextElement } from "../element";
 import { newElementWith } from "../element/mutateElement";
-import { getContainerElement } from "../element/textElement";
-import { isBoundToContainer } from "../element/typeChecks";
 import type {
   ExcalidrawElement,
   ExcalidrawTextElement,
@@ -12,17 +10,9 @@ import { ShapeCache } from "./ShapeCache";
 
 export class Fonts {
   private scene: Scene;
-  private onSceneUpdated: () => void;
 
-  constructor({
-    scene,
-    onSceneUpdated,
-  }: {
-    scene: Scene;
-    onSceneUpdated: () => void;
-  }) {
+  constructor({ scene }: { scene: Scene }) {
     this.scene = scene;
-    this.onSceneUpdated = onSceneUpdated;
   }
 
   // it's ok to track fonts across multiple instances only once, so let's use
@@ -57,22 +47,16 @@ export class Fonts {
     let didUpdate = false;
 
     this.scene.mapElements((element) => {
-      if (isTextElement(element) && !isBoundToContainer(element)) {
-        ShapeCache.delete(element);
+      if (isTextElement(element)) {
         didUpdate = true;
-        return newElementWith(element, {
-          ...refreshTextDimensions(
-            element,
-            getContainerElement(element, this.scene.getNonDeletedElementsMap()),
-            this.scene.getNonDeletedElementsMap(),
-          ),
-        });
+        ShapeCache.delete(element);
+        return newElementWith(element, {}, true);
       }
       return element;
     });
 
     if (didUpdate) {
-      this.onSceneUpdated();
+      this.scene.triggerUpdate();
     }
   };
 

+ 3 - 4
packages/excalidraw/scene/Renderer.ts

@@ -107,9 +107,8 @@ export class Renderer {
         width,
         editingElement,
         pendingImageElementId,
-        // unused but serves we cache on it to invalidate elements if they
-        // get mutated
-        versionNonce: _versionNonce,
+        // cache-invalidation nonce
+        sceneNonce: _sceneNonce,
       }: {
         zoom: AppState["zoom"];
         offsetLeft: AppState["offsetLeft"];
@@ -120,7 +119,7 @@ export class Renderer {
         width: AppState["width"];
         editingElement: AppState["editingElement"];
         pendingImageElementId: AppState["pendingImageElementId"];
-        versionNonce: ReturnType<InstanceType<typeof Scene>["getVersionNonce"]>;
+        sceneNonce: ReturnType<InstanceType<typeof Scene>["getSceneNonce"]>;
       }) => {
         const elements = this.scene.getNonDeletedElements();
 

+ 15 - 9
packages/excalidraw/scene/Scene.ts

@@ -138,7 +138,17 @@ class Scene {
     elements: null,
     cache: new Map(),
   };
-  private versionNonce: number | undefined;
+  /**
+   * Random integer regenerated each scene update.
+   *
+   * Does not relate to elements versions, it's only a renderer
+   * cache-invalidation nonce at the moment.
+   */
+  private sceneNonce: number | undefined;
+
+  getSceneNonce() {
+    return this.sceneNonce;
+  }
 
   getNonDeletedElementsMap() {
     return this.nonDeletedElementsMap;
@@ -214,10 +224,6 @@ class Scene {
     return (this.elementsMap.get(id) as T | undefined) || null;
   }
 
-  getVersionNonce() {
-    return this.versionNonce;
-  }
-
   getNonDeletedElement(
     id: ExcalidrawElement["id"],
   ): NonDeleted<ExcalidrawElement> | null {
@@ -286,18 +292,18 @@ class Scene {
     this.frames = nextFrameLikes;
     this.nonDeletedFramesLikes = getNonDeletedElements(this.frames).elements;
 
-    this.informMutation();
+    this.triggerUpdate();
   }
 
-  informMutation() {
-    this.versionNonce = randomInteger();
+  triggerUpdate() {
+    this.sceneNonce = randomInteger();
 
     for (const callback of Array.from(this.callbacks)) {
       callback();
     }
   }
 
-  addCallback(cb: SceneStateCallback): SceneStateCallbackRemover {
+  onUpdate(cb: SceneStateCallback): SceneStateCallbackRemover {
     if (this.callbacks.has(cb)) {
       throw new Error();
     }