Migrate fonts to be stored as resources (#4165)

* Good start

* Impl

* allow responses.add(async { Message::NoOp });

* Fix

* Cache font blob in text node

* Refactor font handling to use Font struct direcly

* Fix fmt

* Embedd Font Resources by default

* Review

* Review

* Cache font info based on ResourceHash
This commit is contained in:
Timon
2026-06-03 09:21:51 +00:00
committed by Keavon Chambers
parent 7b9c480a8d
commit cd8ea1f554
46 changed files with 838 additions and 703 deletions

View File

@@ -41,21 +41,21 @@ export function createFontsManager(subscriptions: SubscriptionsRouter, editor: E
}
});
subscriptions.subscribeFrontendMessage("TriggerFontDataLoad", async (data) => {
const { fontFamily, fontStyle } = data.font;
// Generic URL resolver
// TODO(keavon): This is currently only used for fonts, but it could be used for other resources and thus should be moved to a more sesible location
subscriptions.subscribeFrontendMessage("TriggerResolveResource", async (data) => {
try {
if (!data.url) throw new Error("No URL provided for font data load");
if (!data.url) throw new Error("No URL provided for resource resolution");
const response = await fetch(data.url, abortController ? { signal: abortController.signal } : undefined);
if (!response.ok) throw new Error(`Font data request failed with status ${response.status}`);
if (!response.ok) throw new Error(`Resource request failed with status ${response.status}`);
const buffer = await response.arrayBuffer();
const bytes = new Uint8Array(buffer);
editor.onFontLoad(fontFamily, fontStyle, bytes);
editor.onResourceResolved(data.documentId, data.resourceId, bytes);
} catch (error) {
if (error instanceof DOMException && error.name === "AbortError") return;
// eslint-disable-next-line no-console
console.error("Failed to load font:", error);
console.error("Failed to resolve resource:", error);
}
});
}
@@ -66,7 +66,7 @@ export function destroyFontsManager() {
abortController?.abort();
subscriptions.unsubscribeFrontendMessage("TriggerFontCatalogLoad");
subscriptions.unsubscribeFrontendMessage("TriggerFontDataLoad");
subscriptions.unsubscribeFrontendMessage("TriggerResolveResource");
}
// Self-accepting HMR: tear down the old instance and re-create with the new module's code

View File

@@ -20,7 +20,8 @@ use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseStat
use editor::messages::layout::utility_types::layout_widget::LayoutTarget;
use editor::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use editor::messages::portfolio::document::utility_types::network_interface::ImportOrExport;
use editor::messages::portfolio::utility_types::{DockingSplitDirection, FontCatalog, FontCatalogFamily, PanelGroupId, PanelType};
use editor::messages::portfolio::fonts::utility_types::{FontCatalog, FontCatalogFamily};
use editor::messages::portfolio::utility_types::{DockingSplitDirection, PanelGroupId, PanelType};
use editor::messages::prelude::*;
use editor::messages::tool::tool_messages::tool_prelude::WidgetId;
use graph_craft::document::NodeId;
@@ -650,14 +651,19 @@ impl EditorWrapper {
/// The font catalog has been loaded
#[wasm_bindgen(js_name = onFontCatalogLoad)]
pub fn on_font_catalog_load(&self, catalog: Vec<FontCatalogFamily>) {
self.dispatch(PortfolioMessage::FontCatalogLoaded { catalog: FontCatalog(catalog) });
self.dispatch(FontsMessage::CatalogLoaded { catalog: FontCatalog::from(catalog) });
}
/// A font has been downloaded
#[wasm_bindgen(js_name = onFontLoad)]
pub fn on_font_load(&self, font_family: String, font_style: String, data: Vec<u8>) -> Result<(), JsValue> {
let message = PortfolioMessage::FontLoaded { font_family, font_style, data };
self.dispatch(message);
/// A requested resource has been resolved by the frontend.
#[wasm_bindgen(js_name = onResourceResolved)]
pub fn on_resource_resolved(&self, document_id: u64, resource_id: u64, data: Vec<u8>) -> Result<(), JsValue> {
self.dispatch(PortfolioMessage::DocumentPassMessage {
document_id: DocumentId(document_id),
message: DocumentMessage::Resource(ResourceMessage::Resolved {
resource_id: resource_id.into(),
data: std::sync::Arc::from(data),
}),
});
Ok(())
}