mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-12 10:03:51 +00:00
craft-drawingml: extra named theme colours and explicit tint rows
Ported from the format part of storytold/deckcraft#1746 by @jagjordi: ColorScheme::{extra, tints, computed_tints}, ExtraColor, GalleryVariant, variants_of, and the a:clrScheme extension that keeps them in a file.
This commit is contained in:
committed by
Dylan M. Taylor
parent
1ed16df88b
commit
e58ea291d4
@@ -1,6 +1,8 @@
|
||||
//! Colour: sRGB colours, theme colour slots and DrawingML-style colour transforms
|
||||
//! (tint, shade, luminance modulation/offset, alpha…), so theme-relative colours resolve the way
|
||||
//! presentation files expect.
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// An sRGB colour with straight alpha, components 0–255.
|
||||
@@ -137,7 +139,7 @@ impl Rgba {
|
||||
|
||||
/// The twelve theme colour slots, plus the mapped aliases (`bg1`, `tx1`…) and `phClr` (the
|
||||
/// placeholder colour a style matrix entry is applied with).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum SchemeSlot {
|
||||
Dk1,
|
||||
@@ -236,15 +238,62 @@ impl SchemeSlot {
|
||||
}
|
||||
}
|
||||
|
||||
/// A colour scheme: the twelve theme colours.
|
||||
/// Rows of tints and shades computed under a colour of the colour galleries.
|
||||
pub const VARIANT_ROWS: usize = 5;
|
||||
/// The most rows an explicit `tints` list may hold.
|
||||
pub const MAX_TINT_ROWS: usize = 10;
|
||||
/// The most extra named colours a scheme holds: what `design.colors` and the dialog accept, and
|
||||
/// what a file carries in either direction.
|
||||
pub const MAX_EXTRA_COLORS: usize = 200;
|
||||
|
||||
/// A named colour beyond the twelve slots (a brand palette entry). It is shown in the colour
|
||||
/// galleries after the theme colours and can be named wherever a scheme colour name is accepted;
|
||||
/// applying it stores its RGB, since files have no slot for it.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExtraColor {
|
||||
pub name: String,
|
||||
pub color: Rgba,
|
||||
/// Explicit gallery rows under the colour (0 to [`MAX_TINT_ROWS`]; exactly these are shown)
|
||||
/// in place of the computed tints and shades.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub tints: Option<Vec<Rgba>>,
|
||||
}
|
||||
|
||||
/// One cell under a gallery colour: the colour shown and how it is applied (`mods` on the slot
|
||||
/// when computed, the colour itself when it comes from an explicit `tints` override).
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct GalleryVariant {
|
||||
pub color: Rgba,
|
||||
pub label: String,
|
||||
pub mods: Option<Vec<ColorTransform>>,
|
||||
}
|
||||
|
||||
/// A colour scheme: the twelve theme colours, plus any number of extra named colours and
|
||||
/// optional explicit tint rows (both extensions: a file keeps them in an `a:extLst` of the colour
|
||||
/// scheme that other applications ignore, see [`crate::theme::EXT_EXTRA_COLORS`]).
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ColorScheme {
|
||||
pub name: String,
|
||||
pub colors: [Rgba; 12],
|
||||
/// Named colours shown after the theme colours, in order.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub extra: Vec<ExtraColor>,
|
||||
/// Explicit gallery rows per theme slot (0 to [`MAX_TINT_ROWS`]; exactly these are shown),
|
||||
/// replacing the computed tints and shades.
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub tints: BTreeMap<SchemeSlot, Vec<Rgba>>,
|
||||
/// Whether a colour without an explicit list gets the computed tint and shade rows (the
|
||||
/// default); `false` shows no rows under it.
|
||||
#[serde(default = "crate::yes", skip_serializing_if = "Clone::clone")]
|
||||
pub computed_tints: bool,
|
||||
}
|
||||
|
||||
impl ColorScheme {
|
||||
pub fn new(name: impl Into<String>, colors: [Rgba; 12]) -> Self {
|
||||
ColorScheme { name: name.into(), colors, extra: Vec::new(), tints: BTreeMap::new(), computed_tints: true }
|
||||
}
|
||||
pub fn get(&self, slot: SchemeSlot) -> Rgba {
|
||||
let i = match slot {
|
||||
SchemeSlot::Dk1 | SchemeSlot::Tx1 => 0,
|
||||
@@ -270,6 +319,36 @@ impl ColorScheme {
|
||||
*v = c;
|
||||
}
|
||||
}
|
||||
/// The extra colour called `name` (case-insensitive, trimmed), if any.
|
||||
pub fn extra_color(&self, name: &str) -> Option<&ExtraColor> {
|
||||
let name = name.trim();
|
||||
if name.is_empty() {
|
||||
return None;
|
||||
}
|
||||
self.extra.iter().find(|e| e.name.trim().eq_ignore_ascii_case(name))
|
||||
}
|
||||
/// The gallery rows under theme colour `slot`: exactly its `tints` list where given, else the
|
||||
/// computed tints and shades when `computed_tints`, else none.
|
||||
pub fn slot_variants(&self, slot: SchemeSlot) -> Vec<GalleryVariant> {
|
||||
variants_of(self.get(slot), self.tints.get(&slot).map(Vec::as_slice), self.computed_tints)
|
||||
}
|
||||
/// The gallery rows under extra colour `i` (as for a slot).
|
||||
pub fn extra_variants(&self, i: usize) -> Vec<GalleryVariant> {
|
||||
self.extra.get(i).map(|e| variants_of(e.color, e.tints.as_deref(), self.computed_tints)).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
/// The cells under `base`: exactly the explicit `tints` when there is a list (possibly none), the
|
||||
/// [`VARIANT_ROWS`] computed tints and shades when `computed`, nothing otherwise.
|
||||
pub fn variants_of(base: Rgba, tints: Option<&[Rgba]>, computed: bool) -> Vec<GalleryVariant> {
|
||||
match tints {
|
||||
Some(list) => list.iter().take(MAX_TINT_ROWS).map(|c| GalleryVariant { color: *c, label: format!("#{}", c.hex()), mods: None }).collect(),
|
||||
None if computed => theme_variants(base)
|
||||
.into_iter()
|
||||
.map(|(mods, label)| GalleryVariant { color: apply(base, &mods), label: label.to_string(), mods: Some(mods) })
|
||||
.collect(),
|
||||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// One colour transform, in file units (percentages ×1000, angles ×60000), applied in order.
|
||||
@@ -972,7 +1051,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn scheme_slots() {
|
||||
let mut s = ColorScheme { name: "t".into(), colors: [Rgba::BLACK; 12] };
|
||||
let mut s = ColorScheme::new("t", [Rgba::BLACK; 12]);
|
||||
s.set(SchemeSlot::Accent3, Rgba::WHITE);
|
||||
assert_eq!(s.get(SchemeSlot::Accent3), Rgba::WHITE);
|
||||
assert_eq!(s.get(SchemeSlot::Tx1), Rgba::BLACK);
|
||||
@@ -1003,4 +1082,70 @@ mod tests {
|
||||
assert_eq!(system("btnFace"), Some(Rgba::rgb(0xF0, 0xF0, 0xF0)));
|
||||
assert_eq!(system("scrollBar"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variants_are_the_list_given_else_computed_else_none() {
|
||||
let base = Rgba::rgb(0x4D, 0xA0, 0x60);
|
||||
let computed = variants_of(base, None, true);
|
||||
assert_eq!(computed.len(), VARIANT_ROWS);
|
||||
assert!(computed.iter().all(|v| v.mods.is_some()));
|
||||
assert_eq!(computed[0].label, "Lighter 80%");
|
||||
assert!(variants_of(base, None, false).is_empty(), "no list and no computed rows: nothing");
|
||||
let two = [Rgba::rgb(0xC7, 0xEB, 0xBA), Rgba::rgb(0x0D, 0x4A, 0x21)];
|
||||
for computed in [true, false] {
|
||||
let v = variants_of(base, Some(&two), computed);
|
||||
assert_eq!(v.len(), 2, "exactly the list, no filling");
|
||||
assert_eq!(v[0], GalleryVariant { color: two[0], label: "#C7EBBA".into(), mods: None });
|
||||
assert_eq!(v[1].color, two[1]);
|
||||
assert!(variants_of(base, Some(&[]), computed).is_empty(), "an empty list: no rows");
|
||||
}
|
||||
assert_eq!(variants_of(base, Some(&[Rgba::WHITE; 20]), true).len(), MAX_TINT_ROWS, "a long list is cut");
|
||||
let mut s = ColorScheme::new("t", [base; 12]);
|
||||
s.tints.insert(SchemeSlot::Accent5, two.to_vec());
|
||||
s.tints.insert(SchemeSlot::Accent6, vec![]);
|
||||
assert_eq!(s.slot_variants(SchemeSlot::Accent5).len(), 2);
|
||||
assert!(s.slot_variants(SchemeSlot::Accent6).is_empty());
|
||||
assert_eq!(s.slot_variants(SchemeSlot::Accent4), computed);
|
||||
s.computed_tints = false;
|
||||
assert_eq!(s.slot_variants(SchemeSlot::Accent5).len(), 2);
|
||||
assert!(s.slot_variants(SchemeSlot::Accent4).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extra_colours_by_name() {
|
||||
let mut s = ColorScheme::new("t", [Rgba::BLACK; 12]);
|
||||
let sand = Rgba::rgb(0xEB, 0xE5, 0xE0);
|
||||
s.extra.push(ExtraColor { name: "Sand".into(), color: sand, tints: Some(vec![Rgba::WHITE]) });
|
||||
assert_eq!(s.extra_color("sand").map(|e| e.color), Some(sand));
|
||||
assert_eq!(s.extra_color(" SAND ").map(|e| e.color), Some(sand));
|
||||
assert_eq!(s.extra_color("Navy"), None);
|
||||
assert_eq!(s.extra_color(""), None);
|
||||
assert_eq!(s.extra_variants(0), vec![GalleryVariant { color: Rgba::WHITE, label: "#FFFFFF".into(), mods: None }]);
|
||||
assert!(s.extra_variants(7).is_empty());
|
||||
s.extra.push(ExtraColor { name: "Navy".into(), color: Rgba::rgb(0, 0, 0x61), tints: None });
|
||||
assert_eq!(s.extra_variants(1).len(), VARIANT_ROWS);
|
||||
s.computed_tints = false;
|
||||
assert!(s.extra_variants(1).is_empty());
|
||||
assert_eq!(s.extra_variants(0).len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scheme_json_keeps_extras_and_reads_old_files() {
|
||||
let mut s = ColorScheme::new("KTH", [Rgba::WHITE; 12]);
|
||||
s.extra.push(ExtraColor { name: "Sand".into(), color: Rgba::rgb(0xEB, 0xE5, 0xE0), tints: None });
|
||||
s.tints.insert(SchemeSlot::Accent1, vec![Rgba::BLACK; VARIANT_ROWS]);
|
||||
s.tints.insert(SchemeSlot::Accent2, vec![]);
|
||||
s.computed_tints = false;
|
||||
let json = serde_json::to_string(&s).unwrap();
|
||||
assert!(json.contains("\"accent1\"") && json.contains("\"computedTints\":false"), "{json}");
|
||||
assert_eq!(serde_json::from_str::<ColorScheme>(&json).unwrap(), s);
|
||||
s.computed_tints = true;
|
||||
assert!(!serde_json::to_string(&s).unwrap().contains("computedTints"), "the default is not written");
|
||||
// A file written before the extension has neither key.
|
||||
let plain = ColorScheme::new("t", [Rgba::BLACK; 12]);
|
||||
let json = serde_json::to_string(&plain).unwrap();
|
||||
assert!(!json.contains("extra") && !json.contains("tints"), "{json}");
|
||||
assert_eq!(serde_json::from_str::<ColorScheme>(&json).unwrap(), plain);
|
||||
assert!(plain.computed_tints);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,4 +192,51 @@ mod tests {
|
||||
assert_eq!(back.format.fills, t.format.fills);
|
||||
assert_eq!(back.format.lines.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extra_theme_colours_and_tint_rows_survive_writing_and_reading() {
|
||||
use crate::color::{ExtraColor, MAX_EXTRA_COLORS, MAX_TINT_ROWS, Rgba, VARIANT_ROWS};
|
||||
let mut t = Theme::default();
|
||||
let c = &mut t.colors;
|
||||
c.computed_tints = false;
|
||||
c.tints.insert(SchemeSlot::Accent5, vec![Rgba::rgb(0xC7, 0xEB, 0xBA), Rgba::rgb(0x0D, 0x4A, 0x21)]);
|
||||
c.tints.insert(SchemeSlot::Accent6, vec![]);
|
||||
c.tints.insert(SchemeSlot::Dk2, vec![Rgba::WHITE; VARIANT_ROWS]);
|
||||
c.extra.push(ExtraColor { name: "Sand".into(), color: Rgba::rgb(0xEB, 0xE5, 0xE0), tints: None });
|
||||
c.extra.push(ExtraColor { name: "Broken white".into(), color: Rgba::rgb(0xFC, 0xFC, 0xFC), tints: Some(vec![]) });
|
||||
c.extra.push(ExtraColor {
|
||||
name: "Grey & \"tones\"".into(),
|
||||
color: Rgba::rgb(0xA5, 0xA5, 0xA5),
|
||||
tints: Some(vec![Rgba::rgb(0xE6, 0xE6, 0xE6), Rgba::rgb(0x32, 0x32, 0x32)]),
|
||||
});
|
||||
let xml = String::from_utf8(crate::theme::theme_xml(&mut Part::new(), &t)).unwrap();
|
||||
let back = crate::theme::read_theme(&mut Part::new(), &parse(xml.as_bytes()).unwrap());
|
||||
assert_eq!(back.colors, t.colors, "lists of 0, 2 and 5 rows, no list, and computed_tints");
|
||||
let (cs_start, cs_end) = (xml.find("<a:clrScheme").unwrap(), xml.find("</a:clrScheme>").unwrap());
|
||||
let ext = xml.find("<a:extLst>").unwrap();
|
||||
assert!(cs_start < ext && ext < cs_end, "the extension belongs to the colour scheme");
|
||||
assert!(xml.contains("computed=\"0\""), "{xml}");
|
||||
assert!(xml.contains("<dc:color name=\"Sand\" val=\"EBE5E0\"></dc:color>"), "{xml}");
|
||||
assert!(xml.contains("<dc:tints slot=\"accent6\"></dc:tints>"), "{xml}");
|
||||
assert!(xml.contains("Grey & "tones""), "{xml}");
|
||||
// A plain scheme writes no extension; `computed_tints = false` alone is kept.
|
||||
let plain = String::from_utf8(crate::theme::theme_xml(&mut Part::new(), &Theme::default())).unwrap();
|
||||
assert!(!plain.contains("extraColors"), "{plain}");
|
||||
let mut t = Theme::default();
|
||||
t.colors.computed_tints = false;
|
||||
let xml = crate::theme::theme_xml(&mut Part::new(), &t);
|
||||
assert!(!crate::theme::read_theme(&mut Part::new(), &parse(&xml).unwrap()).colors.computed_tints);
|
||||
// Lists and extras past the limits are cut the same way on writing and reading.
|
||||
let mut t = Theme::default();
|
||||
let long: Vec<Rgba> = (0..MAX_TINT_ROWS + 3).map(|i| Rgba::rgb(i as u8, 0, 0)).collect();
|
||||
t.colors.tints.insert(SchemeSlot::Accent1, long.clone());
|
||||
for i in 0..MAX_EXTRA_COLORS + 7 {
|
||||
t.colors.extra.push(ExtraColor { name: format!("Extra {i}"), color: Rgba::rgb((i % 256) as u8, 0, 0), tints: Some(long.clone()) });
|
||||
}
|
||||
let xml = crate::theme::theme_xml(&mut Part::new(), &t);
|
||||
let back = crate::theme::read_theme(&mut Part::new(), &parse(&xml).unwrap()).colors;
|
||||
assert_eq!(back.tints.get(&SchemeSlot::Accent1).map(Vec::as_slice), Some(&long[..MAX_TINT_ROWS]));
|
||||
assert_eq!(back.extra.len(), MAX_EXTRA_COLORS);
|
||||
assert_eq!(back.extra[0].tints.as_deref(), Some(&long[..MAX_TINT_ROWS]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ impl Default for Theme {
|
||||
let colors = ["000000", "FFFFFF", "1E2B3C", "EDEBE7", "2E6FD8", "F26B1D", "2EA36F", "8A5CD8", "D83F6B", "E3B21C", "2E6FD8", "7A5BA6"];
|
||||
Theme {
|
||||
name: "Theme".into(),
|
||||
colors: ColorScheme { name: "Default".into(), colors: colors.map(hex) },
|
||||
colors: ColorScheme::new("Default", colors.map(hex)),
|
||||
fonts: FontScheme::default(),
|
||||
format: default_format(),
|
||||
raw_extra: None,
|
||||
@@ -182,6 +182,13 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// The `a:ext` of a colour scheme that holds its extra named colours and explicit tint rows
|
||||
/// ([`ColorScheme::extra`], [`ColorScheme::tints`], [`ColorScheme::computed_tints`]). The URI and
|
||||
/// namespace are those of the files already written with it.
|
||||
pub const EXT_EXTRA_COLORS: &str = "{D3C7C0DE-0C0A-4E57-9A1E-5C0E0C0DEC01}";
|
||||
/// The namespace of the elements inside [`EXT_EXTRA_COLORS`].
|
||||
pub const NS_EXTRA_COLORS: &str = "urn:deckcraft:theme:extra-colors:1";
|
||||
|
||||
mod read;
|
||||
mod write;
|
||||
pub use read::read_theme;
|
||||
|
||||
@@ -28,7 +28,7 @@ pub fn read_theme(src: &mut dyn Source, root: &El) -> Theme {
|
||||
let mut t = Theme { name: root.attr_local("name").unwrap_or("Theme").to_string(), ..Default::default() };
|
||||
let Some(te) = root.child(Local("themeElements")) else { return t };
|
||||
if let Some(cs) = te.child(Local("clrScheme")) {
|
||||
let mut scheme = ColorScheme { name: cs.attr_local("name").unwrap_or("").to_string(), colors: t.colors.colors };
|
||||
let mut scheme = ColorScheme::new(cs.attr_local("name").unwrap_or(""), t.colors.colors);
|
||||
for slot in SchemeSlot::THEME {
|
||||
if let Some(c) = cs.child(Local(slot.xml_name())).and_then(dml::color) {
|
||||
let rgb = match c.base {
|
||||
@@ -40,6 +40,7 @@ pub fn read_theme(src: &mut dyn Source, root: &El) -> Theme {
|
||||
scheme.set(slot, rgb);
|
||||
}
|
||||
}
|
||||
extra_colors(cs, &mut scheme);
|
||||
t.colors = scheme;
|
||||
}
|
||||
if let Some(fs) = te.child(Local("fontScheme")) {
|
||||
@@ -99,6 +100,36 @@ pub fn read_theme(src: &mut dyn Source, root: &El) -> Theme {
|
||||
t
|
||||
}
|
||||
|
||||
/// The extra named colours and tint rows (see `write::extra_colors`); a scheme saved by another
|
||||
/// application has none.
|
||||
fn extra_colors(cs: &El, scheme: &mut ColorScheme) {
|
||||
let Some(x) = cs
|
||||
.child(Local("extLst"))
|
||||
.and_then(|l| l.children(Local("ext")).find(|e| e.attr_local("uri") == Some(super::EXT_EXTRA_COLORS)))
|
||||
.and_then(|e| e.child(Local("extraColors")))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let clrs = |e: &El| -> Vec<Rgba> {
|
||||
e.children(Local("clr")).filter_map(|c| c.attr_local("val").and_then(Rgba::from_hex)).take(crate::color::MAX_TINT_ROWS).collect()
|
||||
};
|
||||
scheme.computed_tints = x.attr_local("computed") != Some("0");
|
||||
for t in x.children(Local("tints")).take(SchemeSlot::THEME.len()) {
|
||||
if let Some(slot) = t.attr_local("slot").and_then(SchemeSlot::from_xml)
|
||||
&& SchemeSlot::THEME.contains(&slot)
|
||||
{
|
||||
scheme.tints.insert(slot, clrs(t));
|
||||
}
|
||||
}
|
||||
for c in x.children(Local("color")).take(crate::color::MAX_EXTRA_COLORS) {
|
||||
let (Some(name), Some(color)) = (c.attr_local("name"), c.attr_local("val").and_then(Rgba::from_hex)) else { continue };
|
||||
if name.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
scheme.extra.push(crate::color::ExtraColor { name: name.to_string(), color, tints: c.child(Local("tints")).map(clrs) });
|
||||
}
|
||||
}
|
||||
|
||||
fn pad<T: Clone>(v: &mut Vec<T>, d: &[T]) {
|
||||
while v.len() < 3 {
|
||||
match d.get(v.len()) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Theme parts.
|
||||
|
||||
use crate::color::SchemeSlot;
|
||||
use crate::color::{ColorScheme, Rgba, SchemeSlot};
|
||||
use crate::style::{ColorRef, Effects, Fill};
|
||||
use crate::theme::{FontSet, Theme, default_format};
|
||||
|
||||
@@ -28,6 +28,44 @@ fn style_fill(f: &Fill) -> Fill {
|
||||
}
|
||||
}
|
||||
|
||||
/// The scheme's extra named colours, explicit tint rows and `computed_tints`, as an `a:ext`
|
||||
/// ([`super::EXT_EXTRA_COLORS`]) after the twelve slots: `<dc:extraColors computed="0"?>` holding
|
||||
/// `<dc:tints slot="accent1"><dc:clr val="RRGGBB"/>…</dc:tints>` per slot with a list (an empty
|
||||
/// list is an empty element) and `<dc:color name="Sand" val="RRGGBB">(<dc:tints>…</dc:tints>
|
||||
/// when it has a list)</dc:color>` per extra colour. Nothing when there is nothing to keep.
|
||||
fn extra_colors(w: &mut Writer, c: &ColorScheme) {
|
||||
if c.extra.is_empty() && c.tints.is_empty() && c.computed_tints {
|
||||
return;
|
||||
}
|
||||
w.open("a:extLst", &[]);
|
||||
w.tag("a:ext").attr("uri", super::EXT_EXTRA_COLORS).open();
|
||||
w.tag("dc:extraColors").attr("xmlns:dc", super::NS_EXTRA_COLORS).attr_opt("computed", (!c.computed_tints).then_some("0")).open();
|
||||
for (slot, tints) in &c.tints {
|
||||
w.tag("dc:tints").attr("slot", slot.xml_name()).open();
|
||||
rows(w, tints);
|
||||
}
|
||||
// What the reader takes back, no more.
|
||||
for e in c.extra.iter().take(crate::color::MAX_EXTRA_COLORS) {
|
||||
w.tag("dc:color").attr("name", &e.name).attr("val", e.color.hex()).open();
|
||||
if let Some(t) = &e.tints {
|
||||
w.open("dc:tints", &[]);
|
||||
rows(w, t);
|
||||
}
|
||||
w.close();
|
||||
}
|
||||
w.close();
|
||||
w.close();
|
||||
w.close();
|
||||
}
|
||||
|
||||
/// The rows of an open `dc:tints`, cut to `MAX_TINT_ROWS` as the reader takes them; closes it.
|
||||
fn rows(w: &mut Writer, tints: &[Rgba]) {
|
||||
for t in tints.iter().take(crate::color::MAX_TINT_ROWS) {
|
||||
w.tag("dc:clr").attr("val", t.hex()).empty();
|
||||
}
|
||||
w.close();
|
||||
}
|
||||
|
||||
pub fn theme_xml(out: &mut dyn Sink, t: &Theme) -> Vec<u8> {
|
||||
let mut w = Writer::document();
|
||||
w.tag("a:theme").attr("xmlns:a", NS_A).attr("xmlns:r", NS_R).attr("name", if t.name.is_empty() { "Theme" } else { &t.name }).open();
|
||||
@@ -39,6 +77,7 @@ pub fn theme_xml(out: &mut dyn Sink, t: &Theme) -> Vec<u8> {
|
||||
w.tag("a:srgbClr").attr("val", t.colors.get(slot).hex()).empty();
|
||||
w.close();
|
||||
}
|
||||
extra_colors(&mut w, &t.colors);
|
||||
w.close();
|
||||
w.tag("a:fontScheme").attr("name", if t.fonts.name.is_empty() { &t.name } else { &t.fonts.name }).open();
|
||||
font_set(&mut w, "a:majorFont", &t.fonts.major);
|
||||
|
||||
Reference in New Issue
Block a user