Files
galaxy/crates/editor/src/content/mermaid_diagram.rs
T

112 lines
3.4 KiB
Rust

use std::hash::{DefaultHasher, Hash, Hasher};
use std::sync::Arc;
use bytes::Bytes;
use galaxyui_core::assets::asset_cache::{
AssetCache, AssetSource, AssetState, AsyncAssetId, AsyncAssetType,
};
use galaxyui_core::image_cache::ImageType;
use galaxyui_core::units::{IntoPixels, Pixels};
use galaxyui_core::{AppContext, SingletonEntity};
use crate::render::layout::TextLayout;
use crate::render::model::{BlockSpacing, ImageBlockConfig};
const DEFAULT_MERMAID_HEIGHT_LINE_MULTIPLIER: f32 = 10.0;
const FAILED_MERMAID_HEIGHT_LINE_MULTIPLIER: f32 = 2.0;
struct MermaidDiagramAsset;
impl AsyncAssetType for MermaidDiagramAsset {}
pub fn mermaid_asset_source(source: &str) -> AssetSource {
let source = source.to_string();
let mut hasher = DefaultHasher::new();
source.hash(&mut hasher);
let id = format!("configured:{:x}", hasher.finish());
let fetch_source = source.clone();
AssetSource::Async {
id: AsyncAssetId::new::<MermaidDiagramAsset>(id),
fetch: Arc::new(move || {
let source = fetch_source.clone();
Box::pin(async move {
mermaid_to_svg::render_mermaid_to_svg(&source, None)
.map(|svg| Bytes::from(svg.into_bytes()))
.map_err(Into::into)
})
}),
}
}
pub fn mermaid_diagram_layout(
source: &str,
layout: &TextLayout,
spacing: BlockSpacing,
app: &AppContext,
) -> (AssetSource, ImageBlockConfig) {
let asset_source = mermaid_asset_source(source);
let config = mermaid_diagram_config(&asset_source, layout, spacing, app);
(asset_source, config)
}
fn mermaid_diagram_config(
asset_source: &AssetSource,
layout: &TextLayout,
spacing: BlockSpacing,
app: &AppContext,
) -> ImageBlockConfig {
let max_width = layout.max_width() - spacing.x_axis_offset();
let (width, height) = mermaid_diagram_size(asset_source, max_width, app).unwrap_or_else(|| {
let height = layout.rich_text_styles().base_line_height()
* mermaid_diagram_fallback_height_line_multiplier(asset_source, app).into_pixels();
(max_width, height)
});
ImageBlockConfig {
width,
height,
spacing,
}
}
fn mermaid_diagram_fallback_height_line_multiplier(
asset_source: &AssetSource,
app: &AppContext,
) -> f32 {
let asset_cache = AssetCache::as_ref(app);
match asset_cache.load_asset::<ImageType>(asset_source.clone()) {
AssetState::FailedToLoad(_) => FAILED_MERMAID_HEIGHT_LINE_MULTIPLIER,
AssetState::Loading { .. } | AssetState::Loaded { .. } | AssetState::Evicted => {
DEFAULT_MERMAID_HEIGHT_LINE_MULTIPLIER
}
}
}
fn mermaid_diagram_size(
asset_source: &AssetSource,
max_width: Pixels,
app: &AppContext,
) -> Option<(Pixels, Pixels)> {
let asset_cache = AssetCache::as_ref(app);
let AssetState::Loaded { data } = asset_cache.load_asset::<ImageType>(asset_source.clone())
else {
return None;
};
let ImageType::Svg { svg } = data.as_ref() else {
return None;
};
let intrinsic_size = svg.size();
let intrinsic_width = intrinsic_size.width();
let intrinsic_height = intrinsic_size.height();
if intrinsic_width <= 0. || intrinsic_height <= 0. {
return None;
}
let width = max_width;
let height = Pixels::new(width.as_f32() * intrinsic_height / intrinsic_width);
Some((width, height))
}
#[cfg(test)]
#[path = "mermaid_diagram_tests.rs"]
mod tests;