112 lines
3.4 KiB
Rust
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;
|