pub mod data_resolve; pub mod expr_eval; pub mod page_break; pub mod sizing; pub mod table_layout; pub mod text_measure; pub mod tree; #[cfg(target_arch = "wasm32")] pub mod wasm_api; pub mod barcode_gen; pub mod chart_layout; pub mod chart_render; pub mod font_meta; pub mod font_provider; #[cfg(not(target_arch = "wasm32"))] pub mod pdf_render; use dreport_core::models::{ChartType, Template}; use serde::{Deserialize, Serialize}; /// Layout hesaplama hata tipi #[derive(Debug)] pub enum LayoutError { Taffy(taffy::TaffyError), } impl std::fmt::Display for LayoutError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { LayoutError::Taffy(e) => write!(f, "Taffy layout hatası: {:?}", e), } } } impl std::error::Error for LayoutError {} impl From for LayoutError { fn from(e: taffy::TaffyError) -> Self { LayoutError::Taffy(e) } } // --- Layout sonuç tipleri --- #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LayoutResult { pub pages: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PageLayout { pub page_index: usize, pub width_mm: f64, pub height_mm: f64, pub elements: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ElementLayout { pub id: String, pub x_mm: f64, pub y_mm: f64, pub width_mm: f64, pub height_mm: f64, pub element_type: String, pub content: Option, pub style: ResolvedStyle, pub children: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type")] pub enum ResolvedContent { #[serde(rename = "text")] Text { value: String }, #[serde(rename = "image")] Image { src: String }, #[serde(rename = "line")] Line, #[serde(rename = "barcode")] Barcode { format: String, value: String }, #[serde(rename = "page_number")] PageNumber { current: usize, total: usize }, #[serde(rename = "shape")] Shape { #[serde(rename = "shapeType")] shape_type: String, }, #[serde(rename = "checkbox")] Checkbox { checked: bool }, #[serde(rename = "rich_text")] RichText { spans: Vec }, #[serde(rename = "table")] Table { headers: Vec, rows: Vec>, column_widths_mm: Vec, }, #[serde(rename = "chart")] Chart { svg: String, /// PDF render icin chart verisi (frontend bunu kullanmaz) #[serde(flatten)] chart_data: Box, }, } /// PDF renderer icin chart verisi — ResolvedContent::Chart icinde tasınır #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChartRenderData { pub chart_type: ChartType, pub categories: Vec, pub series: Vec, #[serde(default)] pub title_text: Option, #[serde(default)] pub title_font_size: Option, #[serde(default)] pub title_color: Option, #[serde(default)] pub colors: Vec, #[serde(default)] pub show_labels: bool, #[serde(default)] pub label_font_size: Option, #[serde(default)] pub show_grid: bool, #[serde(default)] pub grid_color: Option, #[serde(default)] pub bar_gap: Option, #[serde(default)] pub stacked: bool, #[serde(default)] pub inner_radius: Option, #[serde(default)] pub show_points: Option, #[serde(default)] pub line_width: Option, #[serde(default)] pub background_color: Option, // Label color #[serde(default)] pub label_color: Option, // Legend #[serde(default)] pub legend_show: bool, #[serde(default)] pub legend_position: Option, #[serde(default)] pub legend_font_size: Option, // Axis labels #[serde(default)] pub x_label: Option, #[serde(default)] pub y_label: Option, // Title align #[serde(default)] pub title_align: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChartSeriesData { pub name: String, pub values: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ResolvedRichSpan { pub text: String, pub font_size: Option, pub font_weight: Option, pub font_family: Option, pub color: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TableHeaderCell { pub text: String, pub align: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TableCell { pub text: String, pub align: String, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ResolvedStyle { // Text pub font_size: Option, pub font_weight: Option, pub font_style: Option, pub font_family: Option, pub color: Option, pub text_align: Option, // Line pub stroke_color: Option, pub stroke_width: Option, // Container pub background_color: Option, pub border_color: Option, pub border_width: Option, pub border_radius: Option, pub border_style: Option, // Table pub header_bg: Option, pub header_color: Option, pub zebra_odd: Option, pub zebra_even: Option, pub header_font_size: Option, // Image pub object_fit: Option, // Barcode pub barcode_color: Option, pub barcode_include_text: Option, } // --- From<&XStyle> for ResolvedStyle --- impl From<&dreport_core::models::TextStyle> for ResolvedStyle { fn from(s: &dreport_core::models::TextStyle) -> Self { Self { font_size: s.font_size, font_weight: s.font_weight.clone(), font_style: s.font_style.clone(), font_family: s.font_family.clone(), color: s.color.clone(), text_align: s.align.clone(), ..Default::default() } } } impl From<&dreport_core::models::ContainerStyle> for ResolvedStyle { fn from(s: &dreport_core::models::ContainerStyle) -> Self { Self { background_color: s.background_color.clone(), border_color: s.border_color.clone(), border_width: s.border_width, border_radius: s.border_radius, border_style: s.border_style.clone(), ..Default::default() } } } impl From<&dreport_core::models::LineStyle> for ResolvedStyle { fn from(s: &dreport_core::models::LineStyle) -> Self { Self { stroke_color: s.stroke_color.clone(), stroke_width: s.stroke_width, ..Default::default() } } } impl From<&dreport_core::models::ImageStyle> for ResolvedStyle { fn from(s: &dreport_core::models::ImageStyle) -> Self { Self { object_fit: s.object_fit.clone(), ..Default::default() } } } impl From<&dreport_core::models::BarcodeStyle> for ResolvedStyle { fn from(s: &dreport_core::models::BarcodeStyle) -> Self { Self { barcode_color: s.color.clone(), barcode_include_text: s.include_text, ..Default::default() } } } impl From<&dreport_core::models::CheckboxStyle> for ResolvedStyle { fn from(s: &dreport_core::models::CheckboxStyle) -> Self { Self { color: s.check_color.clone(), border_color: s.border_color.clone(), border_width: s.border_width, ..Default::default() } } } impl From<&data_resolve::ResolvedChartData> for ChartRenderData { fn from(cd: &data_resolve::ResolvedChartData) -> Self { let n_colors = cd.categories.len().max(cd.series.len()).max(1); let colors: Vec = (0..n_colors) .map(|i| { cd.style .colors .as_ref() .and_then(|c| c.get(i).cloned()) .unwrap_or_else(|| { chart_layout::DEFAULT_COLORS[i % chart_layout::DEFAULT_COLORS.len()] .to_string() }) }) .collect(); Self { chart_type: cd.chart_type.clone(), categories: cd.categories.clone(), series: cd .series .iter() .map(|s| ChartSeriesData { name: s.name.clone(), values: s.values.clone(), }) .collect(), title_text: cd.title.as_ref().map(|t| t.text.clone()), title_font_size: cd.title.as_ref().and_then(|t| t.font_size), title_color: cd.title.as_ref().and_then(|t| t.color.clone()), title_align: cd.title.as_ref().and_then(|t| t.align.clone()), colors, show_labels: cd.labels.as_ref().is_some_and(|l| l.show), label_font_size: cd.labels.as_ref().and_then(|l| l.font_size), label_color: cd.labels.as_ref().and_then(|l| l.color.clone()), show_grid: cd.axis.as_ref().and_then(|a| a.show_grid).unwrap_or(true), grid_color: cd.axis.as_ref().and_then(|a| a.grid_color.clone()), bar_gap: cd.style.bar_gap, stacked: matches!(cd.group_mode, Some(dreport_core::models::GroupMode::Stacked)), inner_radius: cd.style.inner_radius, show_points: cd.style.show_points, line_width: cd.style.line_width, background_color: cd.style.background_color.clone(), legend_show: cd.legend.as_ref().is_some_and(|l| l.show), legend_position: cd.legend.as_ref().and_then(|l| l.position.clone()), legend_font_size: cd.legend.as_ref().and_then(|l| l.font_size), x_label: cd.axis.as_ref().and_then(|a| a.x_label.clone()), y_label: cd.axis.as_ref().and_then(|a| a.y_label.clone()), } } } /// Ana layout hesaplama fonksiyonu. /// Template + data + font verileri alır, her element için pozisyon döner. pub fn compute_layout( template: &Template, data: &serde_json::Value, font_data: &[FontData], ) -> Result { let mut measurer = text_measure::TextMeasurer::new(font_data); let resolved = data_resolve::resolve_template(template, data); tree::compute(template, &resolved, &mut measurer) } /// Cache-aware layout hesaplama. /// Önceki çağrıdan kalan text measurement cache'ini alır, hesaplama sonrası /// güncellenen cache'i geri döner. WASM tarafında cross-call persist için kullanılır. pub fn compute_layout_cached( template: &Template, data: &serde_json::Value, font_data: &[FontData], text_cache: text_measure::TextMeasureCache, ) -> Result<(LayoutResult, text_measure::TextMeasureCache), LayoutError> { let mut measurer = text_measure::TextMeasurer::new_with_cache(font_data, text_cache); let resolved = data_resolve::resolve_template(template, data); let result = tree::compute(template, &resolved, &mut measurer)?; Ok((result, measurer.take_cache())) } /// Font verisi (ham TTF/OTF bytes + metadata) #[derive(Debug, Clone)] pub struct FontData { pub family: String, pub weight: u16, pub italic: bool, pub data: Vec, } impl FontData { /// Create FontData from raw bytes, parsing metadata from the font file. /// Returns None if font metadata cannot be parsed. pub fn from_bytes(data: Vec) -> Option { let meta = font_meta::parse_font_meta(&data)?; Some(Self { family: meta.family, weight: meta.weight, italic: meta.italic, data, }) } /// Create FontData with explicit metadata (when metadata is already known). pub fn new(family: String, weight: u16, italic: bool, data: Vec) -> Self { Self { family, weight, italic, data, } } pub fn is_bold(&self) -> bool { self.weight >= 700 } }