Files
wordcraft/crates/doc/src/graphic.rs
T
6a22d7257b Insert › SmartArt: create and edit list, process, cycle, hierarchy, pyramid, radial, matrix and venn graphics (#497) (#503)
- Model (doc::smart_art): layout, items (text + level), colour variant; capped at 100 items,
  255 characters, 8 levels.
- Layout algorithms of our own (docx::smart_art) place rounded rects, circles, bands, arrows and
  connectors; WordCraft draws the graphic from the same dsp:drawing it saves.
- DOCX: data (dgm:dataModel points and connections), our own layout/quick-style/colour
  definitions under the built-in identifiers, the drawing part, relationships and content types.
  A diagram reads back editable only when its data part is exactly what WordCraft writes.
- Commands: insert.smartArt, smartArt.items/addShape/promote/demote/layout/colors/reset/textPane,
  one undo step each; picture.size relays out a SmartArt graphic.
- UI: layout picker with previews drawn in code, SmartArt Design tab, Text Pane; strings in all
  9 catalogs.
- Diagram reader draws `line` presets top-left to bottom-right (was the other diagonal).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 20:09:32 -04:00

137 lines
4.8 KiB
Rust

//! Charts and SmartArt diagrams: drawings kept as neutral items ready to draw. Charts and SmartArt
//! WordCraft can edit also keep their model ([`crate::chart::ChartSpec`],
//! [`crate::smart_art::SmartArtSpec`]); the rest are shown, not edited.
use serde::{Deserialize, Serialize};
use crate::para::ShapeKind;
use crate::props::Rgb;
/// A chart or a SmartArt diagram, as items ready to draw (and, for an editable chart or SmartArt
/// graphic, its model).
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Graphic {
pub kind: GraphicKind,
/// What to draw, in points from the object's top-left corner.
#[serde(default)]
pub items: Vec<GraphicItem>,
/// The size (points) the items were built for. Drawn scaled to the object's rectangle when
/// that differs (layout shrinks oversized inline objects); 0 means the rectangle's own size.
#[serde(default)]
pub w: f32,
#[serde(default)]
pub h: f32,
/// Where the graphic was read from, so saving writes the chart or diagram itself back (not
/// only these items). `None` for graphics made some other way.
#[serde(skip)]
pub source: Option<std::sync::Arc<Embedded>>,
/// A SmartArt graphic WordCraft can edit (one it made, or one read back exactly as it wrote
/// it): saving writes the diagram parts from it, and the items are drawn from it. See
/// [`crate::smart_art`].
#[serde(default, skip_serializing_if = "Option::is_none")]
pub smart_art: Option<std::sync::Arc<crate::smart_art::SmartArtSpec>>,
/// A chart WordCraft can edit (one it made, or one read back that holds nothing more): saving
/// writes the chart part from it, and the items are drawn from it. See [`crate::chart`].
#[serde(default, skip_serializing_if = "Option::is_none")]
pub chart: Option<std::sync::Arc<crate::chart::ChartSpec>>,
}
/// What an object read from a file needs to be written back as it was: its own markup in the
/// source format and the package parts that markup refers to. Never edited; WordCraft only moves
/// and resizes the object around it. The parts themselves (bytes, content type and their own
/// relationships) are kept in [`crate::Document::passthrough`] under their part names.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Embedded {
/// The object's markup as a standalone XML element with its namespace declarations (DOCX:
/// the `a:graphic` of a chart or SmartArt diagram, the `w:object` of an OLE object).
pub xml: String,
/// The relationships it uses, by the ids in `xml` (or in its parts, for a SmartArt drawing).
pub refs: Vec<EmbeddedRef>,
}
/// One relationship an [`Embedded`] object uses.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct EmbeddedRef {
/// The relationship id the markup uses.
pub id: String,
/// Relationship type (a URI).
pub kind: String,
/// The part name (a `Document::passthrough` key), or the URL of an external target.
pub target: String,
pub external: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum GraphicKind {
#[default]
Chart,
Diagram,
}
impl GraphicKind {
/// What the graphic is, in words: the alt text of one that has none of its own.
pub fn noun(self) -> &'static str {
match self {
GraphicKind::Chart => "chart",
GraphicKind::Diagram => "diagram",
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum GraphicItem {
/// A preset shape filling `rect` ([x, y, w, h]).
Shape {
rect: [f32; 4],
kind: ShapeKind,
fill: Option<Rgb>,
stroke: Option<Rgb>,
#[serde(default)]
stroke_width: f32,
},
/// A path: polygons, pie slices, line series.
Path {
segs: Vec<PathSeg>,
fill: Option<Rgb>,
stroke: Option<Rgb>,
#[serde(default)]
stroke_width: f32,
},
/// One line of text in `rect`, aligned horizontally by `align`, centred vertically.
Text {
rect: [f32; 4],
text: String,
size: f32,
color: Rgb,
#[serde(default)]
bold: bool,
#[serde(default)]
align: TextAlign,
#[serde(default)]
font: Option<String>,
},
/// A picture (`Document::media` key) stretched into `rect`.
Image { rect: [f32; 4], media: String },
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum TextAlign {
Left,
#[default]
Center,
Right,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum PathSeg {
Move(f32, f32),
Line(f32, f32),
Cubic(f32, f32, f32, f32, f32, f32),
Close,
}