mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
Merge PR #613 from NikitkaRa: SmartArt: node pictures from the data model, Brick Scene camera, four more layouts
This commit is contained in:
@@ -23,7 +23,7 @@ pub const MAX_LEVEL: u8 = 8;
|
||||
const MAX_SHAPES: usize = 40;
|
||||
|
||||
/// DeckCraft's layouts, with the names PowerPoint's gallery gives them.
|
||||
pub const LAYOUTS: [(&str, &str); 18] = [
|
||||
pub const LAYOUTS: [(&str, &str); 22] = [
|
||||
("list", "Vertical Bullet List"),
|
||||
("blockList", "Basic Block List"),
|
||||
("hBulletList", "Horizontal Bullet List"),
|
||||
@@ -42,6 +42,10 @@ pub const LAYOUTS: [(&str, &str); 18] = [
|
||||
("pictureList", "Vertical Picture List"),
|
||||
("pictureCaption", "Picture Caption List"),
|
||||
("pictureProcess", "Picture Accent Process"),
|
||||
("pictureHList", "Horizontal Picture List"),
|
||||
("timeline", "Basic Timeline"),
|
||||
("venn", "Basic Venn"),
|
||||
("target", "Basic Target"),
|
||||
];
|
||||
/// SmartArt Styles.
|
||||
pub const STYLES: [(&str, &str); 9] = [
|
||||
@@ -125,7 +129,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Change Layout",
|
||||
["SmartArt Design", "Layouts"],
|
||||
None,
|
||||
"{id?, kind: list|blockList|hBulletList|process|chevron|verticalProcess|stepUp|cycle|blockCycle|radial|hierarchy|pyramid|invertedPyramid|matrix|titledMatrix|pictureList|pictureCaption|pictureProcess}",
|
||||
"{id?, kind: list|blockList|hBulletList|process|chevron|verticalProcess|stepUp|cycle|blockCycle|radial|hierarchy|pyramid|invertedPyramid|matrix|titledMatrix|pictureList|pictureCaption|pictureProcess|pictureHList|timeline|venn|target}",
|
||||
has_selection,
|
||||
change_layout
|
||||
),
|
||||
@@ -323,14 +327,17 @@ fn shade(d: &Diagram, i: usize) -> Option<ColorTransform> {
|
||||
|
||||
/// The 3-D XML (`scene3d` + `sp3d`) for a 3-D SmartArt Style: a top bevel (`w`, `h` in points)
|
||||
/// lit by the three-point rig.
|
||||
fn three_d(bevel: &str, w: f64, h: f64) -> String {
|
||||
fn three_d(bevel: &str, w: f64, h: f64, camera: &str) -> String {
|
||||
format!(
|
||||
"<a:scene3d><a:camera prst=\"orthographicFront\"/><a:lightRig rig=\"threePt\" dir=\"t\"/></a:scene3d><a:sp3d prstMaterial=\"matte\"><a:bevelT w=\"{}\" h=\"{}\" prst=\"{bevel}\"/></a:sp3d>",
|
||||
"<a:scene3d><a:camera prst=\"{camera}\"/><a:lightRig rig=\"threePt\" dir=\"t\"/></a:scene3d><a:sp3d prstMaterial=\"matte\"><a:bevelT w=\"{}\" h=\"{}\" prst=\"{bevel}\"/></a:sp3d>",
|
||||
(w * 12700.0).round() as i64,
|
||||
(h * 12700.0).round() as i64
|
||||
)
|
||||
}
|
||||
|
||||
/// Brick Scene's camera: the diagram's shapes seen from above and to the side.
|
||||
pub const BRICK_CAMERA: &str = "isometricOffAxis2Top";
|
||||
|
||||
/// Whether `style` is one of the 3-D SmartArt Styles.
|
||||
pub fn is_3d(style: &str) -> bool {
|
||||
matches!(style, "polished" | "inset" | "cartoon" | "powder" | "brick")
|
||||
@@ -374,7 +381,7 @@ fn paint(d: &Diagram, sh: &mut Shape, i: usize, light: bool) {
|
||||
sh.effects = Some(Effects {
|
||||
outer_shadow: (d.style != "inset").then(|| shadow(6.0, 4.0)),
|
||||
bevel: Some(bevel.into()),
|
||||
raw3d: Some(three_d(bevel, bw, bw * 0.6)),
|
||||
raw3d: Some(three_d(bevel, bw, bw * 0.6, if d.style == "brick" { BRICK_CAMERA } else { "orthographicFront" })),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
@@ -778,6 +785,105 @@ fn layout_raw(d: &Diagram, area: Xfrm) -> Vec<Item> {
|
||||
}
|
||||
}
|
||||
}
|
||||
"pictureHList" => {
|
||||
// Horizontal Picture List: a column per node, a big picture over its text.
|
||||
let w = area.w / nf;
|
||||
for (i, g) in gs.iter().enumerate() {
|
||||
let x = area.x + i as f64 * w + w * 0.04;
|
||||
let bw = w * 0.92;
|
||||
let ph = area.h * 0.55;
|
||||
out.push((picture_shape(d, "rect", Xfrm::new(x, area.y, bw, ph), i, g[0]), vec![]));
|
||||
let mut sh = text_shape("rect", Xfrm::new(x, area.y + ph, bw, area.h - ph), &nodes(g), true);
|
||||
if let Some(b) = &mut sh.text {
|
||||
b.body.anchor = Some(deckcraft_model::text::Anchor::Top);
|
||||
}
|
||||
paint(d, &mut sh, i, false);
|
||||
out.push((sh, g.clone()));
|
||||
}
|
||||
}
|
||||
"timeline" => {
|
||||
// Basic Timeline: an arrow across the middle, a dot per node on it, the text above and
|
||||
// below in turn.
|
||||
let mid = area.y + area.h / 2.0;
|
||||
let bar = (area.h * 0.08).max(6.0);
|
||||
let mut sh = deco("rightArrow", Xfrm::new(area.x, mid - bar, area.w, bar * 2.0), 0.0);
|
||||
paint(d, &mut sh, 0, true);
|
||||
out.push((sh, vec![]));
|
||||
let w = area.w * 0.92 / nf;
|
||||
for (i, g) in gs.iter().enumerate() {
|
||||
let cx = area.x + w * (i as f64 + 0.5);
|
||||
let dot = bar * 1.4;
|
||||
let mut dsh = deco("ellipse", Xfrm::new(cx - dot / 2.0, mid - dot / 2.0, dot, dot), 0.0);
|
||||
paint(d, &mut dsh, i, false);
|
||||
out.push((dsh, vec![]));
|
||||
let th = area.h / 2.0 - bar * 1.6;
|
||||
let y = if i % 2 == 0 { area.y } else { mid + bar * 1.6 };
|
||||
let mut t = text_shape("rect", Xfrm::new(cx - w * 0.48, y, w * 0.96, th.max(1.0)), &nodes(g), true);
|
||||
if let Some(b) = &mut t.text {
|
||||
b.body.anchor = Some(if i % 2 == 0 { deckcraft_model::text::Anchor::Bottom } else { deckcraft_model::text::Anchor::Top });
|
||||
}
|
||||
paint(d, &mut t, i, true);
|
||||
t.fill = Some(Fill::None);
|
||||
t.line = Some(Line { fill: Some(Fill::None), ..Default::default() });
|
||||
out.push((t, g.clone()));
|
||||
}
|
||||
}
|
||||
"venn" => {
|
||||
// Basic Venn: see-through circles overlapping in a ring (two or three in a row/triangle).
|
||||
let s = area.h.min(area.w);
|
||||
let (cx, cy) = (area.x + area.w / 2.0, area.y + area.h / 2.0);
|
||||
let dc = if n == 1 { s } else { s * 0.62 };
|
||||
let r = if n == 1 { 0.0 } else { (s - dc) / 2.0 };
|
||||
for (i, g) in gs.iter().enumerate() {
|
||||
let a = (i as f64 / nf * 360.0 + if n == 2 { 180.0 } else { -90.0 }).to_radians();
|
||||
let mut sh = text_shape("ellipse", Xfrm::new(cx + r * a.cos() - dc / 2.0, cy + r * a.sin() - dc / 2.0, dc, dc), &nodes(g), true);
|
||||
paint(d, &mut sh, i, false);
|
||||
if let Some(Fill::Solid { color: c }) = &mut sh.fill {
|
||||
*c = c.clone().with(ColorTransform::Alpha(50_000));
|
||||
}
|
||||
if let Some(b) = &mut sh.text {
|
||||
// Text towards the circle's outer side, clear of the overlap.
|
||||
let p = dc * 0.18;
|
||||
let (dx, dy) = (a.cos() * p, a.sin() * p);
|
||||
b.body.inset_l = Some((7.2 + dx).max(2.0));
|
||||
b.body.inset_r = Some((7.2 - dx).max(2.0));
|
||||
b.body.inset_t = Some((3.6 + dy).max(2.0));
|
||||
b.body.inset_b = Some((3.6 - dy).max(2.0));
|
||||
}
|
||||
out.push((sh, g.clone()));
|
||||
}
|
||||
}
|
||||
"target" => {
|
||||
// Basic Target: nested rings, the outermost first, each ring's text in a box beside it
|
||||
// with a line from the ring.
|
||||
let s = area.h.min(area.w * 0.5);
|
||||
let (cx, cy) = (area.x + s / 2.0, area.y + area.h / 2.0);
|
||||
let band = s / 2.0 / nf;
|
||||
for (i, _) in gs.iter().enumerate() {
|
||||
let dc = s - 2.0 * band * i as f64;
|
||||
let mut sh = deco("ellipse", Xfrm::new(cx - dc / 2.0, cy - dc / 2.0, dc, dc), 0.0);
|
||||
paint(d, &mut sh, i, false);
|
||||
out.push((sh, vec![]));
|
||||
}
|
||||
let tx = area.x + s + area.w * 0.06;
|
||||
let rh = area.h / nf;
|
||||
for (i, g) in gs.iter().enumerate() {
|
||||
let ty = area.y + rh * (i as f64 + 0.5);
|
||||
// The point on ring i's band level with the text, else its top.
|
||||
let ry = cy - s / 2.0 + band * (i as f64 + 0.5);
|
||||
out.push((line_shape((cx, ry.min(ty)), (tx, ty)), vec![]));
|
||||
let mut t = text_shape("rect", Xfrm::new(tx, ty - rh * 0.45, (area.x + area.w - tx).max(1.0), rh * 0.9), &nodes(g), true);
|
||||
paint(d, &mut t, i, true);
|
||||
t.fill = Some(Fill::None);
|
||||
t.line = Some(Line { fill: Some(Fill::None), ..Default::default() });
|
||||
if let Some(b) = &mut t.text {
|
||||
for p in &mut b.paragraphs {
|
||||
p.props.align = Some(Align::Left);
|
||||
}
|
||||
}
|
||||
out.push((t, g.clone()));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
out
|
||||
|
||||
@@ -1796,7 +1796,12 @@ fn smartart_pictures_3d_styles_and_colour_variants() {
|
||||
let bytes = deckcraft_pptx::export(s.doc().unwrap().doc.as_ref()).unwrap();
|
||||
let back = deckcraft_pptx::import(&bytes).unwrap();
|
||||
assert!(back.validate().is_empty());
|
||||
assert_eq!(back.slides[1].shapes.iter().find_map(|x| x.diagram.as_ref()).map(|d| d.kind.clone()).as_deref(), Some("pictureCaption"));
|
||||
let bd = back.slides[1].shapes.iter().find_map(|x| x.diagram.as_ref()).unwrap();
|
||||
assert_eq!(bd.kind, "pictureCaption");
|
||||
// The node's picture comes back from the data model's blip, as its media.
|
||||
let m = bd.nodes[0].picture.expect("node picture read back");
|
||||
assert!(back.media(m).is_some_and(|x| !x.data.is_empty()));
|
||||
assert!(bd.nodes.iter().skip(1).all(|n| n.picture.is_none()));
|
||||
s.execute("smartArt.picture", &json!({"index": 0, "reset": true})).unwrap();
|
||||
assert!(pics(&s).iter().all(|p| !matches!(p.fill, Some(deckcraft_model::Fill::Picture(_)))));
|
||||
|
||||
|
||||
+84
-12
@@ -111,7 +111,11 @@ pub fn layout_id(kind: &str) -> &'static str {
|
||||
"titledMatrix" => "urn:microsoft.com/office/officeart/2005/8/layout/matrix1",
|
||||
"pictureList" => "urn:microsoft.com/office/officeart/2005/8/layout/vList4",
|
||||
"pictureCaption" => "urn:microsoft.com/office/officeart/2005/8/layout/pList1",
|
||||
"pictureProcess" => "urn:microsoft.com/office/officeart/2005/8/layout/pList2",
|
||||
"pictureProcess" => "urn:microsoft.com/office/officeart/2005/8/layout/hProcess10",
|
||||
"pictureHList" => "urn:microsoft.com/office/officeart/2005/8/layout/pList2",
|
||||
"timeline" => "urn:microsoft.com/office/officeart/2005/8/layout/hProcess11",
|
||||
"venn" => "urn:microsoft.com/office/officeart/2005/8/layout/venn1",
|
||||
"target" => "urn:microsoft.com/office/officeart/2005/8/layout/target1",
|
||||
_ => "urn:microsoft.com/office/officeart/2005/8/layout/vList2",
|
||||
}
|
||||
}
|
||||
@@ -138,6 +142,10 @@ pub fn kind_for(layout_id: &str, category: &str) -> &'static str {
|
||||
"pictureList",
|
||||
"pictureCaption",
|
||||
"pictureProcess",
|
||||
"pictureHList",
|
||||
"timeline",
|
||||
"venn",
|
||||
"target",
|
||||
"list",
|
||||
] {
|
||||
if layout_id == layout_id_of(k) {
|
||||
@@ -145,7 +153,13 @@ pub fn kind_for(layout_id: &str, category: &str) -> &'static str {
|
||||
}
|
||||
}
|
||||
let s = format!("{last} {}", category.to_ascii_lowercase());
|
||||
if s.contains("hierarchy") || s.contains("orgchart") {
|
||||
if s.contains("venn") {
|
||||
"venn"
|
||||
} else if s.contains("target") {
|
||||
"target"
|
||||
} else if s.contains("timeline") {
|
||||
"timeline"
|
||||
} else if s.contains("hierarchy") || s.contains("orgchart") {
|
||||
"hierarchy"
|
||||
} else if s.contains("radial") {
|
||||
"radial"
|
||||
@@ -174,7 +188,8 @@ fn layout_id_of(kind: &str) -> &'static str {
|
||||
|
||||
fn category(kind: &str) -> &'static str {
|
||||
match kind {
|
||||
"process" | "chevron" | "verticalProcess" | "stepUp" | "pictureProcess" => "process",
|
||||
"process" | "chevron" | "verticalProcess" | "stepUp" | "pictureProcess" | "timeline" => "process",
|
||||
"venn" | "target" => "relationship",
|
||||
"cycle" | "blockCycle" | "radial" => "cycle",
|
||||
"hierarchy" => "hierarchy",
|
||||
"pyramid" | "invertedPyramid" => "pyramid",
|
||||
@@ -206,8 +221,8 @@ fn text(w: &mut W, t: &str) {
|
||||
}
|
||||
|
||||
/// `ppt/diagrams/dataN.xml` for the outline. `drawing_rel` is the slide's relationship id of the
|
||||
/// cached drawing.
|
||||
pub fn data_xml(d: &Diagram, seed: u32, drawing_rel: &str) -> Vec<u8> {
|
||||
/// cached drawing; `pics` the data part's relationship ids of node pictures, by node index.
|
||||
pub fn data_xml(d: &Diagram, seed: u32, drawing_rel: &str, pics: &[(usize, String)]) -> Vec<u8> {
|
||||
let lo = if d.layout_id.is_empty() { layout_id(&d.kind).to_string() } else { d.layout_id.clone() };
|
||||
let mut w = W::new();
|
||||
w.open("dgm:dataModel", A::new().a("xmlns:dgm", NS_DGM).a("xmlns:a", crate::opc::NS_A).a("xmlns:r", crate::opc::NS_R));
|
||||
@@ -245,6 +260,24 @@ pub fn data_xml(d: &Diagram, seed: u32, drawing_rel: &str) -> Vec<u8> {
|
||||
w.close("dgm:pt");
|
||||
}
|
||||
}
|
||||
// A node's picture sits on the presentation point of its picture placeholder, as PowerPoint
|
||||
// stores it (`presAssocID` names the node, the blip is a relationship of the data part).
|
||||
for (k, rid) in pics {
|
||||
if *k >= d.nodes.len() {
|
||||
continue;
|
||||
}
|
||||
w.open("dgm:pt", A::new().a("modelId", guid(seed, 0x20_0000 + k)).a("type", "pres"));
|
||||
w.empty("dgm:prSet", A::new().a("presAssocID", node(*k)).a("presName", "Image").a("presStyleLbl", "fgImgPlace1"));
|
||||
w.open0("dgm:spPr");
|
||||
w.open0("a:blipFill");
|
||||
w.empty("a:blip", A::new().a("r:embed", rid.as_str()));
|
||||
w.open0("a:stretch");
|
||||
w.empty0("a:fillRect");
|
||||
w.close("a:stretch");
|
||||
w.close("a:blipFill");
|
||||
w.close("dgm:spPr");
|
||||
w.close("dgm:pt");
|
||||
}
|
||||
w.close("dgm:ptLst");
|
||||
w.open0("dgm:cxnLst");
|
||||
let mut stack: Vec<(u8, String)> = vec![];
|
||||
@@ -286,6 +319,31 @@ pub fn data_xml(d: &Diagram, seed: u32, drawing_rel: &str) -> Vec<u8> {
|
||||
|
||||
/// The outline of a data part: the `parOf` tree under the document point, depth first.
|
||||
pub fn outline(data: &El) -> Vec<DiagramNode> {
|
||||
outline_ids(data).into_iter().map(|(_, n)| n).collect()
|
||||
}
|
||||
|
||||
/// The relationship ids (of the data part) of node pictures, by node index in [`outline`]: the
|
||||
/// blip fill of a presentation point associated with the node.
|
||||
pub fn node_pictures(data: &El) -> Vec<(usize, String)> {
|
||||
let ids: HashMap<String, usize> = outline_ids(data).into_iter().enumerate().map(|(k, (id, _))| (id, k)).collect();
|
||||
let Some(pts) = data.child("ptLst") else { return vec![] };
|
||||
let mut out: Vec<(usize, String)> = vec![];
|
||||
for pt in pts.children_named("pt").take(MAX_NODES * 8) {
|
||||
if pt.attr("type") != Some("pres") {
|
||||
continue;
|
||||
}
|
||||
let Some(k) = pt.child("prSet").and_then(|p| p.attr("presAssocID")).and_then(|a| ids.get(a)) else { continue };
|
||||
let Some(rid) = pt.child("spPr").and_then(|s| s.child("blipFill")).and_then(|b| b.child("blip")).and_then(|b| b.attr("r:embed")) else {
|
||||
continue;
|
||||
};
|
||||
if !out.iter().any(|(j, _)| j == k) {
|
||||
out.push((*k, rid.to_string()));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn outline_ids(data: &El) -> Vec<(String, DiagramNode)> {
|
||||
let Some(pts) = data.child("ptLst") else { return vec![] };
|
||||
let mut texts: HashMap<&str, String> = HashMap::new();
|
||||
let mut doc: Option<&str> = None;
|
||||
@@ -324,7 +382,7 @@ pub fn outline(data: &El) -> Vec<DiagramNode> {
|
||||
if out.len() >= MAX_NODES || !seen.insert(id) {
|
||||
continue;
|
||||
}
|
||||
out.push(DiagramNode { text: texts.get(id).cloned().unwrap_or_default(), level: level.min(8), picture: None });
|
||||
out.push((id.to_string(), DiagramNode { text: texts.get(id).cloned().unwrap_or_default(), level: level.min(8), picture: None }));
|
||||
if let Some(v) = kids.get(id) {
|
||||
stack.extend(v.iter().rev().map(|(_, c)| (*c, level.saturating_add(1))));
|
||||
}
|
||||
@@ -348,10 +406,12 @@ fn paragraphs(t: &El) -> String {
|
||||
/// The layout algorithm and node shape of a DeckCraft layout.
|
||||
fn alg(kind: &str) -> (&'static str, Vec<(&'static str, &'static str)>, &'static str) {
|
||||
match kind {
|
||||
"process" | "stepUp" | "pictureProcess" | "pictureCaption" => ("lin", vec![("linDir", "fromL"), ("nodeVertAlign", "mid")], "roundRect"),
|
||||
"process" | "stepUp" | "pictureProcess" | "pictureCaption" | "pictureHList" | "timeline" => {
|
||||
("lin", vec![("linDir", "fromL"), ("nodeVertAlign", "mid")], "roundRect")
|
||||
}
|
||||
"chevron" => ("lin", vec![("linDir", "fromL"), ("nodeVertAlign", "mid")], "chevron"),
|
||||
"verticalProcess" => ("lin", vec![("linDir", "fromT"), ("nodeHorzAlign", "ctr")], "roundRect"),
|
||||
"cycle" | "radial" => ("cycle", vec![("stAng", "0"), ("spanAng", "360")], "ellipse"),
|
||||
"cycle" | "radial" | "venn" | "target" => ("cycle", vec![("stAng", "0"), ("spanAng", "360")], "ellipse"),
|
||||
"blockCycle" => ("cycle", vec![("stAng", "0"), ("spanAng", "360")], "roundRect"),
|
||||
"pyramid" | "invertedPyramid" => {
|
||||
("pyra", vec![("linDir", if kind == "pyramid" { "fromT" } else { "fromB" }), ("txDir", "fromT"), ("pyraAcctPos", "bef")], "trapezoid")
|
||||
@@ -500,6 +560,8 @@ fn bevel(style: &str) -> Option<(&'static str, i64)> {
|
||||
|
||||
/// `ppt/diagrams/quickStyleN.xml`: flat shapes drawn from the theme's first styles.
|
||||
pub fn style_xml(style: &str) -> Vec<u8> {
|
||||
// Brick Scene looks at the diagram from above and to the side.
|
||||
let camera = if style == "brick" { "isometricOffAxis2Top" } else { "orthographicFront" };
|
||||
let mut w = W::new();
|
||||
w.open("dgm:styleDef", A::new().a("xmlns:dgm", NS_DGM).a("xmlns:a", crate::opc::NS_A).a("uniqueId", qs_id(style)));
|
||||
w.empty("dgm:title", A::new().a("val", ""));
|
||||
@@ -508,12 +570,12 @@ pub fn style_xml(style: &str) -> Vec<u8> {
|
||||
w.empty("dgm:cat", A::new().a("type", if bevel(style).is_some() { "3D" } else { "simple" }).a("pri", "10100"));
|
||||
w.close("dgm:catLst");
|
||||
w.open0("dgm:scene3d");
|
||||
w.empty("a:camera", A::new().a("prst", "orthographicFront"));
|
||||
w.empty("a:camera", A::new().a("prst", camera));
|
||||
w.empty("a:lightRig", A::new().a("rig", "threePt").a("dir", "t"));
|
||||
w.close("dgm:scene3d");
|
||||
w.open("dgm:styleLbl", A::new().a("name", "node1"));
|
||||
w.open0("dgm:scene3d");
|
||||
w.empty("a:camera", A::new().a("prst", "orthographicFront"));
|
||||
w.empty("a:camera", A::new().a("prst", camera));
|
||||
w.empty("a:lightRig", A::new().a("rig", "threePt").a("dir", "t"));
|
||||
w.close("dgm:scene3d");
|
||||
match bevel(style) {
|
||||
@@ -593,7 +655,7 @@ mod tests {
|
||||
fn outline_survives_the_data_model() {
|
||||
let d =
|
||||
Diagram { kind: "hierarchy".into(), nodes: nodes(&[("CEO", 0), ("CTO", 1), ("Dev", 2), ("CFO", 1), ("Board", 0)]), ..Default::default() };
|
||||
let xml = data_xml(&d, 7, "rId9");
|
||||
let xml = data_xml(&d, 7, "rId9", &[]);
|
||||
let doc = crate::xml::parse(&xml).unwrap();
|
||||
assert_eq!(outline(&doc.root), d.nodes);
|
||||
let s = String::from_utf8(xml).unwrap();
|
||||
@@ -613,6 +675,14 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn node_pictures_come_from_presentation_points() {
|
||||
let d = Diagram { kind: "pictureList".into(), nodes: nodes(&[("A", 0), ("a", 1), ("B", 0)]), ..Default::default() };
|
||||
let xml = data_xml(&d, 3, "rId1", &[(2, "rId7".into()), (9, "rId8".into())]);
|
||||
let doc = crate::xml::parse(&xml).unwrap();
|
||||
assert_eq!(node_pictures(&doc.root), vec![(2, "rId7".to_string())]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn style_and_colour_ids_round_trip() {
|
||||
for st in ["", "subtle", "intense", "outline", "polished", "inset", "cartoon", "powder", "brick"] {
|
||||
@@ -622,7 +692,9 @@ mod tests {
|
||||
assert_eq!(colors_for(&cs_id(c)), c);
|
||||
}
|
||||
assert!(String::from_utf8(style_xml("cartoon")).unwrap().contains("bevelT"));
|
||||
for k in ["pictureList", "pictureCaption", "pictureProcess"] {
|
||||
assert!(layout_id("pictureProcess").ends_with("/hProcess10"), "Picture Accent Process");
|
||||
assert!(layout_id("pictureHList").ends_with("/pList2"), "Horizontal Picture List");
|
||||
for k in ["pictureList", "pictureCaption", "pictureProcess", "pictureHList", "timeline", "venn", "target"] {
|
||||
assert_eq!(kind_for(layout_id(k), ""), k);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -509,6 +509,17 @@ fn smartart(imp: &mut Imp, part: &Part, gd: &El, frame: &Shape, depth: usize) ->
|
||||
let x = frame.xfrm.unwrap_or_default();
|
||||
g.kind = ShapeKind::Group { children, child: Xfrm::new(0.0, 0.0, x.w, x.h) };
|
||||
g.diagram = diagram_outline(imp, part, rel_ids, &data.root);
|
||||
// Node pictures, from the blips of the data model's presentation points.
|
||||
if let Some(d) = &mut g.diagram {
|
||||
let rels = imp.pkg.rels(&dm.target);
|
||||
for (k, rid) in crate::dgm::node_pictures(&data.root) {
|
||||
let Some(target) = rels.get(&rid).filter(|r| !r.external).map(|r| r.target.clone()) else { continue };
|
||||
let media = imp.media_for(&target);
|
||||
if let Some(n) = d.nodes.get_mut(k) {
|
||||
n.picture = media;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(g)
|
||||
}
|
||||
|
||||
|
||||
@@ -534,7 +534,12 @@ fn diagram(w: &mut W, x: &mut Exp, o: &mut Out, cx: &mut Ctx, s: &Shape, d: &dec
|
||||
let rel = |o: &mut Out, kind: &str, part: &str| o.rels.add_new(kind, &crate::opc::relative(&o.name, part), false);
|
||||
let drawing_rel = rel(o, dgm::RT_DRAWING, &dname);
|
||||
let data = name("data");
|
||||
x.pkg.xml_part(&data, dgm::CT_DATA, dgm::data_xml(d, s.id.0, &drawing_rel));
|
||||
// Node pictures: on the data model's presentation points, through the data part's rels.
|
||||
let mut mout = Out::new(&data);
|
||||
let pics: Vec<(usize, String)> =
|
||||
d.nodes.iter().enumerate().filter_map(|(k, n)| n.picture.and_then(|m| x.media_rel(&mut mout, m, "image")).map(|r| (k, r))).collect();
|
||||
x.pkg.xml_part(&data, dgm::CT_DATA, dgm::data_xml(d, s.id.0, &drawing_rel, &pics));
|
||||
x.pkg.rels(&data, &mout.rels);
|
||||
let dm = rel(o, "diagramData", &data);
|
||||
let layout = name("layout");
|
||||
let lo_xml = d.layout_xml.clone().map(String::into_bytes).unwrap_or_else(|| dgm::layout_xml(&d.kind));
|
||||
|
||||
Reference in New Issue
Block a user