mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-13 22:12:12 +00:00
craft-drawingml: preset geometry, text warp, custom geometry and colour fixes from open DeckCraft PRs (#1023)
* craft-drawingml preset: the extra outlines dispatch on a Name enum
Ported from storytold/deckcraft#739 by @Bryson14.
* craft-drawingml preset: the 3- and 4-bend elbow and the curved connectors follow presetShapeDefinitions
Ported from storytold/deckcraft#1851 by @Hamza0101.
* craft-drawingml warp: text on a guide sets each glyph down whole
Ported from storytold/deckcraft#1799 by @leobasilio.
* craft-drawingml preset: the Name dispatch test's digests as this repository builds the outlines
The digests in storytold/deckcraft#739 were taken on DeckCraft's fc35ad67. Here two curved arrows
digest differently both before and after the refactor (the preset sources are identical), so the
test now holds the values from craft-libs 2b35d243, before the refactor.
* craft-drawingml preset: every preset's text rectangle from presetShapeDefinitions
Ported from storytold/deckcraft#650 by @NikitkaRa. Its preset::defaults is #1851's
adjust_defaults, which this repository already has.
* craft-drawingml custgeom: connection sites (a:cxnLst) read in the shape space and written back
Ported from the format part of storytold/deckcraft#1748 by @jagjordi.
* craft-drawingml color: behaviour-preserving cleanups
Ported from storytold/deckcraft#1610 by Cédric Legrand.
* craft-drawingml: resolve current-main API and test conflicts
* craft-pptx: use DrawingML writer defaults for custom paths
* craft-drawingml: strengthen geometry regression checks
---------
Co-authored-by: Bryson Meiling <bryson.meiling@gmail.com>
Co-authored-by: Hamza <mail.hamzanaveed@gmail.com>
Co-authored-by: Leonardo <leobasilio@gmail.com>
Co-authored-by: Nikita Rakitin <nnnrrr93@gmail.com>
Co-authored-by: Jordi <jordiag@kth.se>
Co-authored-by: Cédric Legrand <cedric@legrand-tech.fr>
This commit is contained in:
18 files changed
+1433
-177
No files matched your search
@@ -89,3 +89,5 @@ and the exact transfer revision are recorded in each vendored crate's SOURCE.jso
|
||||
| `crates/raw/docs/coverage/adobe-cameras.tsv` | Facts (maker, model, extension, profile, minimum Camera Raw version) parsed by LightCraft contributors from Adobe's published list | [Adobe Camera Raw supported cameras](https://helpx.adobe.com/camera-raw/desktop/dng-and-file-formats/camera-raw-plug-supported-cameras.html), Wayback capture 20261003133011; moved from [LightCraft `docs/raw`](https://github.com/storytold/lightcraft/tree/5ff567a4a2e067bef1b6bce152c11e7f727ccbaf/docs/raw) | Facts, not copyrightable expression; the file is MIT OR Apache-2.0 |
|
||||
| `crates/raw/docs/coverage/{coverage,coverage-sweep,evidence,usage-weights}.tsv`, `coverage.py` | LightCraft contributors | [LightCraft `docs/raw`](https://github.com/storytold/lightcraft/tree/5ff567a4a2e067bef1b6bce152c11e7f727ccbaf/docs/raw) | MIT OR Apache-2.0 |
|
||||
| `crates/raw/docs/raf-corpus.sha256` (hashes only; the files are not bundled) | The photographers who contributed each file to raw.pixls.us | [raw.pixls.us](https://raw.pixls.us/) | CC0-1.0 |
|
||||
| `crates/drawingml/src/preset_text.txt` | DeckCraft contributors (Nikita Rakitin); generated by `crates/drawingml/tests/fixtures/make_preset_guides.py` from the guide formulas of ECMA-376 Part 1 `presetShapeDefinitions.xml` | [storytold/deckcraft#650](https://github.com/storytold/deckcraft/pull/650) | MIT OR Apache-2.0 (the formulas are the format's definition in ECMA-376) |
|
||||
| `crates/drawingml/tests/fixtures/preset_table.txt` | DeckCraft contributors (Nikita Rakitin); generated by `crates/drawingml/tests/fixtures/make_preset_table.py` from ECMA-376 Part 1 `presetShapeDefinitions.xml` | [storytold/deckcraft#650](https://github.com/storytold/deckcraft/pull/650) | MIT OR Apache-2.0 (the formulas are the format's definition in ECMA-376) |
|
||||
+118
-110
@@ -20,6 +20,11 @@ impl Default for Rgba {
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss, reason = "rounding and clamping bound the cast to 0..=255; NaN maps to zero")]
|
||||
fn channel_from_f64(value: f64) -> u8 {
|
||||
value.round().clamp(0.0, 255.0) as u8
|
||||
}
|
||||
|
||||
impl Rgba {
|
||||
pub const BLACK: Rgba = Rgba::rgb(0, 0, 0);
|
||||
pub const WHITE: Rgba = Rgba::rgb(255, 255, 255);
|
||||
@@ -59,16 +64,18 @@ impl Rgba {
|
||||
Rgba { a, ..self }
|
||||
}
|
||||
pub fn to_f32(self) -> [f32; 4] {
|
||||
[self.r as f32 / 255.0, self.g as f32 / 255.0, self.b as f32 / 255.0, self.a as f32 / 255.0]
|
||||
[f32::from(self.r) / 255.0, f32::from(self.g) / 255.0, f32::from(self.b) / 255.0, f32::from(self.a) / 255.0]
|
||||
}
|
||||
pub fn from_f64(r: f64, g: f64, b: f64, a: f64) -> Self {
|
||||
let c = |v: f64| if v.is_finite() { (v.clamp(0.0, 1.0) * 255.0).round() as u8 } else { 0 };
|
||||
Rgba { r: c(r), g: c(g), b: c(b), a: c(a) }
|
||||
pub fn from_f64(red: f64, green: f64, blue: f64, alpha: f64) -> Self {
|
||||
let channel = |value: f64| {
|
||||
if value.is_finite() { channel_from_f64(value * 255.0) } else { 0 }
|
||||
};
|
||||
Rgba { r: channel(red), g: channel(green), b: channel(blue), a: channel(alpha) }
|
||||
}
|
||||
/// Relative luminance (WCAG), 0–1.
|
||||
pub fn luminance(self) -> f64 {
|
||||
let lin = |c: u8| {
|
||||
let v = c as f64 / 255.0;
|
||||
let v = f64::from(c) / 255.0;
|
||||
if v <= 0.04045 { v / 12.92 } else { ((v + 0.055) / 1.055).powf(2.4) }
|
||||
};
|
||||
0.2126 * lin(self.r) + 0.7152 * lin(self.g) + 0.0722 * lin(self.b)
|
||||
@@ -80,60 +87,60 @@ impl Rgba {
|
||||
(hi + 0.05) / (lo + 0.05)
|
||||
}
|
||||
pub fn to_hsl(self) -> (f64, f64, f64) {
|
||||
let r = self.r as f64 / 255.0;
|
||||
let g = self.g as f64 / 255.0;
|
||||
let b = self.b as f64 / 255.0;
|
||||
let max = r.max(g).max(b);
|
||||
let min = r.min(g).min(b);
|
||||
let l = (max + min) / 2.0;
|
||||
let red = f64::from(self.r) / 255.0;
|
||||
let green = f64::from(self.g) / 255.0;
|
||||
let blue = f64::from(self.b) / 255.0;
|
||||
let max = red.max(green).max(blue);
|
||||
let min = red.min(green).min(blue);
|
||||
let lightness = f64::midpoint(max, min);
|
||||
if (max - min).abs() < 1e-12 {
|
||||
return (0.0, 0.0, l);
|
||||
return (0.0, 0.0, lightness);
|
||||
}
|
||||
let d = max - min;
|
||||
let s = if l > 0.5 { d / (2.0 - max - min) } else { d / (max + min) };
|
||||
let h = if (max - r).abs() < 1e-12 {
|
||||
(g - b) / d + if g < b { 6.0 } else { 0.0 }
|
||||
} else if (max - g).abs() < 1e-12 {
|
||||
(b - r) / d + 2.0
|
||||
let delta = max - min;
|
||||
let saturation = if lightness > 0.5 { delta / (2.0 - max - min) } else { delta / (max + min) };
|
||||
let hue = if (max - red).abs() < 1e-12 {
|
||||
(green - blue) / delta + if green < blue { 6.0 } else { 0.0 }
|
||||
} else if (max - green).abs() < 1e-12 {
|
||||
(blue - red) / delta + 2.0
|
||||
} else {
|
||||
(r - g) / d + 4.0
|
||||
(red - green) / delta + 4.0
|
||||
};
|
||||
(h * 60.0, s, l)
|
||||
(hue * 60.0, saturation, lightness)
|
||||
}
|
||||
pub fn from_hsl(h: f64, s: f64, l: f64, a: u8) -> Self {
|
||||
let h = ((h % 360.0) + 360.0) % 360.0 / 360.0;
|
||||
let s = s.clamp(0.0, 1.0);
|
||||
let l = l.clamp(0.0, 1.0);
|
||||
if s == 0.0 {
|
||||
return Rgba::from_f64(l, l, l, a as f64 / 255.0);
|
||||
pub fn from_hsl(hue: f64, saturation: f64, lightness: f64, alpha: u8) -> Self {
|
||||
let hue = ((hue % 360.0) + 360.0) % 360.0 / 360.0;
|
||||
let saturation = saturation.clamp(0.0, 1.0);
|
||||
let lightness = lightness.clamp(0.0, 1.0);
|
||||
if saturation == 0.0 {
|
||||
return Rgba::from_f64(lightness, lightness, lightness, f64::from(alpha) / 255.0);
|
||||
}
|
||||
let q = if l < 0.5 { l * (1.0 + s) } else { l + s - l * s };
|
||||
let p = 2.0 * l - q;
|
||||
let f = |t: f64| {
|
||||
let t = if t < 0.0 {
|
||||
t + 1.0
|
||||
} else if t > 1.0 {
|
||||
t - 1.0
|
||||
let upper = if lightness < 0.5 { lightness * (1.0 + saturation) } else { lightness + saturation - lightness * saturation };
|
||||
let lower = 2.0 * lightness - upper;
|
||||
let channel = |position: f64| {
|
||||
let position = if position < 0.0 {
|
||||
position + 1.0
|
||||
} else if position > 1.0 {
|
||||
position - 1.0
|
||||
} else {
|
||||
t
|
||||
position
|
||||
};
|
||||
if t < 1.0 / 6.0 {
|
||||
p + (q - p) * 6.0 * t
|
||||
} else if t < 0.5 {
|
||||
q
|
||||
} else if t < 2.0 / 3.0 {
|
||||
p + (q - p) * (2.0 / 3.0 - t) * 6.0
|
||||
if position < 1.0 / 6.0 {
|
||||
lower + (upper - lower) * 6.0 * position
|
||||
} else if position < 0.5 {
|
||||
upper
|
||||
} else if position < 2.0 / 3.0 {
|
||||
lower + (upper - lower) * (2.0 / 3.0 - position) * 6.0
|
||||
} else {
|
||||
p
|
||||
lower
|
||||
}
|
||||
};
|
||||
Rgba::from_f64(f(h + 1.0 / 3.0), f(h), f(h - 1.0 / 3.0), a as f64 / 255.0)
|
||||
Rgba::from_f64(channel(hue + 1.0 / 3.0), channel(hue), channel(hue - 1.0 / 3.0), f64::from(alpha) / 255.0)
|
||||
}
|
||||
/// Linear interpolation (straight alpha).
|
||||
pub fn lerp(self, o: Rgba, t: f64) -> Rgba {
|
||||
let t = if t.is_finite() { t.clamp(0.0, 1.0) } else { 0.0 };
|
||||
let m = |a: u8, b: u8| (a as f64 + (b as f64 - a as f64) * t).round().clamp(0.0, 255.0) as u8;
|
||||
Rgba { r: m(self.r, o.r), g: m(self.g, o.g), b: m(self.b, o.b), a: m(self.a, o.a) }
|
||||
pub fn lerp(self, other: Rgba, amount: f64) -> Rgba {
|
||||
let amount = if amount.is_finite() { amount.clamp(0.0, 1.0) } else { 0.0 };
|
||||
let interpolate = |start: u8, end: u8| channel_from_f64(f64::from(start) + (f64::from(end) - f64::from(start)) * amount);
|
||||
Rgba { r: interpolate(self.r, other.r), g: interpolate(self.g, other.g), b: interpolate(self.b, other.b), a: interpolate(self.a, other.a) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,12 +302,12 @@ impl ColorScheme {
|
||||
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 {
|
||||
let index = match slot {
|
||||
SchemeSlot::Dk1 | SchemeSlot::Tx1 => 0,
|
||||
SchemeSlot::Lt1 | SchemeSlot::Bg1 => 1,
|
||||
SchemeSlot::Dk2 | SchemeSlot::Tx2 => 2,
|
||||
SchemeSlot::Lt2 | SchemeSlot::Bg2 => 3,
|
||||
SchemeSlot::Accent1 => 4,
|
||||
SchemeSlot::Accent1 | SchemeSlot::PhClr => 4,
|
||||
SchemeSlot::Accent2 => 5,
|
||||
SchemeSlot::Accent3 => 6,
|
||||
SchemeSlot::Accent4 => 7,
|
||||
@@ -308,9 +315,8 @@ impl ColorScheme {
|
||||
SchemeSlot::Accent6 => 9,
|
||||
SchemeSlot::Hlink => 10,
|
||||
SchemeSlot::FolHlink => 11,
|
||||
SchemeSlot::PhClr => 4,
|
||||
};
|
||||
self.colors.get(i).copied().unwrap_or(Rgba::BLACK)
|
||||
self.colors.get(index).copied().unwrap_or(Rgba::BLACK)
|
||||
}
|
||||
pub fn set(&mut self, slot: SchemeSlot, c: Rgba) {
|
||||
if let Some(i) = SchemeSlot::THEME.iter().position(|s| *s == slot)
|
||||
@@ -442,88 +448,88 @@ impl ColorTransform {
|
||||
|
||||
/// Apply transforms to a base colour.
|
||||
pub fn apply(base: Rgba, transforms: &[ColorTransform]) -> Rgba {
|
||||
let mut c = base;
|
||||
for t in transforms {
|
||||
c = apply_one(c, *t);
|
||||
let mut color = base;
|
||||
for transform in transforms {
|
||||
color = apply_one(color, *transform);
|
||||
}
|
||||
c
|
||||
color
|
||||
}
|
||||
|
||||
fn pct(v: i32) -> f64 {
|
||||
v as f64 / 100_000.0
|
||||
fn pct(value: i32) -> f64 {
|
||||
f64::from(value) / 100_000.0
|
||||
}
|
||||
|
||||
fn apply_one(c: Rgba, t: ColorTransform) -> Rgba {
|
||||
match t {
|
||||
fn apply_one(color: Rgba, transform: ColorTransform) -> Rgba {
|
||||
match transform {
|
||||
// tint: towards white by (1 - value); shade: towards black by (1 - value). Office applies
|
||||
// these in linear light, which keeps mid-tones from washing out.
|
||||
ColorTransform::Tint(v) => {
|
||||
let k = pct(v).clamp(0.0, 1.0);
|
||||
map_linear(c, |x| x * k + (1.0 - k))
|
||||
ColorTransform::Tint(value) => {
|
||||
let factor = pct(value).clamp(0.0, 1.0);
|
||||
map_linear(color, |channel| channel * factor + (1.0 - factor))
|
||||
}
|
||||
ColorTransform::Shade(v) => {
|
||||
let k = pct(v).clamp(0.0, 1.0);
|
||||
map_linear(c, |x| x * k)
|
||||
ColorTransform::Shade(value) => {
|
||||
let factor = pct(value).clamp(0.0, 1.0);
|
||||
map_linear(color, |channel| channel * factor)
|
||||
}
|
||||
ColorTransform::LumMod(v) => {
|
||||
let (h, s, l) = c.to_hsl();
|
||||
Rgba::from_hsl(h, s, l * pct(v), c.a)
|
||||
ColorTransform::LumMod(value) => {
|
||||
let (hue, saturation, lightness) = color.to_hsl();
|
||||
Rgba::from_hsl(hue, saturation, lightness * pct(value), color.a)
|
||||
}
|
||||
ColorTransform::LumOff(v) => {
|
||||
let (h, s, l) = c.to_hsl();
|
||||
Rgba::from_hsl(h, s, l + pct(v), c.a)
|
||||
ColorTransform::LumOff(value) => {
|
||||
let (hue, saturation, lightness) = color.to_hsl();
|
||||
Rgba::from_hsl(hue, saturation, lightness + pct(value), color.a)
|
||||
}
|
||||
ColorTransform::SatMod(v) => {
|
||||
let (h, s, l) = c.to_hsl();
|
||||
Rgba::from_hsl(h, s * pct(v), l, c.a)
|
||||
ColorTransform::SatMod(value) => {
|
||||
let (hue, saturation, lightness) = color.to_hsl();
|
||||
Rgba::from_hsl(hue, saturation * pct(value), lightness, color.a)
|
||||
}
|
||||
ColorTransform::SatOff(v) => {
|
||||
let (h, s, l) = c.to_hsl();
|
||||
Rgba::from_hsl(h, s + pct(v), l, c.a)
|
||||
ColorTransform::SatOff(value) => {
|
||||
let (hue, saturation, lightness) = color.to_hsl();
|
||||
Rgba::from_hsl(hue, saturation + pct(value), lightness, color.a)
|
||||
}
|
||||
ColorTransform::HueMod(v) => {
|
||||
let (h, s, l) = c.to_hsl();
|
||||
Rgba::from_hsl(h * pct(v), s, l, c.a)
|
||||
ColorTransform::HueMod(value) => {
|
||||
let (hue, saturation, lightness) = color.to_hsl();
|
||||
Rgba::from_hsl(hue * pct(value), saturation, lightness, color.a)
|
||||
}
|
||||
ColorTransform::HueOff(v) => {
|
||||
let (h, s, l) = c.to_hsl();
|
||||
Rgba::from_hsl(h + v as f64 / 60_000.0, s, l, c.a)
|
||||
ColorTransform::HueOff(value) => {
|
||||
let (hue, saturation, lightness) = color.to_hsl();
|
||||
Rgba::from_hsl(hue + f64::from(value) / 60_000.0, saturation, lightness, color.a)
|
||||
}
|
||||
ColorTransform::Alpha(v) => c.with_alpha((pct(v).clamp(0.0, 1.0) * 255.0).round() as u8),
|
||||
ColorTransform::AlphaMod(v) => c.with_alpha((c.a as f64 * pct(v)).round().clamp(0.0, 255.0) as u8),
|
||||
ColorTransform::AlphaOff(v) => c.with_alpha((c.a as f64 + pct(v) * 255.0).round().clamp(0.0, 255.0) as u8),
|
||||
ColorTransform::Alpha(value) => color.with_alpha(channel_from_f64(pct(value) * 255.0)),
|
||||
ColorTransform::AlphaMod(value) => color.with_alpha(channel_from_f64(f64::from(color.a) * pct(value))),
|
||||
ColorTransform::AlphaOff(value) => color.with_alpha(channel_from_f64(f64::from(color.a) + pct(value) * 255.0)),
|
||||
ColorTransform::Comp => {
|
||||
let (h, s, l) = c.to_hsl();
|
||||
Rgba::from_hsl(h + 180.0, s, l, c.a)
|
||||
let (hue, saturation, lightness) = color.to_hsl();
|
||||
Rgba::from_hsl(hue + 180.0, saturation, lightness, color.a)
|
||||
}
|
||||
ColorTransform::Inv => Rgba { r: 255 - c.r, g: 255 - c.g, b: 255 - c.b, a: c.a },
|
||||
ColorTransform::Inv => Rgba { r: 255 - color.r, g: 255 - color.g, b: 255 - color.b, a: color.a },
|
||||
ColorTransform::Gray => {
|
||||
let y = (0.299 * c.r as f64 + 0.587 * c.g as f64 + 0.114 * c.b as f64).round().clamp(0.0, 255.0) as u8;
|
||||
Rgba { r: y, g: y, b: y, a: c.a }
|
||||
let intensity = channel_from_f64(0.299 * f64::from(color.r) + 0.587 * f64::from(color.g) + 0.114 * f64::from(color.b));
|
||||
Rgba { r: intensity, g: intensity, b: intensity, a: color.a }
|
||||
}
|
||||
ColorTransform::RedMod(v) => Rgba { r: (c.r as f64 * pct(v)).round().clamp(0.0, 255.0) as u8, ..c },
|
||||
ColorTransform::GreenMod(v) => Rgba { g: (c.g as f64 * pct(v)).round().clamp(0.0, 255.0) as u8, ..c },
|
||||
ColorTransform::BlueMod(v) => Rgba { b: (c.b as f64 * pct(v)).round().clamp(0.0, 255.0) as u8, ..c },
|
||||
ColorTransform::RedMod(value) => Rgba { r: channel_from_f64(f64::from(color.r) * pct(value)), ..color },
|
||||
ColorTransform::GreenMod(value) => Rgba { g: channel_from_f64(f64::from(color.g) * pct(value)), ..color },
|
||||
ColorTransform::BlueMod(value) => Rgba { b: channel_from_f64(f64::from(color.b) * pct(value)), ..color },
|
||||
}
|
||||
}
|
||||
|
||||
fn to_lin(v: u8) -> f64 {
|
||||
let v = v as f64 / 255.0;
|
||||
if v <= 0.04045 { v / 12.92 } else { ((v + 0.055) / 1.055).powf(2.4) }
|
||||
fn to_lin(value: u8) -> f64 {
|
||||
let value = f64::from(value) / 255.0;
|
||||
if value <= 0.04045 { value / 12.92 } else { ((value + 0.055) / 1.055).powf(2.4) }
|
||||
}
|
||||
fn from_lin(v: f64) -> f64 {
|
||||
let v = v.clamp(0.0, 1.0);
|
||||
if v <= 0.003_130_8 { v * 12.92 } else { 1.055 * v.powf(1.0 / 2.4) - 0.055 }
|
||||
fn from_lin(value: f64) -> f64 {
|
||||
let value = value.clamp(0.0, 1.0);
|
||||
if value <= 0.003_130_8 { value * 12.92 } else { 1.055 * value.powf(1.0 / 2.4) - 0.055 }
|
||||
}
|
||||
fn map_linear(c: Rgba, f: impl Fn(f64) -> f64) -> Rgba {
|
||||
Rgba::from_f64(from_lin(f(to_lin(c.r))), from_lin(f(to_lin(c.g))), from_lin(f(to_lin(c.b))), c.a as f64 / 255.0)
|
||||
fn map_linear(color: Rgba, map: impl Fn(f64) -> f64) -> Rgba {
|
||||
Rgba::from_f64(from_lin(map(to_lin(color.r))), from_lin(map(to_lin(color.g))), from_lin(map(to_lin(color.b))), f64::from(color.a) / 255.0)
|
||||
}
|
||||
|
||||
/// The colour-picker grid shown under each theme colour: lighter 80/60/40%, darker 25/50%
|
||||
/// (for dark colours: lighter 90/75/50/25/10%).
|
||||
pub fn theme_variants(c: Rgba) -> [(Vec<ColorTransform>, &'static str); 5] {
|
||||
let (_, _, l) = c.to_hsl();
|
||||
if l < 0.2 {
|
||||
pub fn theme_variants(color: Rgba) -> [(Vec<ColorTransform>, &'static str); 5] {
|
||||
let (_, _, lightness) = color.to_hsl();
|
||||
if lightness < 0.2 {
|
||||
[
|
||||
(vec![ColorTransform::LumMod(10000), ColorTransform::LumOff(90000)], "Lighter 90%"),
|
||||
(vec![ColorTransform::LumMod(25000), ColorTransform::LumOff(75000)], "Lighter 75%"),
|
||||
@@ -531,7 +537,7 @@ pub fn theme_variants(c: Rgba) -> [(Vec<ColorTransform>, &'static str); 5] {
|
||||
(vec![ColorTransform::LumMod(75000), ColorTransform::LumOff(25000)], "Lighter 25%"),
|
||||
(vec![ColorTransform::LumMod(90000), ColorTransform::LumOff(10000)], "Lighter 10%"),
|
||||
]
|
||||
} else if l > 0.8 {
|
||||
} else if lightness > 0.8 {
|
||||
[
|
||||
(vec![ColorTransform::LumMod(95000)], "Darker 5%"),
|
||||
(vec![ColorTransform::LumMod(85000)], "Darker 15%"),
|
||||
@@ -1029,12 +1035,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn hsl_roundtrip() {
|
||||
for c in [Rgba::rgb(10, 200, 30), Rgba::rgb(255, 0, 0), Rgba::rgb(128, 128, 128), Rgba::rgb(21, 96, 130)] {
|
||||
let (h, s, l) = c.to_hsl();
|
||||
let d = Rgba::from_hsl(h, s, l, 255);
|
||||
for color in [Rgba::rgb(10, 200, 30), Rgba::rgb(255, 0, 0), Rgba::rgb(128, 128, 128), Rgba::rgb(21, 96, 130)] {
|
||||
let (hue, saturation, lightness) = color.to_hsl();
|
||||
let converted = Rgba::from_hsl(hue, saturation, lightness, 255);
|
||||
assert!(
|
||||
(d.r as i32 - c.r as i32).abs() <= 1 && (d.g as i32 - c.g as i32).abs() <= 1 && (d.b as i32 - c.b as i32).abs() <= 1,
|
||||
"{c:?} {d:?}"
|
||||
(i32::from(converted.r) - i32::from(color.r)).abs() <= 1
|
||||
&& (i32::from(converted.g) - i32::from(color.g)).abs() <= 1
|
||||
&& (i32::from(converted.b) - i32::from(color.b)).abs() <= 1,
|
||||
"{color:?} {converted:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,22 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::preset::FillMode;
|
||||
|
||||
/// A connection site of a custom geometry (`a:cxnLst`): where a connector end glues. `x` and `y`
|
||||
/// are fractions of the shape's box (0 = left/top, 1 = right/bottom; outside that range is
|
||||
/// allowed), so the site keeps its place when the shape is resized.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct ConnectionSite {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
}
|
||||
|
||||
impl ConnectionSite {
|
||||
pub const fn new(x: f64, y: f64) -> Self {
|
||||
ConnectionSite { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
/// One sub-path of a custom geometry: SVG-like commands in path units.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
@@ -459,11 +475,52 @@ pub fn parse_path(d: &str) -> Vec<(char, Vec<f64>)> {
|
||||
|
||||
/// Write custom paths as `a:custGeom`. Coordinates are integers in the file, so small path spaces
|
||||
/// are scaled up.
|
||||
pub fn write_cust_geom(w: &mut Writer, paths: &[CustomPath]) {
|
||||
/// The extent a shape without a transform of its own (a placeholder) is read and written with,
|
||||
/// in EMU: its sites keep their fractions through a round trip.
|
||||
pub const INHERITED_EXTENT_EMU: f64 = 21600.0;
|
||||
|
||||
/// `a:cxnLst`: the connection sites as fractions of the shape's box, in file order (connectors
|
||||
/// refer to them by index, so a `cxn` without a `pos` keeps its place as the box's corner).
|
||||
/// Positions are in the shape's own coordinate space, `w_emu` × `h_emu` (the extent, where the
|
||||
/// guides are evaluated), not in a path's `w`/`h` space.
|
||||
pub fn read_cxn_lst(cg: &El, w_emu: f64, h_emu: f64) -> Vec<ConnectionSite> {
|
||||
let Some(list) = cg.child(Local("cxnLst")) else { return vec![] };
|
||||
let mut g = Guides::new(w_emu, h_emu);
|
||||
g.eval_list(cg.child(Local("avLst")));
|
||||
g.eval_list(cg.child(Local("gdLst")));
|
||||
let frac = |v: f64, size: f64| if size > 0.0 { finite(v / size) } else { 0.0 };
|
||||
list.children(Local("cxn"))
|
||||
.take(1000)
|
||||
.map(|c| {
|
||||
c.child(Local("pos"))
|
||||
.map(|pos| {
|
||||
let (x, y) = pt(&g, pos);
|
||||
ConnectionSite::new(frac(x, w_emu), frac(y, h_emu))
|
||||
})
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Write custom paths as `a:custGeom`. Coordinates are integers in the file, so small path spaces
|
||||
/// are scaled up. `sites` go out as `a:cxnLst` in the shape's space `ext` (EMU).
|
||||
pub fn write_cust_geom(w: &mut Writer, paths: &[CustomPath], sites: &[ConnectionSite], ext: (i64, i64)) {
|
||||
w.open("a:custGeom", &[]);
|
||||
for list in ["a:avLst", "a:gdLst", "a:ahLst", "a:cxnLst"] {
|
||||
for list in ["a:avLst", "a:gdLst", "a:ahLst"] {
|
||||
w.empty(list, &[]);
|
||||
}
|
||||
if sites.is_empty() {
|
||||
w.empty("a:cxnLst", &[]);
|
||||
} else {
|
||||
w.open("a:cxnLst", &[]);
|
||||
let emu = |f: f64, size: i64| (finite(f) * size as f64).round().clamp(-2.0e9, 2.0e9) as i64;
|
||||
for s in sites.iter().take(1000) {
|
||||
w.tag("a:cxn").attr("ang", "0").open();
|
||||
w.tag("a:pos").attr("x", emu(s.x, ext.0)).attr("y", emu(s.y, ext.1)).empty();
|
||||
w.close();
|
||||
}
|
||||
w.close();
|
||||
}
|
||||
w.empty("a:rect", &[("l", "l"), ("t", "t"), ("r", "r"), ("b", "b")]);
|
||||
w.open("a:pathLst", &[]);
|
||||
for p in paths {
|
||||
@@ -607,7 +664,7 @@ mod tests {
|
||||
fn write_then_read_path() {
|
||||
let p = CustomPath { w: 100.0, h: 50.0, d: "M 0 0 L 100 0 C 100 10 90 50 50 50 Q 0 50 0 0 Z".into(), fill: FillMode::Norm, stroke: true };
|
||||
let mut w = Writer::new();
|
||||
write_cust_geom(&mut w, std::slice::from_ref(&p));
|
||||
write_cust_geom(&mut w, std::slice::from_ref(&p), &[], (0, 0));
|
||||
let d = parse(w.as_str().as_bytes()).unwrap();
|
||||
let back = read_cust_geom(&d, 1.0, 1.0);
|
||||
assert_eq!(back.len(), 1);
|
||||
@@ -705,8 +762,37 @@ mod tests {
|
||||
assert_eq!(p.d, "M 0 0 L 30 0 C 30 20 20 30 0 30 Z");
|
||||
assert_eq!(p.fill, FillMode::None);
|
||||
let mut w = Writer::new();
|
||||
write_cust_geom(&mut w, std::slice::from_ref(&p));
|
||||
write_cust_geom(&mut w, std::slice::from_ref(&p), &[], (30, 30));
|
||||
let back = read_cust_geom(&parse(w.finish().as_bytes()).unwrap(), 1.0, 1.0);
|
||||
assert_eq!(parse_path(&back[0].d).len(), 4);
|
||||
}
|
||||
|
||||
/// `a:cxnLst` positions are in the shape's space (the extent, with its guides), not the
|
||||
/// path's own `w`/`h`; they come back as fractions of the box and go out again in EMU.
|
||||
#[test]
|
||||
fn connection_sites_read_in_the_shape_space_and_write_back() {
|
||||
let x = br#"<a:custGeom xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
|
||||
<a:avLst/><a:gdLst><a:gd name="mid" fmla="*/ h 1 2"/></a:gdLst><a:ahLst/>
|
||||
<a:cxnLst><a:cxn ang="0"><a:pos x="0" y="mid"/></a:cxn><a:cxn ang="5400000"><a:pos x="r" y="mid"/></a:cxn>
|
||||
<a:cxn ang="0"/><a:cxn ang="0"><a:pos x="500" y="625"/></a:cxn></a:cxnLst>
|
||||
<a:rect l="0" t="0" r="r" b="b"/>
|
||||
<a:pathLst><a:path w="100" h="50"><a:moveTo><a:pt x="0" y="25"/></a:moveTo><a:lnTo><a:pt x="100" y="25"/></a:lnTo></a:path></a:pathLst>
|
||||
</a:custGeom>"#;
|
||||
let d = parse(x).unwrap();
|
||||
let sites = read_cxn_lst(&d, 1000.0, 500.0);
|
||||
assert_eq!(
|
||||
sites,
|
||||
vec![ConnectionSite::new(0.0, 0.5), ConnectionSite::new(1.0, 0.5), ConnectionSite::new(0.0, 0.0), ConnectionSite::new(0.5, 1.25)],
|
||||
"a cxn without a pos keeps its index (a connector may point past it)"
|
||||
);
|
||||
assert!(read_cxn_lst(&d, 0.0, 0.0).iter().all(|s| s.x == 0.0 && s.y == 0.0), "a zero extent gives no division by zero");
|
||||
let paths = read_cust_geom(&d, 1000.0, 500.0);
|
||||
let mut w = Writer::new();
|
||||
write_cust_geom(&mut w, &paths, &sites, (1000, 500));
|
||||
let out = w.finish();
|
||||
assert!(out.contains(r#"<a:cxnLst><a:cxn ang="0"><a:pos x="0" y="250"/></a:cxn><a:cxn ang="0"><a:pos x="1000" y="250"/></a:cxn><a:cxn ang="0"><a:pos x="0" y="0"/></a:cxn><a:cxn ang="0"><a:pos x="500" y="625"/></a:cxn></a:cxnLst>"#), "{out}");
|
||||
let mut w = Writer::new();
|
||||
write_cust_geom(&mut w, &paths, &[], (1000, 500));
|
||||
assert!(w.finish().contains("<a:cxnLst/>"), "no sites: the empty list as before");
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ pub mod diagram;
|
||||
pub mod numfmt;
|
||||
pub mod part;
|
||||
pub mod preset;
|
||||
mod preset_text;
|
||||
pub mod read;
|
||||
pub mod style;
|
||||
pub mod table;
|
||||
|
||||
@@ -16,7 +16,7 @@ mod shapes;
|
||||
|
||||
pub use build::build;
|
||||
use builder::{Corner, Dir, Pb, corner_rect, fill, fill_eo, fill_only, mid_sites, orient, shade, simple, stroke_only, vis};
|
||||
pub use catalog::{CATALOG, Category, FillMode, Geometry, Handle, PresetInfo, SubPath, info, is_line_like, mitred_by_default};
|
||||
pub use catalog::{CATALOG, Category, FillMode, Geometry, Handle, PresetInfo, SubPath, adjust_defaults, info, is_line_like, mitred_by_default};
|
||||
|
||||
mod extra;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::builder::{
|
||||
Corner, Dir, Pb, arrow, corner_rect, double_arrow, ellipse_sites, fill, fill_eo, fill_only, len_thick, mid_sites, orient, polygon_regular, shade,
|
||||
simple, spec_pts, star, star_text_rect, stroke_only, tip_handle, vis,
|
||||
};
|
||||
use super::catalog::{FillMode, Geometry, Handle, info};
|
||||
use super::catalog::{FillMode, Geometry, Handle};
|
||||
use super::shapes::{action_button, cloud, math, ribbon, scroll, wedge_rect};
|
||||
use super::{Arc, BezPath, Point, Rect, Vec2, extra};
|
||||
|
||||
@@ -26,17 +26,26 @@ impl Ctx<'_> {
|
||||
/// Builds the geometry for preset `name` at size `w`×`h` with adjust values `adj` (missing
|
||||
/// values use the preset's defaults). Unknown names return `None`; the caller falls back to `rect`.
|
||||
pub fn build(name: &str, w: f64, h: f64, adj: &[f64]) -> Option<Geometry> {
|
||||
let mut g = build_outline(name, w, h, adj)?;
|
||||
// Text goes where presetShapeDefinitions put it, from the standard's own guide formulas.
|
||||
if let Some(t) = crate::preset_text::text_rect(name, w.max(0.0), h.max(0.0), adj) {
|
||||
g.text_rect = t;
|
||||
}
|
||||
Some(g)
|
||||
}
|
||||
|
||||
fn build_outline(name: &str, w: f64, h: f64, adj: &[f64]) -> Option<Geometry> {
|
||||
if !(w.is_finite() && h.is_finite()) {
|
||||
return None;
|
||||
}
|
||||
let defaults = info(name).map(|i| i.defaults).unwrap_or(&[]);
|
||||
let defaults = super::catalog::adjust_defaults(name);
|
||||
let c = Ctx { name, w: w.max(0.0), h: h.max(0.0), adj, defaults };
|
||||
rect_and_triangle_shapes(&c)
|
||||
.or_else(|| polygon_and_symbol_shapes(&c))
|
||||
.or_else(|| bracket_and_arrow_shapes(&c))
|
||||
.or_else(|| flowchart_and_star_shapes(&c))
|
||||
.or_else(|| callout_wave_and_connector_shapes(&c))
|
||||
.or_else(|| extra::build(name, c.w, c.h, &|i| c.a(i)))
|
||||
.or_else(|| extra::Name::from_str(name).map(|n| extra::build(n, c.w, c.h, &|i| c.a(i))))
|
||||
}
|
||||
|
||||
fn rect_and_triangle_shapes(c: &Ctx) -> Option<Geometry> {
|
||||
@@ -1186,16 +1195,60 @@ fn callout_wave_and_connector_shapes(c: &Ctx) -> Option<Geometry> {
|
||||
let l = Pb::new().m(0.0, 0.0).l(w, 0.0).l(w, h).take();
|
||||
Geometry { paths: vec![stroke_only(l)], text_rect: full, handles: vec![], sites: vec![Point::ZERO, Point::new(w, h)] }
|
||||
}
|
||||
"bentConnector3" | "bentConnector4" | "bentConnector5" => {
|
||||
let x = w * a(0) / 1e5;
|
||||
let l = Pb::new().m(0.0, 0.0).l(x, 0.0).l(x, h).l(w, h).take();
|
||||
// The elbows of ECMA-376's presetShapeDefinitions: 3 has one middle leg, 4 adds a
|
||||
// horizontal one at `adj2`, 5 a third vertical one at `adj3`. 4 and 5 were drawn as 3.
|
||||
"bentConnector3" => {
|
||||
let x1 = w * a(0) / 1e5;
|
||||
let l = Pb::new().m(0.0, 0.0).l(x1, 0.0).l(x1, h).l(w, h).take();
|
||||
let mut g = Geometry { paths: vec![stroke_only(l)], text_rect: full, handles: vec![], sites: vec![Point::ZERO, Point::new(w, h)] };
|
||||
g.handles.push(Handle::new(0, Point::new(0.0, h / 2.0), Vec2::new(w / 1e5, 0.0), a(0), -1e6, 1e6));
|
||||
g
|
||||
}
|
||||
"curvedConnector2" | "curvedConnector3" | "curvedConnector4" | "curvedConnector5" => {
|
||||
let x = w * if name == "curvedConnector2" { 100000.0 } else { a(0) } / 1e5;
|
||||
let l = Pb::new().m(0.0, 0.0).c(x / 2.0, 0.0, x, h / 4.0, x, h / 2.0).c(x, h * 0.75, (x + w) / 2.0, h, w, h).take();
|
||||
"bentConnector4" => {
|
||||
let (x1, y2) = (w * a(0) / 1e5, h * a(1) / 1e5);
|
||||
let l = Pb::new().m(0.0, 0.0).l(x1, 0.0).l(x1, y2).l(w, y2).l(w, h).take();
|
||||
let mut g = Geometry { paths: vec![stroke_only(l)], text_rect: full, handles: vec![], sites: vec![Point::ZERO, Point::new(w, h)] };
|
||||
g.handles.push(Handle::new(0, Point::new(0.0, y2 / 2.0), Vec2::new(w / 1e5, 0.0), a(0), -1e6, 1e6));
|
||||
g.handles.push(Handle::new(1, Point::new((x1 + w) / 2.0, 0.0), Vec2::new(0.0, h / 1e5), a(1), -1e6, 1e6));
|
||||
g
|
||||
}
|
||||
"bentConnector5" => {
|
||||
let (x1, y2, x3) = (w * a(0) / 1e5, h * a(1) / 1e5, w * a(2) / 1e5);
|
||||
let l = Pb::new().m(0.0, 0.0).l(x1, 0.0).l(x1, y2).l(x3, y2).l(x3, h).l(w, h).take();
|
||||
let mut g = Geometry { paths: vec![stroke_only(l)], text_rect: full, handles: vec![], sites: vec![Point::ZERO, Point::new(w, h)] };
|
||||
g.handles.push(Handle::new(0, Point::new(0.0, y2 / 2.0), Vec2::new(w / 1e5, 0.0), a(0), -1e6, 1e6));
|
||||
g.handles.push(Handle::new(1, Point::new((x1 + x3) / 2.0, 0.0), Vec2::new(0.0, h / 1e5), a(1), -1e6, 1e6));
|
||||
g.handles.push(Handle::new(2, Point::new(0.0, (y2 + h) / 2.0), Vec2::new(w / 1e5, 0.0), a(2), -1e6, 1e6));
|
||||
g
|
||||
}
|
||||
// The curved connectors follow the same legs with cubic Béziers.
|
||||
"curvedConnector2" => {
|
||||
let l = Pb::new().m(0.0, 0.0).c(w / 2.0, 0.0, w, h / 2.0, w, h).take();
|
||||
Geometry { paths: vec![stroke_only(l)], text_rect: full, handles: vec![], sites: vec![Point::ZERO, Point::new(w, h)] }
|
||||
}
|
||||
"curvedConnector3" => {
|
||||
let x2 = w * a(0) / 1e5;
|
||||
let l = Pb::new().m(0.0, 0.0).c(x2 / 2.0, 0.0, x2, h / 4.0, x2, h / 2.0).c(x2, h * 0.75, (x2 + w) / 2.0, h, w, h).take();
|
||||
Geometry { paths: vec![stroke_only(l)], text_rect: full, handles: vec![], sites: vec![Point::ZERO, Point::new(w, h)] }
|
||||
}
|
||||
"curvedConnector4" => {
|
||||
let (x2, y4) = (w * a(0) / 1e5, h * a(1) / 1e5);
|
||||
let (x1, x3) = (x2 / 2.0, (w + x2) / 2.0);
|
||||
let (x4, x5) = ((x2 + x3) / 2.0, (x3 + w) / 2.0);
|
||||
let y1 = y4 / 2.0;
|
||||
let (y2, y3, y5) = (y1 / 2.0, (y1 + y4) / 2.0, (h + y4) / 2.0);
|
||||
let l = Pb::new().m(0.0, 0.0).c(x1, 0.0, x2, y2, x2, y1).c(x2, y3, x4, y4, x3, y4).c(x5, y4, w, y5, w, h).take();
|
||||
Geometry { paths: vec![stroke_only(l)], text_rect: full, handles: vec![], sites: vec![Point::ZERO, Point::new(w, h)] }
|
||||
}
|
||||
"curvedConnector5" => {
|
||||
let (x3, y4, x6) = (w * a(0) / 1e5, h * a(1) / 1e5, w * a(2) / 1e5);
|
||||
let x1 = (x3 + x6) / 2.0;
|
||||
let (x2, x4, x5, x7) = (x3 / 2.0, (x3 + x1) / 2.0, (x6 + x1) / 2.0, (x6 + w) / 2.0);
|
||||
let y1 = y4 / 2.0;
|
||||
let (y2, y3, y5) = (y1 / 2.0, (y1 + y4) / 2.0, (h + y4) / 2.0);
|
||||
let (y6, y7) = ((y5 + y4) / 2.0, (y5 + h) / 2.0);
|
||||
let l =
|
||||
Pb::new().m(0.0, 0.0).c(x2, 0.0, x3, y2, x3, y1).c(x3, y3, x4, y4, x1, y4).c(x5, y4, x6, y6, x6, y5).c(x6, y7, x7, h, w, h).take();
|
||||
Geometry { paths: vec![stroke_only(l)], text_rect: full, handles: vec![], sites: vec![Point::ZERO, Point::new(w, h)] }
|
||||
}
|
||||
_ => return None,
|
||||
|
||||
@@ -345,6 +345,22 @@ pub fn info(name: &str) -> Option<&'static PresetInfo> {
|
||||
CATALOG.iter().find(|p| p.name == name)
|
||||
}
|
||||
|
||||
/// Adjust-value defaults of presets we draw but don't offer in the gallery: PowerPoint routes
|
||||
/// connectors with them, and a file may give some of the values and rely on these for the rest
|
||||
/// (ECMA-376 presetShapeDefinitions).
|
||||
static UNLISTED_DEFAULTS: &[(&str, &[f64])] = &[
|
||||
("bentConnector4", &[50000.0, 50000.0]),
|
||||
("bentConnector5", &[50000.0, 50000.0, 50000.0]),
|
||||
("curvedConnector4", &[50000.0, 50000.0]),
|
||||
("curvedConnector5", &[50000.0, 50000.0, 50000.0]),
|
||||
];
|
||||
|
||||
/// The adjust values preset `name` takes when a file or the user gives none: its gallery entry's,
|
||||
/// or for a preset outside the gallery the spec's. Empty for a preset without adjust values.
|
||||
pub fn adjust_defaults(name: &str) -> &'static [f64] {
|
||||
info(name).map(|i| i.defaults).or_else(|| UNLISTED_DEFAULTS.iter().find(|(n, _)| *n == name).map(|(_, d)| *d)).unwrap_or(&[])
|
||||
}
|
||||
|
||||
/// Whether PowerPoint joins this preset's outline with sharp (miter) corners when the line names
|
||||
/// no join (it rounds them otherwise). Observed per preset on its export: the plus, the right,
|
||||
/// up and down block arrows (not the left one), chevrons, stars from five points up, the
|
||||
|
||||
@@ -7,17 +7,154 @@ use super::{
|
||||
stroke_only, vis,
|
||||
};
|
||||
|
||||
/// Builds the presets this module knows; `None` for any other name.
|
||||
pub(super) fn build(name: &str, w: f64, h: f64, a: &dyn Fn(usize) -> f64) -> Option<Geometry> {
|
||||
/// The preset outlines this module builds, named for their Office Open XML identifiers.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(super) enum Name {
|
||||
LineInv,
|
||||
NonIsoscelesTrapezoid,
|
||||
PieWedge,
|
||||
FlowChartOfflineStorage,
|
||||
ChartX,
|
||||
ChartStar,
|
||||
ChartPlus,
|
||||
CornerTabs,
|
||||
SquareTabs,
|
||||
PlaqueTabs,
|
||||
Callout1,
|
||||
Callout2,
|
||||
Callout3,
|
||||
AccentCallout1,
|
||||
AccentCallout2,
|
||||
AccentCallout3,
|
||||
BorderCallout2,
|
||||
BorderCallout3,
|
||||
AccentBorderCallout1,
|
||||
AccentBorderCallout2,
|
||||
AccentBorderCallout3,
|
||||
LeftRightArrowCallout,
|
||||
UpDownArrowCallout,
|
||||
QuadArrowCallout,
|
||||
CurvedRightArrow,
|
||||
CurvedLeftArrow,
|
||||
CurvedUpArrow,
|
||||
CurvedDownArrow,
|
||||
SwooshArrow,
|
||||
EllipseRibbon,
|
||||
EllipseRibbon2,
|
||||
LeftRightRibbon,
|
||||
Gear6,
|
||||
Gear9,
|
||||
Funnel,
|
||||
CircularArrow,
|
||||
LeftCircularArrow,
|
||||
LeftRightCircularArrow,
|
||||
}
|
||||
|
||||
impl Name {
|
||||
/// The Office Open XML identifier (as it appears in files and the catalog). Only the tests
|
||||
/// need the reverse mapping; production code parses strings with [`Name::from_str`].
|
||||
#[cfg(test)]
|
||||
pub(super) fn ooxml(self) -> &'static str {
|
||||
match self {
|
||||
Name::LineInv => "lineInv",
|
||||
Name::NonIsoscelesTrapezoid => "nonIsoscelesTrapezoid",
|
||||
Name::PieWedge => "pieWedge",
|
||||
Name::FlowChartOfflineStorage => "flowChartOfflineStorage",
|
||||
Name::ChartX => "chartX",
|
||||
Name::ChartStar => "chartStar",
|
||||
Name::ChartPlus => "chartPlus",
|
||||
Name::CornerTabs => "cornerTabs",
|
||||
Name::SquareTabs => "squareTabs",
|
||||
Name::PlaqueTabs => "plaqueTabs",
|
||||
Name::Callout1 => "callout1",
|
||||
Name::Callout2 => "callout2",
|
||||
Name::Callout3 => "callout3",
|
||||
Name::AccentCallout1 => "accentCallout1",
|
||||
Name::AccentCallout2 => "accentCallout2",
|
||||
Name::AccentCallout3 => "accentCallout3",
|
||||
Name::BorderCallout2 => "borderCallout2",
|
||||
Name::BorderCallout3 => "borderCallout3",
|
||||
Name::AccentBorderCallout1 => "accentBorderCallout1",
|
||||
Name::AccentBorderCallout2 => "accentBorderCallout2",
|
||||
Name::AccentBorderCallout3 => "accentBorderCallout3",
|
||||
Name::LeftRightArrowCallout => "leftRightArrowCallout",
|
||||
Name::UpDownArrowCallout => "upDownArrowCallout",
|
||||
Name::QuadArrowCallout => "quadArrowCallout",
|
||||
Name::CurvedRightArrow => "curvedRightArrow",
|
||||
Name::CurvedLeftArrow => "curvedLeftArrow",
|
||||
Name::CurvedUpArrow => "curvedUpArrow",
|
||||
Name::CurvedDownArrow => "curvedDownArrow",
|
||||
Name::SwooshArrow => "swooshArrow",
|
||||
Name::EllipseRibbon => "ellipseRibbon",
|
||||
Name::EllipseRibbon2 => "ellipseRibbon2",
|
||||
Name::LeftRightRibbon => "leftRightRibbon",
|
||||
Name::Gear6 => "gear6",
|
||||
Name::Gear9 => "gear9",
|
||||
Name::Funnel => "funnel",
|
||||
Name::CircularArrow => "circularArrow",
|
||||
Name::LeftCircularArrow => "leftCircularArrow",
|
||||
Name::LeftRightCircularArrow => "leftRightCircularArrow",
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an Office Open XML identifier into its variant; `None` if unknown.
|
||||
pub(super) fn from_str(name: &str) -> Option<Self> {
|
||||
let e = match name {
|
||||
"lineInv" => Name::LineInv,
|
||||
"nonIsoscelesTrapezoid" => Name::NonIsoscelesTrapezoid,
|
||||
"pieWedge" => Name::PieWedge,
|
||||
"flowChartOfflineStorage" => Name::FlowChartOfflineStorage,
|
||||
"chartX" => Name::ChartX,
|
||||
"chartStar" => Name::ChartStar,
|
||||
"chartPlus" => Name::ChartPlus,
|
||||
"cornerTabs" => Name::CornerTabs,
|
||||
"squareTabs" => Name::SquareTabs,
|
||||
"plaqueTabs" => Name::PlaqueTabs,
|
||||
"callout1" => Name::Callout1,
|
||||
"callout2" => Name::Callout2,
|
||||
"callout3" => Name::Callout3,
|
||||
"accentCallout1" => Name::AccentCallout1,
|
||||
"accentCallout2" => Name::AccentCallout2,
|
||||
"accentCallout3" => Name::AccentCallout3,
|
||||
"borderCallout2" => Name::BorderCallout2,
|
||||
"borderCallout3" => Name::BorderCallout3,
|
||||
"accentBorderCallout1" => Name::AccentBorderCallout1,
|
||||
"accentBorderCallout2" => Name::AccentBorderCallout2,
|
||||
"accentBorderCallout3" => Name::AccentBorderCallout3,
|
||||
"leftRightArrowCallout" => Name::LeftRightArrowCallout,
|
||||
"upDownArrowCallout" => Name::UpDownArrowCallout,
|
||||
"quadArrowCallout" => Name::QuadArrowCallout,
|
||||
"curvedRightArrow" => Name::CurvedRightArrow,
|
||||
"curvedLeftArrow" => Name::CurvedLeftArrow,
|
||||
"curvedUpArrow" => Name::CurvedUpArrow,
|
||||
"curvedDownArrow" => Name::CurvedDownArrow,
|
||||
"swooshArrow" => Name::SwooshArrow,
|
||||
"ellipseRibbon" => Name::EllipseRibbon,
|
||||
"ellipseRibbon2" => Name::EllipseRibbon2,
|
||||
"leftRightRibbon" => Name::LeftRightRibbon,
|
||||
"gear6" => Name::Gear6,
|
||||
"gear9" => Name::Gear9,
|
||||
"funnel" => Name::Funnel,
|
||||
"circularArrow" => Name::CircularArrow,
|
||||
"leftCircularArrow" => Name::LeftCircularArrow,
|
||||
"leftRightCircularArrow" => Name::LeftRightCircularArrow,
|
||||
_ => return None,
|
||||
};
|
||||
Some(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the geometry for a preset in this module.
|
||||
pub(super) fn build(name: Name, w: f64, h: f64, a: &dyn Fn(usize) -> f64) -> Geometry {
|
||||
let ss = w.min(h);
|
||||
let full = Rect::new(0.0, 0.0, w, h);
|
||||
let mut b = Pb::new();
|
||||
let g = match name {
|
||||
"lineInv" => {
|
||||
match name {
|
||||
Name::LineInv => {
|
||||
let l = b.m(0.0, h).l(w, 0.0).take();
|
||||
Geometry { paths: vec![stroke_only(l)], text_rect: full, handles: vec![], sites: vec![Point::new(0.0, h), Point::new(w, 0.0)] }
|
||||
}
|
||||
"nonIsoscelesTrapezoid" => {
|
||||
Name::NonIsoscelesTrapezoid => {
|
||||
let max = 50000.0 * w / ss.max(1e-9);
|
||||
let (v1, v2) = (a(0).clamp(0.0, max), a(1).clamp(0.0, max));
|
||||
let (d1, d2) = (ss * v1 / 1e5, ss * v2 / 1e5);
|
||||
@@ -26,11 +163,11 @@ pub(super) fn build(name: &str, w: f64, h: f64, a: &dyn Fn(usize) -> f64) -> Opt
|
||||
g.handles.push(Handle::new(1, Point::new(w, 0.0), Vec2::new(-ss / 1e5, 0.0), v2, 0.0, max));
|
||||
g
|
||||
}
|
||||
"pieWedge" => {
|
||||
Name::PieWedge => {
|
||||
b.m(0.0, h).arc(w, h, 180.0, 90.0).l(w, h).z();
|
||||
simple(b.take(), Rect::new(w * 0.29289, h * 0.29289, w, h), w, h)
|
||||
}
|
||||
"flowChartOfflineStorage" => {
|
||||
Name::FlowChartOfflineStorage => {
|
||||
let tri = b.poly(&[(0.0, 0.0), (w, 0.0), (w / 2.0, h)]).take();
|
||||
let bar = Pb::new().m(w * 0.4, h * 0.8).l(w * 0.6, h * 0.8).take();
|
||||
Geometry {
|
||||
@@ -40,37 +177,37 @@ pub(super) fn build(name: &str, w: f64, h: f64, a: &dyn Fn(usize) -> f64) -> Opt
|
||||
sites: vec![Point::new(w / 2.0, 0.0), Point::new(w / 4.0, h / 2.0), Point::new(w / 2.0, h), Point::new(w * 0.75, h / 2.0)],
|
||||
}
|
||||
}
|
||||
"chartX" | "chartStar" | "chartPlus" => {
|
||||
Name::ChartX | Name::ChartStar | Name::ChartPlus => {
|
||||
let body = corner_rect(w, h, [Corner::Square; 4]);
|
||||
let mut l = Pb::new();
|
||||
if name != "chartPlus" {
|
||||
if name != Name::ChartPlus {
|
||||
l.m(0.0, 0.0).l(w, h).m(0.0, h).l(w, 0.0);
|
||||
}
|
||||
if name != "chartX" {
|
||||
if name != Name::ChartX {
|
||||
l.m(w / 2.0, 0.0).l(w / 2.0, h);
|
||||
}
|
||||
if name == "chartPlus" {
|
||||
if name == Name::ChartPlus {
|
||||
l.m(0.0, h / 2.0).l(w, h / 2.0);
|
||||
}
|
||||
Geometry { paths: vec![fill_only(body), stroke_only(l.take())], text_rect: full, handles: vec![], sites: mid_sites(w, h) }
|
||||
}
|
||||
"cornerTabs" | "squareTabs" | "plaqueTabs" => {
|
||||
Name::CornerTabs | Name::SquareTabs | Name::PlaqueTabs => {
|
||||
// Each tab's size is a twentieth of the diagonal.
|
||||
let d = (w.hypot(h) / 20.0).min(w / 2.0).min(h / 2.0);
|
||||
match name {
|
||||
"cornerTabs" => {
|
||||
Name::CornerTabs => {
|
||||
b.poly(&[(0.0, 0.0), (d, 0.0), (0.0, d)]);
|
||||
b.poly(&[(w - d, 0.0), (w, 0.0), (w, d)]);
|
||||
b.poly(&[(w, h - d), (w, h), (w - d, h)]);
|
||||
b.poly(&[(0.0, h - d), (d, h), (0.0, h)]);
|
||||
}
|
||||
"squareTabs" => {
|
||||
Name::SquareTabs => {
|
||||
b.poly(&[(0.0, 0.0), (d, 0.0), (d, d), (0.0, d)]);
|
||||
b.poly(&[(w - d, 0.0), (w, 0.0), (w, d), (w - d, d)]);
|
||||
b.poly(&[(w - d, h - d), (w, h - d), (w, h), (w - d, h)]);
|
||||
b.poly(&[(0.0, h - d), (d, h - d), (d, h), (0.0, h)]);
|
||||
}
|
||||
_ => {
|
||||
Name::PlaqueTabs => {
|
||||
if d > 0.0 {
|
||||
b.m(0.0, 0.0).l(d, 0.0).arc(d, d, 0.0, 90.0).z();
|
||||
b.m(w, 0.0).l(w, d).arc(d, d, 90.0, 90.0).z();
|
||||
@@ -80,29 +217,53 @@ pub(super) fn build(name: &str, w: f64, h: f64, a: &dyn Fn(usize) -> f64) -> Opt
|
||||
b.m(0.0, 0.0).z();
|
||||
}
|
||||
}
|
||||
// The outer match only reaches this arm for the three tab names above.
|
||||
_ => {}
|
||||
}
|
||||
Geometry { paths: vec![fill(b.take())], text_rect: full.inset(-d), handles: vec![], sites: mid_sites(w, h) }
|
||||
}
|
||||
"callout1"
|
||||
| "callout2"
|
||||
| "callout3"
|
||||
| "accentCallout1"
|
||||
| "accentCallout2"
|
||||
| "accentCallout3"
|
||||
| "borderCallout2"
|
||||
| "borderCallout3"
|
||||
| "accentBorderCallout1"
|
||||
| "accentBorderCallout2"
|
||||
| "accentBorderCallout3" => {
|
||||
let n = match name.chars().last() {
|
||||
Some('2') => 2,
|
||||
Some('3') => 3,
|
||||
Name::Callout1
|
||||
| Name::Callout2
|
||||
| Name::Callout3
|
||||
| Name::AccentCallout1
|
||||
| Name::AccentCallout2
|
||||
| Name::AccentCallout3
|
||||
| Name::BorderCallout2
|
||||
| Name::BorderCallout3
|
||||
| Name::AccentBorderCallout1
|
||||
| Name::AccentBorderCallout2
|
||||
| Name::AccentBorderCallout3 => {
|
||||
let n = match name {
|
||||
Name::Callout2 | Name::AccentCallout2 | Name::BorderCallout2 | Name::AccentBorderCallout2 => 2,
|
||||
Name::Callout3 | Name::AccentCallout3 | Name::BorderCallout3 | Name::AccentBorderCallout3 => 3,
|
||||
_ => 1,
|
||||
};
|
||||
line_callout(w, h, a, n + 1, name.starts_with("accent"), name.contains("order"))
|
||||
line_callout(
|
||||
w,
|
||||
h,
|
||||
a,
|
||||
n + 1,
|
||||
matches!(
|
||||
name,
|
||||
Name::AccentCallout1
|
||||
| Name::AccentCallout2
|
||||
| Name::AccentCallout3
|
||||
| Name::AccentBorderCallout1
|
||||
| Name::AccentBorderCallout2
|
||||
| Name::AccentBorderCallout3
|
||||
),
|
||||
matches!(
|
||||
name,
|
||||
Name::BorderCallout2
|
||||
| Name::BorderCallout3
|
||||
| Name::AccentBorderCallout1
|
||||
| Name::AccentBorderCallout2
|
||||
| Name::AccentBorderCallout3
|
||||
),
|
||||
)
|
||||
}
|
||||
"leftRightArrowCallout" | "upDownArrowCallout" => {
|
||||
let vertical = name == "upDownArrowCallout";
|
||||
Name::LeftRightArrowCallout | Name::UpDownArrowCallout => {
|
||||
let vertical = name == Name::UpDownArrowCallout;
|
||||
let (l, t) = if vertical { (h, w) } else { (w, h) };
|
||||
let ss = l.min(t);
|
||||
let head = (ss * a(1).max(0.0) / 1e5).min(t / 2.0);
|
||||
@@ -135,7 +296,7 @@ pub(super) fn build(name: &str, w: f64, h: f64, a: &dyn Fn(usize) -> f64) -> Opt
|
||||
let tr = orient(d, w, h, &[(b0, 0.0), (b1, t)]);
|
||||
simple(b.poly(&pts).take(), Rect::from_points(Point::new(tr[0].0, tr[0].1), Point::new(tr[1].0, tr[1].1)).abs(), w, h)
|
||||
}
|
||||
"quadArrowCallout" => {
|
||||
Name::QuadArrowCallout => {
|
||||
let head = (ss * a(1).max(0.0) / 1e5).min(ss / 2.0);
|
||||
let shaft = (ss * a(0).max(0.0) / 1e5).min(head * 2.0);
|
||||
let hl = (ss * a(2).max(0.0) / 1e5).min(ss / 2.0);
|
||||
@@ -180,16 +341,14 @@ pub(super) fn build(name: &str, w: f64, h: f64, a: &dyn Fn(usize) -> f64) -> Opt
|
||||
];
|
||||
simple(b.poly(&pts).take(), Rect::new(bx0, by0, bx1, by1), w, h)
|
||||
}
|
||||
"curvedRightArrow" | "curvedLeftArrow" | "curvedUpArrow" | "curvedDownArrow" => curved_arrow(name, w, h, a(0), a(1), a(2)),
|
||||
"swooshArrow" => swoosh(w, h, a(0), a(1)),
|
||||
"ellipseRibbon" | "ellipseRibbon2" => ellipse_ribbon(name == "ellipseRibbon2", w, h, a(0), a(1), a(2)),
|
||||
"leftRightRibbon" => left_right_ribbon(w, h, a(0), a(1), a(2)),
|
||||
"gear6" | "gear9" => gear(if name == "gear6" { 6 } else { 9 }, w, h, a(0), a(1)),
|
||||
"funnel" => funnel(w, h),
|
||||
"circularArrow" | "leftCircularArrow" | "leftRightCircularArrow" => circular_arrow(name, w, h, [a(0), a(1), a(2), a(3), a(4)]),
|
||||
_ => return None,
|
||||
};
|
||||
Some(g)
|
||||
Name::CurvedRightArrow | Name::CurvedLeftArrow | Name::CurvedUpArrow | Name::CurvedDownArrow => curved_arrow(name, w, h, a(0), a(1), a(2)),
|
||||
Name::SwooshArrow => swoosh(w, h, a(0), a(1)),
|
||||
Name::EllipseRibbon | Name::EllipseRibbon2 => ellipse_ribbon(name == Name::EllipseRibbon2, w, h, a(0), a(1), a(2)),
|
||||
Name::LeftRightRibbon => left_right_ribbon(w, h, a(0), a(1), a(2)),
|
||||
Name::Gear6 | Name::Gear9 => gear(if name == Name::Gear6 { 6 } else { 9 }, w, h, a(0), a(1)),
|
||||
Name::Funnel => funnel(w, h),
|
||||
Name::CircularArrow | Name::LeftCircularArrow | Name::LeftRightCircularArrow => circular_arrow(name, w, h, [a(0), a(1), a(2), a(3), a(4)]),
|
||||
}
|
||||
}
|
||||
|
||||
/// An ellipse-centred frame for the circular arrows: the band's middle runs on the ellipse
|
||||
@@ -261,7 +420,7 @@ fn turn(a: f64, b: f64, dir: f64) -> f64 {
|
||||
/// The circular arrows: a band along an ellipse with one head (`circularArrow` clockwise,
|
||||
/// `leftCircularArrow` anticlockwise) or a head at each end (`leftRightCircularArrow`).
|
||||
/// Adjusts: band thickness, head sweep (degrees), end angle, start angle, head half-width.
|
||||
fn circular_arrow(name: &str, w: f64, h: f64, a: [f64; 5]) -> Geometry {
|
||||
fn circular_arrow(name: Name, w: f64, h: f64, a: [f64; 5]) -> Geometry {
|
||||
let ss = w.min(h);
|
||||
if ss <= 0.0 {
|
||||
return simple(corner_rect(w, h, [Corner::Square; 4]), Rect::new(0.0, 0.0, w, h), w, h);
|
||||
@@ -283,7 +442,7 @@ fn circular_arrow(name: &str, w: f64, h: f64, a: [f64; 5]) -> Geometry {
|
||||
};
|
||||
let outer_arc = |b: &mut Pb, from: Point, to: Point, dir: f64| edge_arc(b, from, to, half, dir);
|
||||
let inner_arc = |b: &mut Pb, from: Point, to: Point, dir: f64| edge_arc(b, from, to, -half, dir);
|
||||
if name == "leftRightCircularArrow" {
|
||||
if name == Name::LeftRightCircularArrow {
|
||||
let hs = ring.head(start, -sweep);
|
||||
let he = ring.head(end, sweep);
|
||||
b.m(hs[0].x, hs[0].y);
|
||||
@@ -292,7 +451,7 @@ fn circular_arrow(name: &str, w: f64, h: f64, a: [f64; 5]) -> Geometry {
|
||||
inner_arc(&mut b, he[4], hs[4], -1.0);
|
||||
b.l(hs[3].x, hs[3].y).l(hs[2].x, hs[2].y).l(hs[1].x, hs[1].y).z();
|
||||
} else {
|
||||
let dir = if name == "circularArrow" { 1.0 } else { -1.0 };
|
||||
let dir = if name == Name::CircularArrow { 1.0 } else { -1.0 };
|
||||
let he = ring.head(end, sweep);
|
||||
let (o0, i0) = (ring.at(start, half), ring.at(start, -half));
|
||||
b.m(o0.x, o0.y);
|
||||
@@ -488,8 +647,8 @@ fn swoosh(w: f64, h: f64, a0: f64, a1: f64) -> Geometry {
|
||||
/// head side's edge, its horizontal radius is the full length, and the band has a constant
|
||||
/// vertical thickness. The back half (shaded) starts flat against the head side; the front half
|
||||
/// runs to the head, centred on the band where it ends.
|
||||
fn curved_arrow(name: &str, w: f64, h: f64, a0: f64, a1: f64, a2: f64) -> Geometry {
|
||||
let along_x = matches!(name, "curvedRightArrow" | "curvedLeftArrow");
|
||||
fn curved_arrow(name: Name, w: f64, h: f64, a0: f64, a1: f64, a2: f64) -> Geometry {
|
||||
let along_x = matches!(name, Name::CurvedRightArrow | Name::CurvedLeftArrow);
|
||||
let (cw, ch) = if along_x { (w, h) } else { (h, w) };
|
||||
let ss = cw.min(ch);
|
||||
let head = (ss * a1.max(0.0) / 1e5).min(ch / 2.0);
|
||||
@@ -509,9 +668,11 @@ fn curved_arrow(name: &str, w: f64, h: f64, a0: f64, a1: f64, a2: f64) -> Geomet
|
||||
let yc = yb + th / 2.0;
|
||||
front.l(cw - hl, yc - hh).l(cw, 2.0 * r + th / 2.0).l(cw - hl, yc + hh).l(cw - hl, yb + th).arc(cw, r, te, 180.0 - te).z();
|
||||
let m = match name {
|
||||
"curvedLeftArrow" => kurbo::Affine::new([-1.0, 0.0, 0.0, 1.0, w, 0.0]),
|
||||
"curvedUpArrow" => kurbo::Affine::new([0.0, -1.0, 1.0, 0.0, 0.0, h]),
|
||||
"curvedDownArrow" => kurbo::Affine::new([0.0, 1.0, 1.0, 0.0, 0.0, 0.0]),
|
||||
Name::CurvedLeftArrow => kurbo::Affine::new([-1.0, 0.0, 0.0, 1.0, w, 0.0]),
|
||||
Name::CurvedUpArrow => kurbo::Affine::new([0.0, -1.0, 1.0, 0.0, 0.0, h]),
|
||||
Name::CurvedDownArrow => kurbo::Affine::new([0.0, 1.0, 1.0, 0.0, 0.0, 0.0]),
|
||||
// `curved_arrow` is only ever called with the four curved-arrow names; anything else
|
||||
// falls back to the unrotated (right-pointing) construction.
|
||||
_ => kurbo::Affine::IDENTITY,
|
||||
};
|
||||
let (mut back, mut front) = (back.take(), front.take());
|
||||
@@ -549,6 +710,7 @@ fn line_callout(w: f64, h: f64, a: &dyn Fn(usize) -> f64, pts: usize, accent: bo
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::{CATALOG, FillMode, build, info};
|
||||
use super::Name;
|
||||
use kurbo::{BezPath, PathEl, Point, Shape};
|
||||
|
||||
const NEW: [&str; 38] = [
|
||||
@@ -596,6 +758,101 @@ mod tests {
|
||||
build(name, w, h, adj).map(|g| g.outline()).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Adjust sets the digests were taken at: the defaults (an empty slice) and explicit values,
|
||||
/// so every adjust branch runs.
|
||||
const GOLD_ADJ: [&[f64]; 2] = [&[], &[25_000.0, 10_000.0, 50_000.0, 30_000.0, 70_000.0, 20_000.0, 60_000.0, 40_000.0, 90_000.0, 15_000.0]];
|
||||
/// Sizes the digests were taken at: wide, tall and square.
|
||||
const GOLD_SIZES: [(f64, f64); 4] = [(120.0, 80.0), (300.0, 50.0), (64.0, 64.0), (50.0, 300.0)];
|
||||
|
||||
/// FNV-1a over each outline's path elements, for every adjust set and size. Coordinates are
|
||||
/// printed to 3 decimal places so last-bit libm differences between platforms do not change it.
|
||||
fn outline_digest(name: &str) -> u64 {
|
||||
use std::fmt::Write;
|
||||
let mut text = String::new();
|
||||
for adj in GOLD_ADJ {
|
||||
for (w, h) in GOLD_SIZES {
|
||||
let g = build(name, w, h, adj).unwrap_or_else(|| panic!("{name} builds no outline"));
|
||||
for el in g.outline().elements() {
|
||||
let _ = match *el {
|
||||
PathEl::MoveTo(p) => write!(text, "M{:.3},{:.3}", p.x, p.y),
|
||||
PathEl::LineTo(p) => write!(text, "L{:.3},{:.3}", p.x, p.y),
|
||||
PathEl::QuadTo(a, p) => write!(text, "Q{:.3},{:.3},{:.3},{:.3}", a.x, a.y, p.x, p.y),
|
||||
PathEl::CurveTo(a, b, p) => write!(text, "C{:.3},{:.3},{:.3},{:.3},{:.3},{:.3}", a.x, a.y, b.x, b.y, p.x, p.y),
|
||||
PathEl::ClosePath => write!(text, "Z"),
|
||||
};
|
||||
}
|
||||
text.push('|');
|
||||
}
|
||||
}
|
||||
text.bytes().fold(0xcbf2_9ce4_8422_2325_u64, |h, b| (h ^ u64::from(b)).wrapping_mul(0x0100_0000_01b3))
|
||||
}
|
||||
|
||||
/// Digests of every name's outlines as the string dispatch built them (craft-libs 2b35d243;
|
||||
/// DeckCraft's fc35ad67 gave other digests for curvedRightArrow and curvedDownArrow), so
|
||||
/// this refactor is proven output-identical: any changed outline fails here.
|
||||
const GOLDEN: [(&str, u64); 38] = [
|
||||
("lineInv", 0x4a58a16923e54a4f),
|
||||
("nonIsoscelesTrapezoid", 0x79eca0b36812e282),
|
||||
("pieWedge", 0x2a0d3ffe5e9a481d),
|
||||
("leftRightArrowCallout", 0x9cd88f0ab55f76e5),
|
||||
("upDownArrowCallout", 0x568bc91708855007),
|
||||
("quadArrowCallout", 0xca59015298b1584b),
|
||||
("circularArrow", 0x4227aa468cfe990e),
|
||||
("leftCircularArrow", 0x6fc3779ce4b08914),
|
||||
("leftRightCircularArrow", 0x9dd43d9ba80e47df),
|
||||
("curvedRightArrow", 0x83e7dd251401715d),
|
||||
("curvedLeftArrow", 0x82472848de4a9fec),
|
||||
("curvedUpArrow", 0xcfbd88c1aea4bf9d),
|
||||
("curvedDownArrow", 0xa6f75eaf0ee71d49),
|
||||
("swooshArrow", 0x63ff193fdf1d0427),
|
||||
("callout1", 0xe7e0410f96282e8d),
|
||||
("callout2", 0xcaa7730106a9ee1b),
|
||||
("callout3", 0x4bc625449c0dfb30),
|
||||
("accentCallout1", 0x64f4c3eb775542fb),
|
||||
("accentCallout2", 0x2e3caa5b473ab4c7),
|
||||
("accentCallout3", 0x6ca203b149a2618e),
|
||||
("borderCallout2", 0xcaa7730106a9ee1b),
|
||||
("borderCallout3", 0x4bc625449c0dfb30),
|
||||
("accentBorderCallout1", 0x64f4c3eb775542fb),
|
||||
("accentBorderCallout2", 0x2e3caa5b473ab4c7),
|
||||
("accentBorderCallout3", 0x6ca203b149a2618e),
|
||||
("ellipseRibbon", 0x7d259fa067028102),
|
||||
("ellipseRibbon2", 0x3536b31ad0c95cf5),
|
||||
("leftRightRibbon", 0x6adc78f12d1cab9c),
|
||||
("flowChartOfflineStorage", 0x36960029a3770971),
|
||||
("gear6", 0x62b46d43b67decea),
|
||||
("gear9", 0xa42f43c2ae80314b),
|
||||
("funnel", 0x9327e2c2e99ab7d3),
|
||||
("cornerTabs", 0xe236f19aac60a20d),
|
||||
("squareTabs", 0xc3aea5acb50d6ccd),
|
||||
("plaqueTabs", 0xc64ebb2d06640521),
|
||||
("chartX", 0x6b2a9ff342247551),
|
||||
("chartStar", 0x6ebb545c66f313dd),
|
||||
("chartPlus", 0x979031aae16250d7),
|
||||
];
|
||||
|
||||
/// Every name builds the same outline as before the `Name` enum, and unknown names build nothing.
|
||||
#[test]
|
||||
fn outlines_match_the_string_dispatch() {
|
||||
for (name, want) in GOLDEN {
|
||||
let have = outline_digest(name);
|
||||
assert_eq!(have, want, "{name} outline changed");
|
||||
}
|
||||
assert!(build("noSuchShape", 100.0, 100.0, &[]).is_none());
|
||||
}
|
||||
|
||||
/// Every name this module builds round-trips through its OOXML identifier, and unknown
|
||||
/// strings do not parse.
|
||||
#[test]
|
||||
fn name_round_trips_through_ooxml() {
|
||||
for s in NEW {
|
||||
let n = Name::from_str(s).unwrap_or_else(|| panic!("{s} missing from Name"));
|
||||
assert_eq!(n.ooxml(), s, "{s} maps to a different OOXML id");
|
||||
}
|
||||
assert!(Name::from_str("noSuchShape").is_none());
|
||||
assert!(Name::from_str("").is_none());
|
||||
}
|
||||
|
||||
/// Distance from `p` to the nearest point of the (flattened) outline.
|
||||
fn dist(path: &BezPath, p: Point) -> f64 {
|
||||
let mut best = f64::MAX;
|
||||
|
||||
@@ -418,3 +418,71 @@ fn wedge_callout_mirrors_with_its_tip() {
|
||||
assert!(a.iter().any(|p| (p.x - x).abs() < 1e-6 && (p.y - h).abs() < 1e-6), "no wedge corner at ({x}, {h}): {a:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The vertices, and the cubic segments' end points, of preset `name`'s path.
|
||||
fn path_points(name: &str, w: f64, h: f64, adj: &[f64]) -> Vec<(f64, f64)> {
|
||||
let g = build(name, w, h, adj).unwrap();
|
||||
g.paths[0].path.elements().iter().filter_map(|e| e.end_point()).map(|p| (p.x, p.y)).collect()
|
||||
}
|
||||
|
||||
// ECMA-376 presetShapeDefinitions: bentConnector4 runs l,t → x1,t → x1,y2 → r,y2 → r,b and
|
||||
// bentConnector5 l,t → x1,t → x1,y2 → x3,y2 → x3,b → r,b, with x1 = w·adj1, y2 = h·adj2,
|
||||
// x3 = w·adj3 (in 100000ths). Both were drawn as bentConnector3, with only adj1.
|
||||
#[test]
|
||||
fn the_three_and_four_bend_elbows_follow_their_adjust_values() {
|
||||
assert_eq!(path_points("bentConnector4", 200.0, 100.0, &[]), [(0.0, 0.0), (100.0, 0.0), (100.0, 50.0), (200.0, 50.0), (200.0, 100.0)]);
|
||||
assert_eq!(
|
||||
path_points("bentConnector4", 200.0, 100.0, &[20000.0, 80000.0]),
|
||||
[(0.0, 0.0), (40.0, 0.0), (40.0, 80.0), (200.0, 80.0), (200.0, 100.0)]
|
||||
);
|
||||
// Outside 0..100000, as PowerPoint routes a connector around a shape.
|
||||
assert_eq!(
|
||||
path_points("bentConnector4", 200.0, 100.0, &[-40000.0, 120000.0]),
|
||||
[(0.0, 0.0), (-80.0, 0.0), (-80.0, 120.0), (200.0, 120.0), (200.0, 100.0)]
|
||||
);
|
||||
assert_eq!(
|
||||
path_points("bentConnector5", 200.0, 100.0, &[]),
|
||||
[(0.0, 0.0), (100.0, 0.0), (100.0, 50.0), (100.0, 50.0), (100.0, 100.0), (200.0, 100.0)]
|
||||
);
|
||||
assert_eq!(
|
||||
path_points("bentConnector5", 200.0, 100.0, &[25000.0, 75000.0, 60000.0]),
|
||||
[(0.0, 0.0), (50.0, 0.0), (50.0, 75.0), (120.0, 75.0), (120.0, 100.0), (200.0, 100.0)]
|
||||
);
|
||||
// The handles sit where the spec puts them: on the leg each value moves.
|
||||
let g = build("bentConnector5", 200.0, 100.0, &[25000.0, 75000.0, 60000.0]).unwrap();
|
||||
let pos: Vec<(f64, f64)> = g.handles.iter().map(|h| (h.pos.x, h.pos.y)).collect();
|
||||
assert_eq!(pos, [(50.0, 37.5), (85.0, 75.0), (120.0, 87.5)]);
|
||||
}
|
||||
|
||||
// curvedConnector2 is one cubic (l,t)…(r,b) through (wd2,t) and (r,hd2); 4 and 5 bend at the same
|
||||
// places as the elbows, three and four cubics. 2 was drawn as 3 at adj1 = 100%, 4 and 5 as 3.
|
||||
#[test]
|
||||
fn the_curved_connectors_follow_their_adjust_values() {
|
||||
let g = build("curvedConnector2", 200.0, 100.0, &[]).unwrap();
|
||||
let els = g.paths[0].path.elements();
|
||||
assert_eq!(els.len(), 2, "a move and one cubic: {els:?}");
|
||||
assert_eq!(els[1], kurbo::PathEl::CurveTo(Point::new(100.0, 0.0), Point::new(200.0, 50.0), Point::new(200.0, 100.0)));
|
||||
// the joints: (x2, y1), (x3, y4), (r, b) for 4; (x3, y1), (x1, y4), (x6, y5), (r, b) for 5
|
||||
assert_eq!(path_points("curvedConnector4", 200.0, 100.0, &[]), [(0.0, 0.0), (100.0, 25.0), (150.0, 50.0), (200.0, 100.0)]);
|
||||
assert_eq!(path_points("curvedConnector4", 200.0, 100.0, &[20000.0, 80000.0]), [(0.0, 0.0), (40.0, 40.0), (120.0, 80.0), (200.0, 100.0)]);
|
||||
assert_eq!(path_points("curvedConnector5", 200.0, 100.0, &[]), [(0.0, 0.0), (100.0, 25.0), (100.0, 50.0), (100.0, 75.0), (200.0, 100.0)]);
|
||||
assert_eq!(
|
||||
path_points("curvedConnector5", 200.0, 100.0, &[25000.0, 75000.0, 60000.0]),
|
||||
[(0.0, 0.0), (50.0, 37.5), (85.0, 75.0), (120.0, 87.5), (200.0, 100.0)]
|
||||
);
|
||||
}
|
||||
|
||||
// None of these is in the gallery, so their defaults came from nowhere: a file giving no adjust
|
||||
// values, or only some, drew them at 0.
|
||||
#[test]
|
||||
fn connectors_outside_the_gallery_take_the_spec_defaults() {
|
||||
for (name, n) in [("bentConnector4", 2), ("bentConnector5", 3), ("curvedConnector4", 2), ("curvedConnector5", 3)] {
|
||||
assert!(info(name).is_none(), "{name} is not in the gallery");
|
||||
assert_eq!(adjust_defaults(name), vec![50000.0; n].as_slice(), "{name}");
|
||||
for v in [f64::NAN, f64::INFINITY, -1e12, 1e12] {
|
||||
assert!(build(name, 120.0, 80.0, &[v; 3]).is_some(), "{name} {v}");
|
||||
}
|
||||
}
|
||||
assert_eq!(adjust_defaults("bentConnector3"), [50000.0]);
|
||||
assert!(adjust_defaults("rect").is_empty());
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
//! Text rectangles of the preset geometries, evaluated from ECMA-376's presetShapeDefinitions
|
||||
//! guide formulas (`preset_text.txt`, generated by `tests/fixtures/make_preset_guides.py`): each
|
||||
//! preset's adjust values, the guides its `<a:rect>` uses, and the rectangle.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use kurbo::Rect;
|
||||
|
||||
struct Def {
|
||||
adj: Vec<(&'static str, &'static str)>,
|
||||
guides: Vec<(&'static str, &'static str)>,
|
||||
rect: [&'static str; 4],
|
||||
}
|
||||
|
||||
fn defs() -> &'static HashMap<&'static str, Def> {
|
||||
static DEFS: OnceLock<HashMap<&'static str, Def>> = OnceLock::new();
|
||||
DEFS.get_or_init(|| {
|
||||
let pairs = |s: &'static str| -> Vec<(&'static str, &'static str)> {
|
||||
s.split(';').filter(|x| !x.is_empty()).filter_map(|x| x.split_once('=')).collect()
|
||||
};
|
||||
include_str!("preset_text.txt")
|
||||
.lines()
|
||||
.filter(|l| !l.starts_with('#'))
|
||||
.filter_map(|l| {
|
||||
let f: Vec<&'static str> = l.split('|').collect();
|
||||
let r: Vec<&'static str> = f.get(3)?.split_whitespace().collect();
|
||||
Some((*f.first()?, Def { adj: pairs(f.get(1)?), guides: pairs(f.get(2)?), rect: [r.first()?, r.get(1)?, r.get(2)?, r.get(3)?] }))
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
fn eval(f: &str, v: &HashMap<&str, f64>) -> f64 {
|
||||
let mut p = f.split_whitespace();
|
||||
let op = p.next().unwrap_or("val");
|
||||
let a: Vec<f64> = p.map(|x| v.get(x).copied().or_else(|| x.parse().ok()).unwrap_or(0.0)).collect();
|
||||
let g = |i: usize| a.get(i).copied().unwrap_or(0.0);
|
||||
let rad = |x: f64| (x / 60000.0).to_radians();
|
||||
let div = |n: f64, d: f64| if d != 0.0 { n / d } else { 0.0 };
|
||||
let r = match op {
|
||||
"val" => g(0),
|
||||
"*/" => div(g(0) * g(1), g(2)),
|
||||
"+-" => g(0) + g(1) - g(2),
|
||||
"+/" => div(g(0) + g(1), g(2)),
|
||||
"?:" => {
|
||||
if g(0) > 0.0 {
|
||||
g(1)
|
||||
} else {
|
||||
g(2)
|
||||
}
|
||||
}
|
||||
"abs" => g(0).abs(),
|
||||
"at2" => g(1).atan2(g(0)).to_degrees() * 60000.0,
|
||||
"cat2" => g(0) * g(2).atan2(g(1)).cos(),
|
||||
"sat2" => g(0) * g(2).atan2(g(1)).sin(),
|
||||
"cos" => g(0) * rad(g(1)).cos(),
|
||||
"sin" => g(0) * rad(g(1)).sin(),
|
||||
"tan" => g(0) * rad(g(1)).tan(),
|
||||
"max" => g(0).max(g(1)),
|
||||
"min" => g(0).min(g(1)),
|
||||
"mod" => (g(0) * g(0) + g(1) * g(1) + g(2) * g(2)).sqrt(),
|
||||
"pin" => {
|
||||
if g(1) < g(0) {
|
||||
g(0)
|
||||
} else if g(1) > g(2) {
|
||||
g(2)
|
||||
} else {
|
||||
g(1)
|
||||
}
|
||||
}
|
||||
"sqrt" => g(0).max(0.0).sqrt(),
|
||||
_ => 0.0,
|
||||
};
|
||||
if r.is_finite() { r } else { 0.0 }
|
||||
}
|
||||
|
||||
/// The standard's text rectangle for preset `name` in a `w` × `h` box with adjust values `adj`
|
||||
/// (in handle order; missing ones take the standard's defaults). `None` for presets it doesn't
|
||||
/// define or without a rectangle.
|
||||
pub(crate) fn text_rect(name: &str, w: f64, h: f64, adj: &[f64]) -> Option<Rect> {
|
||||
let d = defs().get(name)?;
|
||||
let ss = w.min(h);
|
||||
let mut v: HashMap<&str, f64> = HashMap::with_capacity(64);
|
||||
for (k, x) in [
|
||||
("w", w),
|
||||
("h", h),
|
||||
("l", 0.0),
|
||||
("t", 0.0),
|
||||
("r", w),
|
||||
("b", h),
|
||||
("ss", ss),
|
||||
("ls", w.max(h)),
|
||||
("hc", w / 2.0),
|
||||
("vc", h / 2.0),
|
||||
("cd2", 10_800_000.0),
|
||||
("cd4", 5_400_000.0),
|
||||
("cd8", 2_700_000.0),
|
||||
("3cd4", 16_200_000.0),
|
||||
("3cd8", 8_100_000.0),
|
||||
("5cd8", 13_500_000.0),
|
||||
("7cd8", 18_900_000.0),
|
||||
] {
|
||||
v.insert(k, x);
|
||||
}
|
||||
const DIVS: [(&str, &str, &str, f64); 10] = [
|
||||
("wd2", "hd2", "ssd2", 2.0),
|
||||
("wd3", "hd3", "ssd3", 3.0),
|
||||
("wd4", "hd4", "ssd4", 4.0),
|
||||
("wd5", "hd5", "ssd5", 5.0),
|
||||
("wd6", "hd6", "ssd6", 6.0),
|
||||
("wd8", "hd8", "ssd8", 8.0),
|
||||
("wd10", "hd10", "ssd10", 10.0),
|
||||
("wd12", "hd12", "ssd12", 12.0),
|
||||
("wd16", "hd16", "ssd16", 16.0),
|
||||
("wd32", "hd32", "ssd32", 32.0),
|
||||
];
|
||||
for (a, b, c, n) in DIVS {
|
||||
v.insert(a, w / n);
|
||||
v.insert(b, h / n);
|
||||
v.insert(c, ss / n);
|
||||
}
|
||||
for (i, (k, f)) in d.adj.iter().enumerate() {
|
||||
let x = adj.get(i).copied().filter(|x| x.is_finite()).unwrap_or_else(|| eval(f, &v));
|
||||
v.insert(k, x);
|
||||
}
|
||||
for (k, f) in &d.guides {
|
||||
let x = eval(f, &v);
|
||||
v.insert(k, x);
|
||||
}
|
||||
let get = |k: &str| v.get(k).copied().or_else(|| k.parse().ok()).unwrap_or(0.0);
|
||||
let [l, t, r, b] = d.rect.map(get);
|
||||
Some(Rect::new(l, t, r, b))
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
# Generated by crates/drawingml/tests/fixtures/make_preset_guides.py from presetShapeDefinitions.xml.
|
||||
accentBorderCallout1|adj1=val 18750;adj2=val -8333;adj3=val 112500;adj4=val -38333||l t r b
|
||||
accentBorderCallout2|adj1=val 18750;adj2=val -8333;adj3=val 18750;adj4=val -16667;adj5=val 112500;adj6=val -46667||l t r b
|
||||
accentBorderCallout3|adj1=val 18750;adj2=val -8333;adj3=val 18750;adj4=val -16667;adj5=val 100000;adj6=val -16667;adj7=val 112963;adj8=val -8333||l t r b
|
||||
accentCallout1|adj1=val 18750;adj2=val -8333;adj3=val 112500;adj4=val -38333||l t r b
|
||||
accentCallout2|adj1=val 18750;adj2=val -8333;adj3=val 18750;adj4=val -16667;adj5=val 112500;adj6=val -46667||l t r b
|
||||
accentCallout3|adj1=val 18750;adj2=val -8333;adj3=val 18750;adj4=val -16667;adj5=val 100000;adj6=val -16667;adj7=val 112963;adj8=val -8333||l t r b
|
||||
actionButtonBackPrevious|||l t r b
|
||||
actionButtonBeginning|||l t r b
|
||||
actionButtonBlank|||l t r b
|
||||
actionButtonDocument|||l t r b
|
||||
actionButtonEnd|||l t r b
|
||||
actionButtonForwardNext|||l t r b
|
||||
actionButtonHelp|||l t r b
|
||||
actionButtonHome|||l t r b
|
||||
actionButtonInformation|||l t r b
|
||||
actionButtonMovie|||l t r b
|
||||
actionButtonReturn|||l t r b
|
||||
actionButtonSound|||l t r b
|
||||
arc|adj1=val 16200000;adj2=val 0|stAng=pin 0 adj1 21599999;enAng=pin 0 adj2 21599999;sw11=+- enAng 0 stAng;sw12=+- sw11 21600000 0;swAng=?: sw11 sw11 sw12;wt1=sin wd2 stAng;ht1=cos hd2 stAng;dx1=cat2 wd2 ht1 wt1;dy1=sat2 hd2 ht1 wt1;wt2=sin wd2 enAng;ht2=cos hd2 enAng;dx2=cat2 wd2 ht2 wt2;dy2=sat2 hd2 ht2 wt2;x1=+- hc dx1 0;y1=+- vc dy1 0;x2=+- hc dx2 0;y2=+- vc dy2 0;sw0=+- 21600000 0 stAng;da1=+- swAng 0 sw0;g1=max x1 x2;ir=?: da1 r g1;sw1=+- cd4 0 stAng;sw2=+- 27000000 0 stAng;sw3=?: sw1 sw1 sw2;da2=+- swAng 0 sw3;g5=max y1 y2;ib=?: da2 b g5;sw4=+- cd2 0 stAng;sw5=+- 32400000 0 stAng;sw6=?: sw4 sw4 sw5;da3=+- swAng 0 sw6;g9=min x1 x2;il=?: da3 l g9;sw7=+- 3cd4 0 stAng;sw8=+- 37800000 0 stAng;sw9=?: sw7 sw7 sw8;da4=+- swAng 0 sw9;g13=min y1 y2;it=?: da4 t g13|il it ir ib
|
||||
bentArrow|adj1=val 25000;adj2=val 25000;adj3=val 25000;adj4=val 43750||l t r b
|
||||
bentConnector2|||l t r b
|
||||
bentConnector3|adj1=val 50000||l t r b
|
||||
bentConnector4|adj1=val 50000;adj2=val 50000||l t r b
|
||||
bentConnector5|adj1=val 50000;adj2=val 50000;adj3=val 50000||l t r b
|
||||
bentUpArrow|adj1=val 25000;adj2=val 25000;adj3=val 25000|a1=pin 0 adj1 50000;a2=pin 0 adj2 50000;dx3=*/ ss a2 100000;x3=+- r 0 dx3;dx2=*/ ss a1 200000;x4=+- x3 dx2 0;dy2=*/ ss a1 100000;y2=+- b 0 dy2|l y2 x4 b
|
||||
bevel|adj=val 12500|a=pin 0 adj 50000;x1=*/ ss a 100000;x2=+- r 0 x1;y2=+- b 0 x1|x1 x1 x2 y2
|
||||
blockArc|adj1=val 10800000;adj2=val 0;adj3=val 25000|stAng=pin 0 adj1 21599999;istAng=pin 0 adj2 21599999;a3=pin 0 adj3 50000;sw11=+- istAng 0 stAng;sw12=+- sw11 21600000 0;swAng=?: sw11 sw11 sw12;wt1=sin wd2 stAng;ht1=cos hd2 stAng;wt3=sin wd2 istAng;ht3=cos hd2 istAng;dx1=cat2 wd2 ht1 wt1;dy1=sat2 hd2 ht1 wt1;dx3=cat2 wd2 ht3 wt3;dy3=sat2 hd2 ht3 wt3;x1=+- hc dx1 0;y1=+- vc dy1 0;x3=+- hc dx3 0;y3=+- vc dy3 0;dr=*/ ss a3 100000;iwd2=+- wd2 0 dr;ihd2=+- hd2 0 dr;wt2=sin iwd2 istAng;ht2=cos ihd2 istAng;wt4=sin iwd2 stAng;ht4=cos ihd2 stAng;dx2=cat2 iwd2 ht2 wt2;dy2=sat2 ihd2 ht2 wt2;dx4=cat2 iwd2 ht4 wt4;dy4=sat2 ihd2 ht4 wt4;x2=+- hc dx2 0;y2=+- vc dy2 0;x4=+- hc dx4 0;y4=+- vc dy4 0;sw0=+- 21600000 0 stAng;da1=+- swAng 0 sw0;g1=max x1 x2;g2=max x3 x4;g3=max g1 g2;ir=?: da1 r g3;sw1=+- cd4 0 stAng;sw2=+- 27000000 0 stAng;sw3=?: sw1 sw1 sw2;da2=+- swAng 0 sw3;g5=max y1 y2;g6=max y3 y4;g7=max g5 g6;ib=?: da2 b g7;sw4=+- cd2 0 stAng;sw5=+- 32400000 0 stAng;sw6=?: sw4 sw4 sw5;da3=+- swAng 0 sw6;g9=min x1 x2;g10=min x3 x4;g11=min g9 g10;il=?: da3 l g11;sw7=+- 3cd4 0 stAng;sw8=+- 37800000 0 stAng;sw9=?: sw7 sw7 sw8;da4=+- swAng 0 sw9;g13=min y1 y2;g14=min y3 y4;g15=min g13 g14;it=?: da4 t g15|il it ir ib
|
||||
borderCallout1|adj1=val 18750;adj2=val -8333;adj3=val 112500;adj4=val -38333||l t r b
|
||||
borderCallout2|adj1=val 18750;adj2=val -8333;adj3=val 18750;adj4=val -16667;adj5=val 112500;adj6=val -46667||l t r b
|
||||
borderCallout3|adj1=val 18750;adj2=val -8333;adj3=val 18750;adj4=val -16667;adj5=val 100000;adj6=val -16667;adj7=val 112963;adj8=val -8333||l t r b
|
||||
bracePair|adj=val 8333|a=pin 0 adj 25000;x1=*/ ss a 100000;it=*/ x1 29289 100000;il=+- x1 it 0;ir=+- r 0 il;ib=+- b 0 it|il il ir ib
|
||||
bracketPair|adj=val 16667|a=pin 0 adj 50000;x1=*/ ss a 100000;il=*/ x1 29289 100000;ir=+- r 0 il;ib=+- b 0 il|il il ir ib
|
||||
callout1|adj1=val 18750;adj2=val -8333;adj3=val 112500;adj4=val -38333||l t r b
|
||||
callout2|adj1=val 18750;adj2=val -8333;adj3=val 18750;adj4=val -16667;adj5=val 112500;adj6=val -46667||l t r b
|
||||
callout3|adj1=val 18750;adj2=val -8333;adj3=val 18750;adj4=val -16667;adj5=val 100000;adj6=val -16667;adj7=val 112963;adj8=val -8333||l t r b
|
||||
can|adj=val 25000|maxAdj=*/ 50000 h ss;a=pin 0 adj maxAdj;y1=*/ ss a 200000;y2=+- y1 y1 0;y3=+- b 0 y1|l y2 r y3
|
||||
chevron|adj=val 50000|maxAdj=*/ 100000 w ss;a=pin 0 adj maxAdj;x1=*/ ss a 100000;x2=+- r 0 x1;dx=+- x2 0 x1;il=?: dx x1 l;ir=?: dx x2 r|il t ir b
|
||||
chord|adj1=val 2700000;adj2=val 16200000|idx=cos wd2 2700000;idy=sin hd2 2700000;il=+- hc 0 idx;ir=+- hc idx 0;it=+- vc 0 idy;ib=+- vc idy 0|il it ir ib
|
||||
circularArrow|adj1=val 12500;adj2=val 1142319;adj3=val 20457681;adj4=val 10800000;adj5=val 12500|a5=pin 0 adj5 25000;maxAdj1=*/ a5 2 1;a1=pin 0 adj1 maxAdj1;th=*/ ss a1 100000;thh=*/ ss a5 100000;th2=*/ th 1 2;rw1=+- wd2 th2 thh;rh1=+- hd2 th2 thh;idx=cos rw1 2700000;idy=sin rh1 2700000;il=+- hc 0 idx;ir=+- hc idx 0;it=+- vc 0 idy;ib=+- vc idy 0|il it ir ib
|
||||
cloud||il=*/ w 2977 21600;it=*/ h 3262 21600;ir=*/ w 17087 21600;ib=*/ h 17337 21600|il it ir ib
|
||||
cloudCallout|adj1=val -20833;adj2=val 62500|il=*/ w 2977 21600;it=*/ h 3262 21600;ir=*/ w 17087 21600;ib=*/ h 17337 21600|il it ir ib
|
||||
corner|adj1=val 50000;adj2=val 50000|maxAdj1=*/ 100000 h ss;maxAdj2=*/ 100000 w ss;a1=pin 0 adj1 maxAdj1;a2=pin 0 adj2 maxAdj2;x1=*/ ss a2 100000;dy1=*/ ss a1 100000;y1=+- b 0 dy1;d=+- w 0 h;it=?: d y1 t;ir=?: d r x1|l it ir b
|
||||
cornerTabs||md=mod w h 0;dx=*/ 1 md 20;y1=+- 0 b dx;x1=+- 0 r dx|dx dx x1 y1
|
||||
cube|adj=val 25000|a=pin 0 adj 100000;y1=*/ ss a 100000;x4=+- r 0 y1|l y1 x4 b
|
||||
curvedConnector2|||l t r b
|
||||
curvedConnector3|adj1=val 50000||l t r b
|
||||
curvedConnector4|adj1=val 50000;adj2=val 50000||l t r b
|
||||
curvedConnector5|adj1=val 50000;adj2=val 50000;adj3=val 50000||l t r b
|
||||
curvedDownArrow|adj1=val 25000;adj2=val 50000;adj3=val 25000||l t r b
|
||||
curvedLeftArrow|adj1=val 25000;adj2=val 50000;adj3=val 25000||l t r b
|
||||
curvedRightArrow|adj1=val 25000;adj2=val 50000;adj3=val 25000||l t r b
|
||||
curvedUpArrow|adj1=val 25000;adj2=val 50000;adj3=val 25000||l t r b
|
||||
decagon|vf=val 105146|shd2=*/ hd2 vf 100000;dx1=cos wd2 2160000;x1=+- hc 0 dx1;x4=+- hc dx1 0;dy2=sin shd2 2160000;y2=+- vc 0 dy2;y3=+- vc dy2 0|x1 y2 x4 y3
|
||||
diagStripe|adj=val 50000|a=pin 0 adj 100000;x2=*/ w a 100000;x3=+/ x2 r 2;y2=*/ h a 100000;y3=+/ y2 b 2|l t x3 y3
|
||||
diamond||ir=*/ w 3 4;ib=*/ h 3 4|wd4 hd4 ir ib
|
||||
dodecagon||x1=*/ w 2894 21600;x4=*/ w 18706 21600;y1=*/ h 2894 21600;y4=*/ h 18706 21600|x1 y1 x4 y4
|
||||
donut|adj=val 25000|idx=cos wd2 2700000;idy=sin hd2 2700000;il=+- hc 0 idx;ir=+- hc idx 0;it=+- vc 0 idy;ib=+- vc idy 0|il it ir ib
|
||||
doubleWave|adj1=val 6250;adj2=val 0|a1=pin 0 adj1 12500;a2=pin -10000 adj2 10000;of2=*/ w a2 50000;dx2=?: of2 0 of2;x2=+- l 0 dx2;dx8=?: of2 of2 0;x8=+- r 0 dx8;x9=+- l dx8 0;x15=+- r dx2 0;il=max x2 x9;ir=min x8 x15;it=*/ h a1 50000;ib=+- b 0 it|il it ir ib
|
||||
downArrow|adj1=val 50000;adj2=val 50000|maxAdj2=*/ 100000 h ss;a1=pin 0 adj1 100000;a2=pin 0 adj2 maxAdj2;dy1=*/ ss a2 100000;y1=+- b 0 dy1;dx1=*/ w a1 200000;x1=+- hc 0 dx1;x2=+- hc dx1 0;dy2=*/ x1 dy1 wd2;y2=+- y1 dy2 0|x1 t x2 y2
|
||||
downArrowCallout|adj1=val 25000;adj2=val 25000;adj3=val 25000;adj4=val 64977|maxAdj3=*/ 100000 h ss;a3=pin 0 adj3 maxAdj3;q2=*/ a3 ss h;maxAdj4=+- 100000 0 q2;a4=pin 0 adj4 maxAdj4;y2=*/ h a4 100000|l t r y2
|
||||
ellipse||idx=cos wd2 2700000;idy=sin hd2 2700000;il=+- hc 0 idx;ir=+- hc idx 0;it=+- vc 0 idy;ib=+- vc idy 0|il it ir ib
|
||||
ellipseRibbon|adj1=val 25000;adj2=val 50000;adj3=val 12500|a1=pin 0 adj1 100000;a2=pin 25000 adj2 75000;q10=+- 100000 0 a1;q11=*/ q10 1 2;q12=+- a1 0 q11;minAdj3=max 0 q12;a3=pin minAdj3 adj3 a1;dx2=*/ w a2 200000;x2=+- hc 0 dx2;x5=+- r 0 x2;dy1=*/ h a3 100000;f1=*/ 4 dy1 w;q1=*/ x3 x3 w;q1=*/ h a1 100000;dy3=+- q1 0 dy1;q3=*/ x2 x2 w;q4=+- x2 0 q3;q5=*/ f1 q4 1;y3=+- q5 dy3 0;rh=+- b 0 q1;y6=+- y3 rh 0|x2 q1 x5 y6
|
||||
ellipseRibbon2|adj1=val 25000;adj2=val 50000;adj3=val 12500|a1=pin 0 adj1 100000;a2=pin 25000 adj2 75000;q10=+- 100000 0 a1;q11=*/ q10 1 2;q12=+- a1 0 q11;minAdj3=max 0 q12;a3=pin minAdj3 adj3 a1;dx2=*/ w a2 200000;x2=+- hc 0 dx2;x5=+- r 0 x2;dy1=*/ h a3 100000;f1=*/ 4 dy1 w;q1=*/ x3 x3 w;q1=*/ h a1 100000;dy3=+- q1 0 dy1;q3=*/ x2 x2 w;q4=+- x2 0 q3;q5=*/ f1 q4 1;u3=+- q5 dy3 0;rh=+- b 0 q1;u6=+- u3 rh 0;y6=+- b 0 u6|x2 y6 x5 rh
|
||||
flowChartAlternateProcess||il=*/ ssd6 29289 100000;ir=+- r 0 il;ib=+- b 0 il|il il ir ib
|
||||
flowChartCollate||ir=*/ w 3 4;ib=*/ h 3 4|wd4 hd4 ir ib
|
||||
flowChartConnector||idx=cos wd2 2700000;idy=sin hd2 2700000;il=+- hc 0 idx;ir=+- hc idx 0;it=+- vc 0 idy;ib=+- vc idy 0|il it ir ib
|
||||
flowChartDecision||ir=*/ w 3 4;ib=*/ h 3 4|wd4 hd4 ir ib
|
||||
flowChartDelay||idx=cos wd2 2700000;idy=sin hd2 2700000;ir=+- hc idx 0;it=+- vc 0 idy;ib=+- vc idy 0|l it ir ib
|
||||
flowChartDisplay||x2=*/ w 5 6|wd6 t x2 b
|
||||
flowChartDocument||y1=*/ h 17322 21600|l t r y1
|
||||
flowChartExtract||x2=*/ w 3 4|wd4 vc x2 b
|
||||
flowChartInputOutput||x5=*/ w 4 5|wd5 t x5 b
|
||||
flowChartInternalStorage|||wd8 hd8 r b
|
||||
flowChartMagneticDisk||y3=*/ h 5 6|l hd3 r y3
|
||||
flowChartMagneticDrum||x2=*/ w 2 3|wd6 t x2 b
|
||||
flowChartMagneticTape||idx=cos wd2 2700000;idy=sin hd2 2700000;il=+- hc 0 idx;ir=+- hc idx 0;it=+- vc 0 idy;ib=+- vc idy 0|il it ir ib
|
||||
flowChartManualInput|||l hd5 r b
|
||||
flowChartManualOperation||x3=*/ w 4 5|wd5 t x3 b
|
||||
flowChartMerge||x2=*/ w 3 4|wd4 t x2 vc
|
||||
flowChartMultidocument||y2=*/ h 3675 21600;y8=*/ h 20782 21600;x5=*/ w 18595 21600|l y2 x5 y8
|
||||
flowChartOfflineStorage||x4=*/ w 3 4|wd4 t x4 vc
|
||||
flowChartOffpageConnector||y1=*/ h 4 5|l t r y1
|
||||
flowChartOnlineStorage||x2=*/ w 5 6|wd6 t x2 b
|
||||
flowChartOr||idx=cos wd2 2700000;idy=sin hd2 2700000;il=+- hc 0 idx;ir=+- hc idx 0;it=+- vc 0 idy;ib=+- vc idy 0|il it ir ib
|
||||
flowChartPredefinedProcess||x2=*/ w 7 8|wd8 t x2 b
|
||||
flowChartPreparation||x2=*/ w 4 5|wd5 t x2 b
|
||||
flowChartProcess|||l t r b
|
||||
flowChartPunchedCard|||l hd5 r b
|
||||
flowChartPunchedTape||ib=*/ h 4 5|l hd5 r ib
|
||||
flowChartSort||ir=*/ w 3 4;ib=*/ h 3 4|wd4 hd4 ir ib
|
||||
flowChartSummingJunction||idx=cos wd2 2700000;idy=sin hd2 2700000;il=+- hc 0 idx;ir=+- hc idx 0;it=+- vc 0 idy;ib=+- vc idy 0|il it ir ib
|
||||
flowChartTerminator||il=*/ w 1018 21600;ir=*/ w 20582 21600;it=*/ h 3163 21600;ib=*/ h 18437 21600|il it ir ib
|
||||
foldedCorner|adj=val 16667|a=pin 0 adj 50000;dy2=*/ ss a 100000;y2=+- b 0 dy2|l t r y2
|
||||
frame|adj1=val 12500|a1=pin 0 adj1 50000;x1=*/ ss a1 100000;x4=+- r 0 x1;y4=+- b 0 x1|x1 x1 x4 y4
|
||||
funnel|||l t r b
|
||||
gear6|adj1=val 15000;adj2=val 3526|a1=pin 0 adj1 20000;a2=pin 0 adj2 5358;th=*/ ss a1 100000;lFD=*/ ss a2 100000;th2=*/ th 1 2;l2=*/ lFD 1 2;l3=+- th2 l2 0;rh=+- hd2 0 th;rw=+- wd2 0 th;dr=+- rw 0 rh;maxr=?: dr rh rw;ha=at2 maxr l3;aA1=+- 19800000 0 ha;aD1=+- 19800000 ha 0;ta11=cos rw aA1;ta12=sin rh aA1;bA1=at2 ta11 ta12;cta1=cos rh bA1;sta1=sin rw bA1;ma1=mod cta1 sta1 0;na1=*/ rw rh ma1;dxa1=cos na1 bA1;dya1=sin na1 bA1;xA1=+- hc dxa1 0;yA1=+- vc dya1 0;td11=cos rw aD1;td12=sin rh aD1;bD1=at2 td11 td12;ctd1=cos rh bD1;std1=sin rw bD1;md1=mod ctd1 std1 0;nd1=*/ rw rh md1;dxd1=cos nd1 bD1;dyd1=sin nd1 bD1;xD1=+- hc dxd1 0;yD1=+- vc dyd1 0;xAD1=+- xA1 0 xD1;yAD1=+- yA1 0 yD1;a1=at2 yAD1 xAD1;yD2=+- h 0 yA1;xD5=+- w 0 xA1|xD5 yA1 xA1 yD2
|
||||
gear9|adj1=val 10000;adj2=val 1763|a1=pin 0 adj1 20000;a2=pin 0 adj2 2679;th=*/ ss a1 100000;lFD=*/ ss a2 100000;th2=*/ th 1 2;l2=*/ lFD 1 2;l3=+- th2 l2 0;rh=+- hd2 0 th;rw=+- wd2 0 th;dr=+- rw 0 rh;maxr=?: dr rh rw;ha=at2 maxr l3;aA1=+- 18600000 0 ha;aD1=+- 18600000 ha 0;ta11=cos rw aA1;ta12=sin rh aA1;bA1=at2 ta11 ta12;cta1=cos rh bA1;sta1=sin rw bA1;ma1=mod cta1 sta1 0;na1=*/ rw rh ma1;dxa1=cos na1 bA1;dya1=sin na1 bA1;xA1=+- hc dxa1 0;yA1=+- vc dya1 0;td11=cos rw aD1;td12=sin rh aD1;bD1=at2 td11 td12;ctd1=cos rh bD1;std1=sin rw bD1;md1=mod ctd1 std1 0;nd1=*/ rw rh md1;dxd1=cos nd1 bD1;dyd1=sin nd1 bD1;xD1=+- hc dxd1 0;yD1=+- vc dyd1 0;xAD1=+- xA1 0 xD1;yAD1=+- yA1 0 yD1;a1=at2 yAD1 xAD1;aA2=+- 21000000 0 ha;aD2=+- 21000000 ha 0;ta21=cos rw aA2;ta22=sin rh aA2;bA2=at2 ta21 ta22;cta2=cos rh bA2;sta2=sin rw bA2;ma2=mod cta2 sta2 0;na2=*/ rw rh ma2;dxa2=cos na2 bA2;dya2=sin na2 bA2;xA2=+- hc dxa2 0;yA2=+- vc dya2 0;td21=cos rw aD2;td22=sin rh aD2;bD2=at2 td21 td22;ctd2=cos rh bD2;std2=sin rw bD2;md2=mod ctd2 std2 0;nd2=*/ rw rh md2;dxd2=cos nd2 bD2;dyd2=sin nd2 bD2;xD2=+- hc dxd2 0;yD2=+- vc dyd2 0;xAD2=+- xA2 0 xD2;yAD2=+- yA2 0 yD2;a2=at2 yAD2 xAD2;aD3=+- 1800000 ha 0;td31=cos rw aD3;td32=sin rh aD3;bD3=at2 td31 td32;ctd3=cos rh bD3;std3=sin rw bD3;md3=mod ctd3 std3 0;nd3=*/ rw rh md3;dyd3=sin nd3 bD3;yD3=+- vc dyd3 0;xA8=+- w 0 xD1|xA8 yD1 xD1 yD3
|
||||
halfFrame|adj1=val 33333;adj2=val 33333||l t r b
|
||||
heart||il=*/ w 1 6;ir=*/ w 5 6;ib=*/ h 2 3|il hd4 ir ib
|
||||
heptagon|hf=val 102572;vf=val 105210|swd2=*/ wd2 hf 100000;shd2=*/ hd2 vf 100000;svc=*/ vc vf 100000;dx2=*/ swd2 78183 100000;dy1=*/ shd2 62349 100000;x2=+- hc 0 dx2;x5=+- hc dx2 0;y1=+- svc 0 dy1;ib=+- b 0 y1|x2 y1 x5 ib
|
||||
hexagon|adj=val 25000;vf=val 115470|maxAdj=*/ 50000 w ss;a=pin 0 adj maxAdj;q1=*/ maxAdj -1 2;q2=+- a q1 0;q3=?: q2 4 2;q4=?: q2 3 2;q5=?: q2 q1 0;q6=+/ a q5 q1;q7=*/ q6 q4 -1;q8=+- q3 q7 0;il=*/ w q8 24;it=*/ h q8 24;ir=+- r 0 il;ib=+- b 0 it|il it ir ib
|
||||
homePlate|adj=val 50000|maxAdj=*/ 100000 w ss;a=pin 0 adj maxAdj;dx1=*/ ss a 100000;x1=+- r 0 dx1;ir=+/ x1 r 2|l t ir b
|
||||
horizontalScroll|adj=val 12500|a=pin 0 adj 25000;ch=*/ ss a 100000;ch2=*/ ch 1 2;y6=+- b 0 ch;x4=+- r 0 ch2|ch ch x4 y6
|
||||
irregularSeal1||x5=*/ w 4627 21600;x21=*/ w 16702 21600;y3=*/ h 6320 21600;y9=*/ h 13937 21600|x5 y3 x21 y9
|
||||
irregularSeal2||x5=*/ w 5372 21600;x19=*/ w 14640 21600;y3=*/ h 6382 21600;y17=*/ h 15935 21600|x5 y3 x19 y17
|
||||
leftArrow|adj1=val 50000;adj2=val 50000|maxAdj2=*/ 100000 w ss;a1=pin 0 adj1 100000;a2=pin 0 adj2 maxAdj2;dx2=*/ ss a2 100000;x2=+- l dx2 0;dy1=*/ h a1 200000;y1=+- vc 0 dy1;y2=+- vc dy1 0;dx1=*/ y1 dx2 hd2;x1=+- x2 0 dx1|x1 y1 r y2
|
||||
leftArrowCallout|adj1=val 25000;adj2=val 25000;adj3=val 25000;adj4=val 64977|maxAdj3=*/ 100000 w ss;a3=pin 0 adj3 maxAdj3;q2=*/ a3 ss w;maxAdj4=+- 100000 0 q2;a4=pin 0 adj4 maxAdj4;dx2=*/ w a4 100000;x2=+- r 0 dx2|x2 t r b
|
||||
leftBrace|adj1=val 8333;adj2=val 50000|a2=pin 0 adj2 100000;q1=+- 100000 0 a2;q2=min q1 a2;q3=*/ q2 1 2;maxAdj1=*/ q3 h ss;a1=pin 0 adj1 maxAdj1;y1=*/ ss a1 100000;dx1=cos wd2 2700000;dy1=sin y1 2700000;il=+- r 0 dx1;it=+- y1 0 dy1;ib=+- b dy1 y1|il it r ib
|
||||
leftBracket|adj=val 8333|maxAdj=*/ 50000 h ss;a=pin 0 adj maxAdj;y1=*/ ss a 100000;dx1=cos w 2700000;dy1=sin y1 2700000;il=+- r 0 dx1;it=+- y1 0 dy1;ib=+- b dy1 y1|il it r ib
|
||||
leftCircularArrow|adj1=val 12500;adj2=val -1142319;adj3=val 1142319;adj4=val 10800000;adj5=val 12500|a5=pin 0 adj5 25000;maxAdj1=*/ a5 2 1;a1=pin 0 adj1 maxAdj1;th=*/ ss a1 100000;thh=*/ ss a5 100000;th2=*/ th 1 2;rw1=+- wd2 th2 thh;rh1=+- hd2 th2 thh;idx=cos rw1 2700000;idy=sin rh1 2700000;il=+- hc 0 idx;ir=+- hc idx 0;it=+- vc 0 idy;ib=+- vc idy 0|il it ir ib
|
||||
leftRightArrow|adj1=val 50000;adj2=val 50000|maxAdj2=*/ 50000 w ss;a1=pin 0 adj1 100000;a2=pin 0 adj2 maxAdj2;x2=*/ ss a2 100000;x3=+- r 0 x2;dy=*/ h a1 200000;y1=+- vc 0 dy;y2=+- vc dy 0;dx1=*/ y1 x2 hd2;x1=+- x2 0 dx1;x4=+- x3 dx1 0|x1 y1 x4 y2
|
||||
leftRightArrowCallout|adj1=val 25000;adj2=val 25000;adj3=val 25000;adj4=val 48123|maxAdj3=*/ 50000 w ss;a3=pin 0 adj3 maxAdj3;q2=*/ a3 ss wd2;maxAdj4=+- 100000 0 q2;a4=pin 0 adj4 maxAdj4;dx2=*/ w a4 200000;x2=+- hc 0 dx2;x3=+- hc dx2 0|x2 t x3 b
|
||||
leftRightCircularArrow|adj1=val 12500;adj2=val 1142319;adj3=val 20457681;adj4=val 11942319;adj5=val 12500|a5=pin 0 adj5 25000;maxAdj1=*/ a5 2 1;a1=pin 0 adj1 maxAdj1;th=*/ ss a1 100000;thh=*/ ss a5 100000;th2=*/ th 1 2;rw1=+- wd2 th2 thh;rh1=+- hd2 th2 thh;idx=cos rw1 2700000;idy=sin rh1 2700000;il=+- hc 0 idx;ir=+- hc idx 0;it=+- vc 0 idy;ib=+- vc idy 0|il it ir ib
|
||||
leftRightRibbon|adj1=val 50000;adj2=val 50000;adj3=val 16667|a3=pin 0 adj3 33333;maxAdj1=+- 100000 0 a3;a1=pin 0 adj1 maxAdj1;w1=+- wd2 0 wd32;maxAdj2=*/ 100000 w1 ss;a2=pin 0 adj2 maxAdj2;x1=*/ ss a2 100000;x4=+- r 0 x1;dy1=*/ h a1 200000;dy2=*/ h a3 -200000;ly1=+- vc dy2 dy1;ry4=+- vc dy1 dy2|x1 ly1 x4 ry4
|
||||
leftRightUpArrow|adj1=val 25000;adj2=val 25000;adj3=val 25000|a2=pin 0 adj2 50000;maxAdj1=*/ a2 2 1;a1=pin 0 adj1 maxAdj1;q1=+- 100000 0 maxAdj1;maxAdj3=*/ q1 1 2;a3=pin 0 adj3 maxAdj3;x1=*/ ss a3 100000;dx2=*/ ss a2 100000;dx3=*/ ss a1 200000;y4=+- b 0 dx2;y3=+- y4 0 dx3;y5=+- y4 dx3 0;il=*/ dx3 x1 dx2;ir=+- r 0 il|il y3 ir y5
|
||||
leftUpArrow|adj1=val 25000;adj2=val 25000;adj3=val 25000|a2=pin 0 adj2 50000;maxAdj1=*/ a2 2 1;a1=pin 0 adj1 maxAdj1;maxAdj3=+- 100000 0 maxAdj1;a3=pin 0 adj3 maxAdj3;x1=*/ ss a3 100000;dx4=*/ ss a2 100000;x4=+- r 0 dx4;y4=+- b 0 dx4;dx3=*/ ss a1 200000;y3=+- y4 0 dx3;y5=+- y4 dx3 0;il=*/ dx3 x1 dx4|il y3 x4 y5
|
||||
lightningBolt||x4=*/ w 8757 21600;x9=*/ w 13917 21600;y4=*/ h 7437 21600;y10=*/ h 14277 21600|x4 y4 x9 y10
|
||||
mathDivide|adj1=val 23520;adj2=val 5880;adj3=val 11760|a1=pin 1000 adj1 36745;dy1=*/ h a1 200000;dx1=*/ w 73490 200000;y3=+- vc 0 dy1;y4=+- vc dy1 0;x1=+- hc 0 dx1;x3=+- hc dx1 0|x1 y3 x3 y4
|
||||
mathEqual|adj1=val 23520;adj2=val 11760|a1=pin 0 adj1 36745;2a1=*/ a1 2 1;mAdj2=+- 100000 0 2a1;a2=pin 0 adj2 mAdj2;dy1=*/ h a1 100000;dy2=*/ h a2 200000;dx1=*/ w 73490 200000;y2=+- vc 0 dy2;y3=+- vc dy2 0;y1=+- y2 0 dy1;y4=+- y3 dy1 0;x1=+- hc 0 dx1;x2=+- hc dx1 0|x1 y1 x2 y4
|
||||
mathMinus|adj1=val 23520|a1=pin 0 adj1 100000;dy1=*/ h a1 200000;dx1=*/ w 73490 200000;y1=+- vc 0 dy1;y2=+- vc dy1 0;x1=+- hc 0 dx1;x2=+- hc dx1 0|x1 y1 x2 y2
|
||||
mathMultiply|adj1=val 23520|a1=pin 0 adj1 51965;th=*/ ss a1 100000;a=at2 w h;sa=sin 1 a;ca=cos 1 a;dl=mod w h 0;rw=*/ dl 51965 100000;lM=+- dl 0 rw;xM=*/ ca lM 2;yM=*/ sa lM 2;dxAM=*/ sa th 2;dyAM=*/ ca th 2;xA=+- xM 0 dxAM;yB=+- yM 0 dyAM;xE=+- r 0 xA;yH=+- b 0 yB|xA yB xE yH
|
||||
mathNotEqual|adj1=val 23520;adj2=val 6600000;adj3=val 11760|a1=pin 0 adj1 50000;2a1=*/ a1 2 1;maxAdj3=+- 100000 0 2a1;a3=pin 0 adj3 maxAdj3;dy1=*/ h a1 100000;dy2=*/ h a3 200000;dx1=*/ w 73490 200000;x1=+- hc 0 dx1;x8=+- hc dx1 0;y2=+- vc 0 dy2;y3=+- vc dy2 0;y1=+- y2 0 dy1;y4=+- y3 dy1 0|x1 y1 x8 y4
|
||||
mathPlus|adj1=val 23520|a1=pin 0 adj1 73490;dx1=*/ w 73490 200000;dx2=*/ ss a1 200000;x1=+- hc 0 dx1;x4=+- hc dx1 0;y2=+- vc 0 dx2;y3=+- vc dx2 0|x1 y2 x4 y3
|
||||
moon|adj=val 50000|a=pin 0 adj 87500;g0=*/ ss a 100000;g0w=*/ g0 w ss;g12=*/ g0 9598 32768;g12w=*/ g12 w ss;g13=+- ss 0 g12;q1=*/ ss ss 1;q2=*/ g13 g13 1;q3=+- q1 0 q2;q4=sqrt q3;dy4=*/ q4 hd2 ss;g15h=+- vc 0 dy4;g16h=+- vc dy4 0|g12w g15h g0w g16h
|
||||
nonIsoscelesTrapezoid|adj1=val 25000;adj2=val 25000|maxAdj=*/ 50000 w ss;a1=pin 0 adj1 maxAdj;a2=pin 0 adj2 maxAdj;il=*/ wd3 a1 maxAdj;adjm=max a1 a2;it=*/ hd3 adjm maxAdj;irt=*/ wd3 a2 maxAdj;ir=+- r 0 irt|il it ir b
|
||||
noSmoking|adj=val 18750|idx=cos wd2 2700000;idy=sin hd2 2700000;il=+- hc 0 idx;ir=+- hc idx 0;it=+- vc 0 idy;ib=+- vc idy 0|il it ir ib
|
||||
notchedRightArrow|adj1=val 50000;adj2=val 50000|maxAdj2=*/ 100000 w ss;a1=pin 0 adj1 100000;a2=pin 0 adj2 maxAdj2;dx2=*/ ss a2 100000;dy1=*/ h a1 200000;y1=+- vc 0 dy1;y2=+- vc dy1 0;x1=*/ dy1 dx2 hd2;x3=+- r 0 x1|x1 y1 x3 y2
|
||||
octagon|adj=val 29289|a=pin 0 adj 50000;x1=*/ ss a 100000;il=*/ x1 1 2;ir=+- r 0 il;ib=+- b 0 il|il il ir ib
|
||||
parallelogram|adj=val 25000|maxAdj=*/ 100000 w ss;a=pin 0 adj maxAdj;il=*/ wd2 a maxAdj;q1=*/ 5 a maxAdj;q2=+/ 1 q1 12;il=*/ q2 w 1;it=*/ q2 h 1;ir=+- r 0 il;ib=+- b 0 it|il it ir ib
|
||||
pentagon|hf=val 105146;vf=val 110557|swd2=*/ wd2 hf 100000;shd2=*/ hd2 vf 100000;svc=*/ vc vf 100000;dx1=cos swd2 1080000;dx2=cos swd2 18360000;dy1=sin shd2 1080000;dy2=sin shd2 18360000;x2=+- hc 0 dx2;x3=+- hc dx2 0;y1=+- svc 0 dy1;y2=+- svc 0 dy2;it=*/ y1 dx2 dx1|x2 it x3 y2
|
||||
pie|adj1=val 0;adj2=val 16200000|idx=cos wd2 2700000;idy=sin hd2 2700000;il=+- hc 0 idx;ir=+- hc idx 0;it=+- vc 0 idy;ib=+- vc idy 0|il it ir ib
|
||||
pieWedge||g1=cos w 13500000;g2=sin h 13500000;x1=+- r g1 0;y1=+- b g2 0|x1 y1 r b
|
||||
plaque|adj=val 16667|a=pin 0 adj 50000;x1=*/ ss a 100000;il=*/ x1 70711 100000;ir=+- r 0 il;ib=+- b 0 il|il il ir ib
|
||||
plaqueTabs||md=mod w h 0;dx=*/ 1 md 20;y1=+- 0 b dx;x1=+- 0 r dx|dx dx x1 y1
|
||||
plus|adj=val 25000|a=pin 0 adj 50000;x1=*/ ss a 100000;x2=+- r 0 x1;y2=+- b 0 x1;d=+- w 0 h;il=?: d l x1;ir=?: d r x2;it=?: d x1 t;ib=?: d y2 b|il it ir ib
|
||||
quadArrow|adj1=val 22500;adj2=val 22500;adj3=val 22500|a2=pin 0 adj2 50000;maxAdj1=*/ a2 2 1;a1=pin 0 adj1 maxAdj1;q1=+- 100000 0 maxAdj1;maxAdj3=*/ q1 1 2;a3=pin 0 adj3 maxAdj3;x1=*/ ss a3 100000;dx2=*/ ss a2 100000;dx3=*/ ss a1 200000;y3=+- vc 0 dx3;y4=+- vc dx3 0;il=*/ dx3 x1 dx2;ir=+- r 0 il|il y3 ir y4
|
||||
quadArrowCallout|adj1=val 18515;adj2=val 18515;adj3=val 18515;adj4=val 48123|a2=pin 0 adj2 50000;maxAdj1=*/ a2 2 1;a1=pin 0 adj1 maxAdj1;maxAdj3=+- 50000 0 a2;a3=pin 0 adj3 maxAdj3;q2=*/ a3 2 1;maxAdj4=+- 100000 0 q2;a4=pin a1 adj4 maxAdj4;dx1=*/ w a4 200000;dy1=*/ h a4 200000;x2=+- hc 0 dx1;x7=+- hc dx1 0;y2=+- vc 0 dy1;y7=+- vc dy1 0|x2 y2 x7 y7
|
||||
rect|||l t r b
|
||||
ribbon|adj1=val 16667;adj2=val 50000|a1=pin 0 adj1 33333;a2=pin 25000 adj2 75000;dx2=*/ w a2 200000;x2=+- hc 0 dx2;x9=+- hc dx2 0;y2=*/ h a1 100000|x2 y2 x9 b
|
||||
ribbon2|adj1=val 16667;adj2=val 50000|a1=pin 0 adj1 33333;a2=pin 25000 adj2 75000;dx2=*/ w a2 200000;x2=+- hc 0 dx2;x9=+- hc dx2 0;dy2=*/ h a1 100000;y2=+- b 0 dy2|x2 t x9 y2
|
||||
rightArrow|adj1=val 50000;adj2=val 50000|maxAdj2=*/ 100000 w ss;a1=pin 0 adj1 100000;a2=pin 0 adj2 maxAdj2;dx1=*/ ss a2 100000;x1=+- r 0 dx1;dy1=*/ h a1 200000;y1=+- vc 0 dy1;y2=+- vc dy1 0;dx2=*/ y1 dx1 hd2;x2=+- x1 dx2 0|l y1 x2 y2
|
||||
rightArrowCallout|adj1=val 25000;adj2=val 25000;adj3=val 25000;adj4=val 64977|maxAdj3=*/ 100000 w ss;a3=pin 0 adj3 maxAdj3;q2=*/ a3 ss w;maxAdj4=+- 100000 0 q2;a4=pin 0 adj4 maxAdj4;x2=*/ w a4 100000|l t x2 b
|
||||
rightBrace|adj1=val 8333;adj2=val 50000|a2=pin 0 adj2 100000;q1=+- 100000 0 a2;q2=min q1 a2;q3=*/ q2 1 2;maxAdj1=*/ q3 h ss;a1=pin 0 adj1 maxAdj1;y1=*/ ss a1 100000;dx1=cos wd2 2700000;dy1=sin y1 2700000;ir=+- l dx1 0;it=+- y1 0 dy1;ib=+- b dy1 y1|l it ir ib
|
||||
rightBracket|adj=val 8333|maxAdj=*/ 50000 h ss;a=pin 0 adj maxAdj;y1=*/ ss a 100000;dx1=cos w 2700000;dy1=sin y1 2700000;ir=+- l dx1 0;it=+- y1 0 dy1;ib=+- b dy1 y1|l it ir ib
|
||||
round1Rect|adj=val 16667|a=pin 0 adj 50000;dx1=*/ ss a 100000;idx=*/ dx1 29289 100000;ir=+- r 0 idx|l t ir b
|
||||
round2DiagRect|adj1=val 16667;adj2=val 0|a1=pin 0 adj1 50000;a2=pin 0 adj2 50000;x1=*/ ss a1 100000;a=*/ ss a2 100000;dx1=*/ x1 29289 100000;dx2=*/ a 29289 100000;d=+- dx1 0 dx2;dx=?: d dx1 dx2;ir=+- r 0 dx;ib=+- b 0 dx|dx dx ir ib
|
||||
round2SameRect|adj1=val 16667;adj2=val 0|a1=pin 0 adj1 50000;a2=pin 0 adj2 50000;tx1=*/ ss a1 100000;bx1=*/ ss a2 100000;d=+- tx1 0 bx1;tdx=*/ tx1 29289 100000;bdx=*/ bx1 29289 100000;il=?: d tdx bdx;ir=+- r 0 il;ib=+- b 0 bdx|il tdx ir ib
|
||||
roundRect|adj=val 16667|a=pin 0 adj 50000;x1=*/ ss a 100000;il=*/ x1 29289 100000;ir=+- r 0 il;ib=+- b 0 il|il il ir ib
|
||||
rtTriangle||it=*/ h 7 12;ir=*/ w 7 12;ib=*/ h 11 12|wd12 it ir ib
|
||||
smileyFace|adj=val 4653|idx=cos wd2 2700000;idy=sin hd2 2700000;il=+- hc 0 idx;ir=+- hc idx 0;it=+- vc 0 idy;ib=+- vc idy 0|il it ir ib
|
||||
snip1Rect|adj=val 16667|a=pin 0 adj 50000;dx1=*/ ss a 100000;x1=+- r 0 dx1;it=*/ dx1 1 2;ir=+/ x1 r 2|l it ir b
|
||||
snip2DiagRect|adj1=val 0;adj2=val 16667|a1=pin 0 adj1 50000;a2=pin 0 adj2 50000;lx1=*/ ss a1 100000;rx1=*/ ss a2 100000;d=+- lx1 0 rx1;dx=?: d lx1 rx1;il=*/ dx 1 2;ir=+- r 0 il;ib=+- b 0 il|il il ir ib
|
||||
snip2SameRect|adj1=val 16667;adj2=val 0|a1=pin 0 adj1 50000;a2=pin 0 adj2 50000;tx1=*/ ss a1 100000;bx1=*/ ss a2 100000;by1=+- b 0 bx1;d=+- tx1 0 bx1;dx=?: d tx1 bx1;il=*/ dx 1 2;ir=+- r 0 il;it=*/ tx1 1 2;ib=+/ by1 b 2|il it ir ib
|
||||
snipRoundRect|adj1=val 16667;adj2=val 16667|a1=pin 0 adj1 50000;a2=pin 0 adj2 50000;x1=*/ ss a1 100000;dx2=*/ ss a2 100000;x2=+- r 0 dx2;il=*/ x1 29289 100000;ir=+/ x2 r 2|il il ir b
|
||||
squareTabs||md=mod w h 0;dx=*/ 1 md 20;y1=+- 0 b dx;x1=+- 0 r dx|dx dx x1 y1
|
||||
star10|adj=val 42533;hf=val 105146|a=pin 0 adj 50000;swd2=*/ wd2 hf 100000;iwd2=*/ swd2 a 50000;ihd2=*/ hd2 a 50000;sdx1=*/ iwd2 80902 100000;sdy2=*/ ihd2 58779 100000;sx2=+- hc 0 sdx1;sx5=+- hc sdx1 0;sy2=+- vc 0 sdy2;sy3=+- vc sdy2 0|sx2 sy2 sx5 sy3
|
||||
star12|adj=val 37500|a=pin 0 adj 50000;iwd2=*/ wd2 a 50000;ihd2=*/ hd2 a 50000;sdx2=cos iwd2 2700000;sdy2=sin ihd2 2700000;sx2=+- hc 0 sdx2;sx5=+- hc sdx2 0;sy2=+- vc 0 sdy2;sy5=+- vc sdy2 0|sx2 sy2 sx5 sy5
|
||||
star16|adj=val 37500|a=pin 0 adj 50000;iwd2=*/ wd2 a 50000;ihd2=*/ hd2 a 50000;idx=cos iwd2 2700000;idy=sin ihd2 2700000;il=+- hc 0 idx;it=+- vc 0 idy;ir=+- hc idx 0;ib=+- vc idy 0|il it ir ib
|
||||
star24|adj=val 37500|a=pin 0 adj 50000;iwd2=*/ wd2 a 50000;ihd2=*/ hd2 a 50000;idx=cos iwd2 2700000;idy=sin ihd2 2700000;il=+- hc 0 idx;it=+- vc 0 idy;ir=+- hc idx 0;ib=+- vc idy 0|il it ir ib
|
||||
star32|adj=val 37500|a=pin 0 adj 50000;iwd2=*/ wd2 a 50000;ihd2=*/ hd2 a 50000;idx=cos iwd2 2700000;idy=sin ihd2 2700000;il=+- hc 0 idx;it=+- vc 0 idy;ir=+- hc idx 0;ib=+- vc idy 0|il it ir ib
|
||||
star4|adj=val 12500|a=pin 0 adj 50000;iwd2=*/ wd2 a 50000;ihd2=*/ hd2 a 50000;sdx=cos iwd2 2700000;sdy=sin ihd2 2700000;sx1=+- hc 0 sdx;sx2=+- hc sdx 0;sy1=+- vc 0 sdy;sy2=+- vc sdy 0|sx1 sy1 sx2 sy2
|
||||
star5|adj=val 19098;hf=val 105146;vf=val 110557|a=pin 0 adj 50000;swd2=*/ wd2 hf 100000;shd2=*/ hd2 vf 100000;svc=*/ vc vf 100000;iwd2=*/ swd2 a 50000;ihd2=*/ shd2 a 50000;sdx1=cos iwd2 20520000;sdy1=sin ihd2 3240000;sx1=+- hc 0 sdx1;sx4=+- hc sdx1 0;sy1=+- svc 0 sdy1;sy3=+- svc ihd2 0|sx1 sy1 sx4 sy3
|
||||
star6|adj=val 28868;hf=val 115470|a=pin 0 adj 50000;swd2=*/ wd2 hf 100000;iwd2=*/ swd2 a 50000;ihd2=*/ hd2 a 50000;sx1=+- hc 0 iwd2;sx4=+- hc iwd2 0;sdy1=sin ihd2 3600000;sy1=+- vc 0 sdy1;sy2=+- vc sdy1 0|sx1 sy1 sx4 sy2
|
||||
star7|adj=val 34601;hf=val 102572;vf=val 105210|a=pin 0 adj 50000;swd2=*/ wd2 hf 100000;shd2=*/ hd2 vf 100000;svc=*/ vc vf 100000;iwd2=*/ swd2 a 50000;ihd2=*/ shd2 a 50000;sdx2=*/ iwd2 78183 100000;sx2=+- hc 0 sdx2;sx5=+- hc sdx2 0;sdy1=*/ ihd2 90097 100000;sdy3=*/ ihd2 62349 100000;sy1=+- svc 0 sdy1;sy3=+- svc sdy3 0|sx2 sy1 sx5 sy3
|
||||
star8|adj=val 37500|a=pin 0 adj 50000;iwd2=*/ wd2 a 50000;ihd2=*/ hd2 a 50000;sdx1=*/ iwd2 92388 100000;sdy1=*/ ihd2 92388 100000;sx1=+- hc 0 sdx1;sx4=+- hc sdx1 0;sy1=+- vc 0 sdy1;sy4=+- vc sdy1 0|sx1 sy1 sx4 sy4
|
||||
straightConnector1|||l t r b
|
||||
stripedRightArrow|adj1=val 50000;adj2=val 50000|maxAdj2=*/ 84375 w ss;a1=pin 0 adj1 100000;a2=pin 0 adj2 maxAdj2;x4=*/ ss 5 32;dx5=*/ ss a2 100000;dy1=*/ h a1 200000;y1=+- vc 0 dy1;y2=+- vc dy1 0;dx6=*/ dy1 dx5 hd2;x6=+- r 0 dx6|x4 y1 x6 y2
|
||||
sun|adj=val 25000|a=pin 12500 adj 46875;g0=+- 50000 0 a;g7=*/ g0 23170 32768;g8=+- 50000 g7 0;g9=+- 50000 0 g7;x8=*/ w g8 100000;x9=*/ w g9 100000;y8=*/ h g8 100000;y9=*/ h g9 100000|x9 y9 x8 y8
|
||||
swooshArrow|adj1=val 25000;adj2=val 16667||l t r b
|
||||
teardrop|adj=val 100000|idx=cos wd2 2700000;idy=sin hd2 2700000;il=+- hc 0 idx;ir=+- hc idx 0;it=+- vc 0 idy;ib=+- vc idy 0|il it ir ib
|
||||
trapezoid|adj=val 25000|maxAdj=*/ 50000 w ss;a=pin 0 adj maxAdj;il=*/ wd3 a maxAdj;it=*/ hd3 a maxAdj;ir=+- r 0 il|il it ir b
|
||||
triangle|adj=val 50000|a=pin 0 adj 100000;x1=*/ w a 200000;x3=+- x1 wd2 0|x1 vc x3 b
|
||||
upArrowCallout|adj1=val 25000;adj2=val 25000;adj3=val 25000;adj4=val 64977|maxAdj3=*/ 100000 h ss;a3=pin 0 adj3 maxAdj3;q2=*/ a3 ss h;maxAdj4=+- 100000 0 q2;a4=pin 0 adj4 maxAdj4;dy2=*/ h a4 100000;y2=+- b 0 dy2|l y2 r b
|
||||
upDownArrow|adj1=val 50000;adj2=val 50000|maxAdj2=*/ 50000 h ss;a1=pin 0 adj1 100000;a2=pin 0 adj2 maxAdj2;y2=*/ ss a2 100000;y3=+- b 0 y2;dx1=*/ w a1 200000;x1=+- hc 0 dx1;x2=+- hc dx1 0;dy1=*/ x1 y2 wd2;y1=+- y2 0 dy1;y4=+- y3 dy1 0|x1 y1 x2 y4
|
||||
upArrow|adj1=val 50000;adj2=val 50000|maxAdj2=*/ 100000 h ss;a1=pin 0 adj1 100000;a2=pin 0 adj2 maxAdj2;dy2=*/ ss a2 100000;y2=+- t dy2 0;dx1=*/ w a1 200000;x1=+- hc 0 dx1;x2=+- hc dx1 0;dy1=*/ x1 dy2 wd2;y1=+- y2 0 dy1|x1 y1 x2 b
|
||||
upDownArrowCallout|adj1=val 25000;adj2=val 25000;adj3=val 25000;adj4=val 48123|maxAdj3=*/ 50000 h ss;a3=pin 0 adj3 maxAdj3;q2=*/ a3 ss hd2;maxAdj4=+- 100000 0 q2;a4=pin 0 adj4 maxAdj4;dy2=*/ h a4 200000;y2=+- vc 0 dy2;y3=+- vc dy2 0|l y2 r y3
|
||||
uturnArrow|adj1=val 25000;adj2=val 25000;adj3=val 25000;adj4=val 43750;adj5=val 75000||l t r b
|
||||
verticalScroll|adj=val 12500|a=pin 0 adj 25000;ch=*/ ss a 100000;ch2=*/ ch 1 2;x6=+- r 0 ch;y4=+- b 0 ch2|ch ch x6 y4
|
||||
wave|adj1=val 12500;adj2=val 0|a1=pin 0 adj1 20000;a2=pin -10000 adj2 10000;of2=*/ w a2 50000;dx2=?: of2 0 of2;x2=+- l 0 dx2;dx5=?: of2 of2 0;x5=+- r 0 dx5;x6=+- l dx5 0;x10=+- r dx2 0;il=max x2 x6;ir=min x5 x10;it=*/ h a1 50000;ib=+- b 0 it|il it ir ib
|
||||
wedgeEllipseCallout|adj1=val -20833;adj2=val 62500|idx=cos wd2 2700000;idy=sin hd2 2700000;il=+- hc 0 idx;ir=+- hc idx 0;it=+- vc 0 idy;ib=+- vc idy 0|il it ir ib
|
||||
wedgeRectCallout|adj1=val -20833;adj2=val 62500||l t r b
|
||||
wedgeRoundRectCallout|adj1=val -20833;adj2=val 62500;adj3=val 16667|u1=*/ ss adj3 100000;il=*/ u1 29289 100000;ir=+- r 0 il;ib=+- b 0 il|il il ir ib
|
||||
@@ -107,7 +107,50 @@ impl Guide {
|
||||
let d = b - a;
|
||||
let l = d.hypot();
|
||||
let t = if l > 1e-12 { d / l } else { Vec2::new(1.0, 0.0) };
|
||||
(a + t * (s - c0), t)
|
||||
let u = if l > 1e-12 { (s - c0) / l } else { 0.0 };
|
||||
(a + t * (s - c0), self.blended(i, t, u))
|
||||
}
|
||||
|
||||
/// The direction of the guide's segment `i` bent towards its neighbour's (the previous one in
|
||||
/// the first half of the segment, the next in the second) so it turns continuously: `t` at the
|
||||
/// middle of the segment, the mean of both at a vertex. A letter placed at a distance along
|
||||
/// the normal would otherwise step by the turn of the segment at each vertex, which shows as
|
||||
/// a ragged outline on tightly curved or large text.
|
||||
fn blended(&self, i: usize, t: Vec2, u: f64) -> Vec2 {
|
||||
let n = self.pts.len();
|
||||
let u = u.clamp(0.0, 1.0);
|
||||
let (other, w) = if u < 0.5 {
|
||||
(
|
||||
if i > 0 {
|
||||
Some(i - 1)
|
||||
} else if self.closed {
|
||||
n.checked_sub(2)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
0.5 - u,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
if i + 2 < n {
|
||||
Some(i + 1)
|
||||
} else if self.closed {
|
||||
Some(0)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
u - 0.5,
|
||||
)
|
||||
};
|
||||
let unit = |j: usize| -> Option<Vec2> {
|
||||
let v = *self.pts.get(j + 1)? - *self.pts.get(j)?;
|
||||
let l = v.hypot();
|
||||
(l > 1e-12).then(|| v / l)
|
||||
};
|
||||
let Some(o) = other.and_then(unit) else { return t };
|
||||
let m = t * (1.0 - w) + o * w;
|
||||
let l = m.hypot();
|
||||
if l > 1e-12 { m / l } else { t }
|
||||
}
|
||||
|
||||
/// The parallel curve `d` points away on the left of the direction of travel (above a
|
||||
@@ -992,6 +1035,22 @@ impl Mapper<'_> {
|
||||
self.zoom
|
||||
}
|
||||
|
||||
/// Map point `p` of a glyph of line `line` whose advance is centred on layout x `pivot`, for
|
||||
/// the lines that follow a guide: PowerPoint sets such a glyph down whole, turned to the
|
||||
/// guide's direction where its centre falls, instead of bending it along the guide. Other lines
|
||||
/// map as [`Mapper::map`].
|
||||
pub fn map_rigid(&self, line: usize, pivot: f64, p: Point) -> Point {
|
||||
let Some(&LineMap::Follow { band, guide, e0, s0, k, ref_y }) = self.lines.get(line) else { return self.map(line, p) };
|
||||
let path = match (guide.and_then(|g| self.offsets.get(g)), self.warp.bands.get(band)) {
|
||||
(Some(g), _) => g,
|
||||
(None, Some(Band::Follow { path, .. })) => path,
|
||||
_ => return p,
|
||||
};
|
||||
let (q, t) = path.at(s0 + (pivot - e0) * k);
|
||||
let out = q + t * ((p.x - pivot) * k) + Vec2::new(t.y, -t.x) * ((ref_y - p.y) * k);
|
||||
if out.x.is_finite() && out.y.is_finite() { out } else { p }
|
||||
}
|
||||
|
||||
/// Map point `p` of line `line` (layout coordinates) onto the warp.
|
||||
pub fn map(&self, line: usize, p: Point) -> Point {
|
||||
let out = match self.lines.get(line) {
|
||||
|
||||
@@ -320,6 +320,66 @@ fn arch_and_inflate_handles_follow_the_pointer() {
|
||||
assert!((more - 20_000.0).abs() < 1.0, "{more}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_guides_direction_turns_continuously_across_its_vertices() {
|
||||
// A coarse circle of radius 100: a vertex every 20 degrees, a 35 pt chord.
|
||||
let mut path = BezPath::new();
|
||||
for k in 0..=18 {
|
||||
let a = f64::from(k) * 20.0_f64.to_radians();
|
||||
let p = Point::new(100.0 * a.cos(), 100.0 * a.sin());
|
||||
if k == 0 { path.move_to(p) } else { path.line_to(p) }
|
||||
}
|
||||
let g = Guide::from_path(&path, false);
|
||||
let chord = 2.0 * 100.0 * 10.0_f64.to_radians().sin();
|
||||
let angle = |s: f64| {
|
||||
let (_, t) = g.at(s);
|
||||
t.y.atan2(t.x)
|
||||
};
|
||||
for k in 1..18 {
|
||||
let s = f64::from(k) * chord;
|
||||
let step = (angle(s + 1e-6) - angle(s - 1e-6)).abs();
|
||||
assert!(step < 1e-4, "the direction jumps by {step} rad at vertex {k}");
|
||||
}
|
||||
// Along the guide, away from its two ends (which keep the end chords' direction), the direction follows the circle:
|
||||
// the tangent is 90 degrees ahead of the radius, within a degree even in the middle of a chord.
|
||||
for k in 10..350 {
|
||||
let s = f64::from(k) * 0.05 * chord;
|
||||
let (p, t) = g.at(s);
|
||||
let radius = p.y.atan2(p.x);
|
||||
let want = radius + std::f64::consts::FRAC_PI_2;
|
||||
let diff = (t.y.atan2(t.x) - want + std::f64::consts::PI).rem_euclid(std::f64::consts::TAU) - std::f64::consts::PI;
|
||||
assert!(diff.abs() < 1.0_f64.to_radians(), "at {s}: the direction is {diff} rad off the circle's");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_glyph_on_a_follow_guide_is_set_down_whole_and_a_fill_warp_still_bends_it() {
|
||||
let ink = Rect::new(100.0, 50.0, 300.0, 90.0);
|
||||
let lines = one_line(ink);
|
||||
// The corners of a 20 x 30 glyph whose advance is centred on x = 150, baseline at the line's bottom.
|
||||
let glyph = [(140.0, 60.0), (160.0, 60.0), (160.0, 90.0), (140.0, 90.0)].map(|(x, y)| Point::new(x, y));
|
||||
let mapped = |name: &str, whole: bool| {
|
||||
let w = warp(name, &[], rect()).unwrap();
|
||||
let m = w.mapper(&lines, rect().inset(-5.0));
|
||||
glyph.map(|p| if whole { m.map_rigid(0, 150.0, p) } else { m.map(0, p) })
|
||||
};
|
||||
let sides = |q: &[Point; 4]| {
|
||||
[(q[0] - q[1]).hypot(), (q[1] - q[2]).hypot(), (q[2] - q[3]).hypot(), (q[3] - q[0]).hypot(), (q[0] - q[2]).hypot(), (q[1] - q[3]).hypot()]
|
||||
};
|
||||
let want = sides(&glyph);
|
||||
for name in ["textArchUp", "textArchDown", "textCircle", "textButton"] {
|
||||
let whole = sides(&mapped(name, true));
|
||||
for (a, b) in whole.iter().zip(&want) {
|
||||
assert!((a - b).abs() < 1e-6, "{name}: a glyph's size changed ({a} against {b})");
|
||||
}
|
||||
// Mapped point by point along the same guide, the glyph is bent: its two sides differ.
|
||||
let bent = sides(&mapped(name, false));
|
||||
assert!(bent.iter().zip(&want).any(|(a, b)| (a - b).abs() > 0.05), "{name}: bending left the glyph whole");
|
||||
}
|
||||
// A fill warp bends the glyph either way.
|
||||
assert_eq!(mapped("textInflate", true), mapped("textInflate", false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_lines_on_a_follow_band_are_mapped_from_one_measurement() {
|
||||
// 50,000 lines on an arch: each line's scale and anchor come from the band measured once.
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
"""From ECMA-376 presetShapeDefinitions.xml, write each preset's adjust values, the guides its
|
||||
text rectangle depends on, and the rectangle, one preset per line (src/preset_text.txt):
|
||||
name|adj=fmla;...|guide=fmla;...|l t r b
|
||||
Usage: make_preset_guides.py presetShapeDefinitions.xml > ../../src/preset_text.txt"""
|
||||
import sys, xml.etree.ElementTree as ET
|
||||
A = '{http://schemas.openxmlformats.org/drawingml/2006/main}'
|
||||
root = ET.parse(sys.argv[1]).getroot()
|
||||
print('# Generated by crates/drawingml/tests/fixtures/make_preset_guides.py from presetShapeDefinitions.xml.')
|
||||
for sh in root:
|
||||
rect = sh.find(A + 'rect')
|
||||
if rect is None:
|
||||
continue
|
||||
av = [(g.get('name'), g.get('fmla')) for g in list(sh.find(A + 'avLst') if sh.find(A + 'avLst') is not None else [])]
|
||||
gds = [(g.get('name'), g.get('fmla')) for g in list(sh.find(A + 'gdLst') if sh.find(A + 'gdLst') is not None else [])]
|
||||
names = {n for n, _ in gds}
|
||||
need = set()
|
||||
todo = [rect.get(k) for k in 'ltrb']
|
||||
deps = dict(gds)
|
||||
while todo:
|
||||
x = todo.pop()
|
||||
if x in names and x not in need:
|
||||
need.add(x)
|
||||
todo += deps[x].split()[1:]
|
||||
kept = [(n, f) for n, f in gds if n in need]
|
||||
print('%s|%s|%s|%s' % (sh.tag, ';'.join(f'{n}={f}' for n, f in av), ';'.join(f'{n}={f}' for n, f in kept),
|
||||
' '.join(rect.get(k) for k in 'ltrb')))
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Evaluate ECMA-376 presetShapeDefinitions.xml: for every preset, its default adjust values and
|
||||
its text rectangle at a few sizes. Writes preset_table.txt, which the geometry tests compare
|
||||
against. Usage: make_preset_table.py presetShapeDefinitions.xml > preset_table.txt
|
||||
(The XML ships with LibreOffice's source: oox/source/drawingml/customshapes/.)"""
|
||||
import math, sys, xml.etree.ElementTree as ET
|
||||
|
||||
A = '{http://schemas.openxmlformats.org/drawingml/2006/main}'
|
||||
SIZES = [(200.0, 100.0), (100.0, 200.0), (144.0, 144.0)]
|
||||
|
||||
def builtins(w, h):
|
||||
ss = min(w, h); ls = max(w, h)
|
||||
v = {'w': w, 'h': h, 'l': 0.0, 't': 0.0, 'r': w, 'b': h, 'ss': ss, 'ls': ls, 'hc': w / 2, 'vc': h / 2,
|
||||
'cd2': 10800000.0, 'cd4': 5400000.0, 'cd8': 2700000.0, '3cd4': 16200000.0, '3cd8': 8100000.0,
|
||||
'5cd8': 13500000.0, '7cd8': 18900000.0}
|
||||
for n in (2, 3, 4, 5, 6, 8, 10, 12, 16, 32):
|
||||
v[f'wd{n}'] = w / n; v[f'hd{n}'] = h / n; v[f'ssd{n}'] = ss / n
|
||||
return v
|
||||
|
||||
def ev(f, v):
|
||||
p = f.split()
|
||||
def g(x):
|
||||
return v[x] if x in v else float(x)
|
||||
op = p[0]
|
||||
a = [g(x) for x in p[1:]]
|
||||
rad = lambda x: x / 60000.0 * math.pi / 180.0
|
||||
if op == 'val': return a[0]
|
||||
if op == '*/': return a[0] * a[1] / a[2] if a[2] else 0.0
|
||||
if op == '+-': return a[0] + a[1] - a[2]
|
||||
if op == '+/': return (a[0] + a[1]) / a[2] if a[2] else 0.0
|
||||
if op == '?:': return a[1] if a[0] > 0 else a[2]
|
||||
if op == 'abs': return abs(a[0])
|
||||
if op == 'at2': return math.degrees(math.atan2(a[1], a[0])) * 60000.0
|
||||
if op == 'cat2': return a[0] * math.cos(math.atan2(a[2], a[1]))
|
||||
if op == 'sat2': return a[0] * math.sin(math.atan2(a[2], a[1]))
|
||||
if op == 'cos': return a[0] * math.cos(rad(a[1]))
|
||||
if op == 'sin': return a[0] * math.sin(rad(a[1]))
|
||||
if op == 'tan': return a[0] * math.tan(rad(a[1]))
|
||||
if op == 'max': return max(a[0], a[1])
|
||||
if op == 'min': return min(a[0], a[1])
|
||||
if op == 'mod': return math.sqrt(a[0] ** 2 + a[1] ** 2 + a[2] ** 2)
|
||||
if op == 'pin': return a[0] if a[1] < a[0] else (a[2] if a[1] > a[2] else a[1])
|
||||
if op == 'sqrt': return math.sqrt(max(a[0], 0.0))
|
||||
raise ValueError(f)
|
||||
|
||||
def main():
|
||||
root = ET.parse(sys.argv[1]).getroot()
|
||||
print('# name | default adjust values | text rect l t r b at 200x100 ; 100x200 ; 144x144')
|
||||
for sh in root:
|
||||
av = sh.find(A + 'avLst'); gd = sh.find(A + 'gdLst'); rect = sh.find(A + 'rect')
|
||||
avs = [] if av is None else [(g.get('name'), g.get('fmla')) for g in av]
|
||||
defaults = [ev(f, {}) for _, f in avs]
|
||||
rects = []
|
||||
for w, h in SIZES:
|
||||
v = builtins(w, h)
|
||||
for n, f in avs:
|
||||
v[n] = ev(f, v)
|
||||
for g in ([] if gd is None else gd):
|
||||
v[g.get('name')] = ev(g.get('fmla'), v)
|
||||
if rect is None:
|
||||
rects.append('0 0 %g %g' % (w, h))
|
||||
else:
|
||||
rects.append(' '.join('%.3f' % (v[rect.get(k)] if rect.get(k) in v else float(rect.get(k))) for k in 'ltrb'))
|
||||
print('%s | %s | %s' % (sh.tag, ' '.join(repr(round(d, 3)).removesuffix('.0') for d in defaults), ' ; '.join(rects)))
|
||||
|
||||
main()
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
# name | default adjust values | text rect l t r b at 200x100 ; 100x200 ; 144x144
|
||||
accentBorderCallout1 | 18750 -8333 112500 -38333 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
accentBorderCallout2 | 18750 -8333 18750 -16667 112500 -46667 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
accentBorderCallout3 | 18750 -8333 18750 -16667 100000 -16667 112963 -8333 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
accentCallout1 | 18750 -8333 112500 -38333 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
accentCallout2 | 18750 -8333 18750 -16667 112500 -46667 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
accentCallout3 | 18750 -8333 18750 -16667 100000 -16667 112963 -8333 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
actionButtonBackPrevious | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
actionButtonBeginning | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
actionButtonBlank | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
actionButtonDocument | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
actionButtonEnd | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
actionButtonForwardNext | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
actionButtonHelp | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
actionButtonHome | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
actionButtonInformation | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
actionButtonMovie | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
actionButtonReturn | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
actionButtonSound | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
arc | 16200000 0 | 100.000 0.000 200.000 50.000 ; 50.000 0.000 100.000 100.000 ; 72.000 0.000 144.000 72.000
|
||||
bentArrow | 25000 25000 25000 43750 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
bentConnector2 | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
bentConnector3 | 50000 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
bentConnector4 | 50000 50000 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
bentConnector5 | 50000 50000 50000 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
bentUpArrow | 25000 25000 25000 | 0.000 75.000 187.500 100.000 ; 0.000 175.000 87.500 200.000 ; 0.000 108.000 126.000 144.000
|
||||
bevel | 12500 | 12.500 12.500 187.500 87.500 ; 12.500 12.500 87.500 187.500 ; 18.000 18.000 126.000 126.000
|
||||
blockArc | 10800000 0 25000 | 0.000 0.000 200.000 50.000 ; 0.000 0.000 100.000 100.000 ; 0.000 0.000 144.000 72.000
|
||||
borderCallout1 | 18750 -8333 112500 -38333 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
borderCallout2 | 18750 -8333 18750 -16667 112500 -46667 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
borderCallout3 | 18750 -8333 18750 -16667 100000 -16667 112963 -8333 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
bracePair | 8333 | 10.774 10.774 189.226 97.559 ; 10.774 10.774 89.226 197.559 ; 15.514 15.514 128.486 140.485
|
||||
bracketPair | 16667 | 4.882 4.882 195.118 95.118 ; 4.882 4.882 95.118 195.118 ; 7.030 7.030 136.970 136.970
|
||||
callout1 | 18750 -8333 112500 -38333 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
callout2 | 18750 -8333 18750 -16667 112500 -46667 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
callout3 | 18750 -8333 18750 -16667 100000 -16667 112963 -8333 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
can | 25000 | 0.000 25.000 200.000 87.500 ; 0.000 25.000 100.000 187.500 ; 0.000 36.000 144.000 126.000
|
||||
chartPlus | | 0 0 200 100 ; 0 0 100 200 ; 0 0 144 144
|
||||
chartStar | | 0 0 200 100 ; 0 0 100 200 ; 0 0 144 144
|
||||
chartX | | 0 0 200 100 ; 0 0 100 200 ; 0 0 144 144
|
||||
chevron | 50000 | 50.000 0.000 150.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
chord | 2700000 16200000 | 29.289 14.645 170.711 85.355 ; 14.645 29.289 85.355 170.711 ; 21.088 21.088 122.912 122.912
|
||||
circularArrow | 12500 1142319 20457681 10800000 12500 | 33.709 19.064 166.291 80.936 ; 19.064 33.709 80.936 166.291 ; 27.452 27.452 116.548 116.548
|
||||
cloud | | 27.565 15.102 158.213 80.264 ; 13.782 30.204 79.106 160.528 ; 19.847 21.747 113.913 115.580
|
||||
cloudCallout | -20833 62500 | 27.565 15.102 158.213 80.264 ; 13.782 30.204 79.106 160.528 ; 19.847 21.747 113.913 115.580
|
||||
corner | 50000 50000 | 0.000 50.000 200.000 100.000 ; 0.000 0.000 50.000 200.000 ; 0.000 0.000 72.000 144.000
|
||||
cornerTabs | | 11.180 11.180 188.820 88.820 ; 11.180 11.180 88.820 188.820 ; 10.182 10.182 133.818 133.818
|
||||
cube | 25000 | 0.000 25.000 175.000 100.000 ; 0.000 25.000 75.000 200.000 ; 0.000 36.000 108.000 144.000
|
||||
curvedConnector2 | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
curvedConnector3 | 50000 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
curvedConnector4 | 50000 50000 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
curvedConnector5 | 50000 50000 50000 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
curvedDownArrow | 25000 50000 25000 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
curvedLeftArrow | 25000 50000 25000 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
curvedRightArrow | 25000 50000 25000 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
curvedUpArrow | 25000 50000 25000 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
decagon | 105146 | 19.098 19.098 180.902 80.902 ; 9.549 38.197 90.451 161.803 ; 13.751 27.502 130.249 116.498
|
||||
diagStripe | 50000 | 0.000 0.000 150.000 75.000 ; 0.000 0.000 75.000 150.000 ; 0.000 0.000 108.000 108.000
|
||||
diamond | | 50.000 25.000 150.000 75.000 ; 25.000 50.000 75.000 150.000 ; 36.000 36.000 108.000 108.000
|
||||
dodecagon | | 26.796 13.398 173.204 86.602 ; 13.398 26.796 86.602 173.204 ; 19.293 19.293 124.707 124.707
|
||||
donut | 25000 | 29.289 14.645 170.711 85.355 ; 14.645 29.289 85.355 170.711 ; 21.088 21.088 122.912 122.912
|
||||
doubleWave | 6250 0 | 0.000 12.500 200.000 87.500 ; 0.000 25.000 100.000 175.000 ; 0.000 18.000 144.000 126.000
|
||||
downArrow | 50000 50000 | 50.000 0.000 150.000 75.000 ; 25.000 0.000 75.000 175.000 ; 36.000 0.000 108.000 108.000
|
||||
downArrowCallout | 25000 25000 25000 64977 | 0.000 0.000 200.000 64.977 ; 0.000 0.000 100.000 129.954 ; 0.000 0.000 144.000 93.567
|
||||
ellipse | | 29.289 14.645 170.711 85.355 ; 14.645 29.289 85.355 170.711 ; 21.088 21.088 122.912 122.912
|
||||
ellipseRibbon | 25000 50000 12500 | 50.000 25.000 150.000 96.875 ; 25.000 50.000 75.000 193.750 ; 36.000 36.000 108.000 139.500
|
||||
ellipseRibbon2 | 25000 50000 12500 | 50.000 3.125 150.000 75.000 ; 25.000 6.250 75.000 150.000 ; 36.000 4.500 108.000 108.000
|
||||
flowChartAlternateProcess | | 4.882 4.882 195.119 95.118 ; 4.882 4.882 95.118 195.119 ; 7.029 7.029 136.971 136.971
|
||||
flowChartCollate | | 50.000 25.000 150.000 75.000 ; 25.000 50.000 75.000 150.000 ; 36.000 36.000 108.000 108.000
|
||||
flowChartConnector | | 29.289 14.645 170.711 85.355 ; 14.645 29.289 85.355 170.711 ; 21.088 21.088 122.912 122.912
|
||||
flowChartDecision | | 50.000 25.000 150.000 75.000 ; 25.000 50.000 75.000 150.000 ; 36.000 36.000 108.000 108.000
|
||||
flowChartDelay | | 0.000 14.645 170.711 85.355 ; 0.000 29.289 85.355 170.711 ; 0.000 21.088 122.912 122.912
|
||||
flowChartDisplay | | 33.333 0.000 166.667 100.000 ; 16.667 0.000 83.333 200.000 ; 24.000 0.000 120.000 144.000
|
||||
flowChartDocument | | 0.000 0.000 200.000 80.194 ; 0.000 0.000 100.000 160.389 ; 0.000 0.000 144.000 115.480
|
||||
flowChartExtract | | 50.000 50.000 150.000 100.000 ; 25.000 100.000 75.000 200.000 ; 36.000 72.000 108.000 144.000
|
||||
flowChartInputOutput | | 40.000 0.000 160.000 100.000 ; 20.000 0.000 80.000 200.000 ; 28.800 0.000 115.200 144.000
|
||||
flowChartInternalStorage | | 25.000 12.500 200.000 100.000 ; 12.500 25.000 100.000 200.000 ; 18.000 18.000 144.000 144.000
|
||||
flowChartMagneticDisk | | 0.000 33.333 200.000 83.333 ; 0.000 66.667 100.000 166.667 ; 0.000 48.000 144.000 120.000
|
||||
flowChartMagneticDrum | | 33.333 0.000 133.333 100.000 ; 16.667 0.000 66.667 200.000 ; 24.000 0.000 96.000 144.000
|
||||
flowChartMagneticTape | | 29.289 14.645 170.711 85.355 ; 14.645 29.289 85.355 170.711 ; 21.088 21.088 122.912 122.912
|
||||
flowChartManualInput | | 0.000 20.000 200.000 100.000 ; 0.000 40.000 100.000 200.000 ; 0.000 28.800 144.000 144.000
|
||||
flowChartManualOperation | | 40.000 0.000 160.000 100.000 ; 20.000 0.000 80.000 200.000 ; 28.800 0.000 115.200 144.000
|
||||
flowChartMerge | | 50.000 0.000 150.000 50.000 ; 25.000 0.000 75.000 100.000 ; 36.000 0.000 108.000 72.000
|
||||
flowChartMultidocument | | 0.000 17.014 172.176 96.213 ; 0.000 34.028 86.088 192.426 ; 0.000 24.500 123.967 138.547
|
||||
flowChartOfflineStorage | | 50.000 0.000 150.000 50.000 ; 25.000 0.000 75.000 100.000 ; 36.000 0.000 108.000 72.000
|
||||
flowChartOffpageConnector | | 0.000 0.000 200.000 80.000 ; 0.000 0.000 100.000 160.000 ; 0.000 0.000 144.000 115.200
|
||||
flowChartOnlineStorage | | 33.333 0.000 166.667 100.000 ; 16.667 0.000 83.333 200.000 ; 24.000 0.000 120.000 144.000
|
||||
flowChartOr | | 29.289 14.645 170.711 85.355 ; 14.645 29.289 85.355 170.711 ; 21.088 21.088 122.912 122.912
|
||||
flowChartPredefinedProcess | | 25.000 0.000 175.000 100.000 ; 12.500 0.000 87.500 200.000 ; 18.000 0.000 126.000 144.000
|
||||
flowChartPreparation | | 40.000 0.000 160.000 100.000 ; 20.000 0.000 80.000 200.000 ; 28.800 0.000 115.200 144.000
|
||||
flowChartProcess | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
flowChartPunchedCard | | 0.000 20.000 200.000 100.000 ; 0.000 40.000 100.000 200.000 ; 0.000 28.800 144.000 144.000
|
||||
flowChartPunchedTape | | 0.000 20.000 200.000 80.000 ; 0.000 40.000 100.000 160.000 ; 0.000 28.800 144.000 115.200
|
||||
flowChartSort | | 50.000 25.000 150.000 75.000 ; 25.000 50.000 75.000 150.000 ; 36.000 36.000 108.000 108.000
|
||||
flowChartSummingJunction | | 29.289 14.645 170.711 85.355 ; 14.645 29.289 85.355 170.711 ; 21.088 21.088 122.912 122.912
|
||||
flowChartTerminator | | 9.426 14.644 190.574 85.356 ; 4.713 29.287 95.287 170.713 ; 6.787 21.087 137.213 122.913
|
||||
foldedCorner | 16667 | 0.000 0.000 200.000 83.333 ; 0.000 0.000 100.000 183.333 ; 0.000 0.000 144.000 120.000
|
||||
frame | 12500 | 12.500 12.500 187.500 87.500 ; 12.500 12.500 87.500 187.500 ; 18.000 18.000 126.000 126.000
|
||||
funnel | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
gear6 | 15000 3526 | 39.711 25.327 160.289 74.673 ; 25.175 40.081 74.825 159.919 ; 36.252 36.472 107.748 107.528
|
||||
gear9 | 10000 1763 | 32.735 23.425 167.265 74.827 ; 20.104 40.205 79.896 155.860 ; 28.950 33.731 115.050 107.750
|
||||
halfFrame | 33333 33333 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
heart | | 33.333 25.000 166.667 66.667 ; 16.667 50.000 83.333 133.333 ; 24.000 36.000 120.000 96.000
|
||||
heptagon | 102572 105210 | 19.806 19.806 180.194 80.194 ; 9.903 39.613 90.097 160.387 ; 14.260 28.521 129.740 115.479
|
||||
hexagon | 25000 115470 | 25.000 12.500 175.000 87.500 ; 16.667 33.333 83.333 166.667 ; 24.000 24.000 120.000 120.000
|
||||
homePlate | 50000 | 0.000 0.000 175.000 100.000 ; 0.000 0.000 75.000 200.000 ; 0.000 0.000 108.000 144.000
|
||||
horizontalScroll | 12500 | 12.500 12.500 193.750 87.500 ; 12.500 12.500 93.750 187.500 ; 18.000 18.000 135.000 126.000
|
||||
irregularSeal1 | | 42.843 29.259 154.648 64.523 ; 21.421 58.519 77.324 129.046 ; 30.847 42.133 111.347 92.913
|
||||
irregularSeal2 | | 49.741 29.546 135.556 73.773 ; 24.870 59.093 67.778 147.546 ; 35.813 42.547 97.600 106.233
|
||||
leftArrow | 50000 50000 | 25.000 25.000 200.000 75.000 ; 25.000 50.000 100.000 150.000 ; 36.000 36.000 144.000 108.000
|
||||
leftArrowCallout | 25000 25000 25000 64977 | 70.046 0.000 200.000 100.000 ; 35.023 0.000 100.000 200.000 ; 50.433 0.000 144.000 144.000
|
||||
leftBrace | 8333 50000 | 129.289 2.441 200.000 97.559 ; 64.645 2.441 100.000 197.559 ; 93.088 3.515 144.000 140.485
|
||||
leftBracket | 8333 | 58.579 2.441 200.000 97.559 ; 29.289 2.441 100.000 197.559 ; 42.177 3.515 144.000 140.485
|
||||
leftCircularArrow | 12500 -1142319 1142319 10800000 12500 | 33.709 19.064 166.291 80.936 ; 19.064 33.709 80.936 166.291 ; 27.452 27.452 116.548 116.548
|
||||
leftRightArrow | 50000 50000 | 25.000 25.000 175.000 75.000 ; 25.000 50.000 75.000 150.000 ; 36.000 36.000 108.000 108.000
|
||||
leftRightArrowCallout | 25000 25000 25000 48123 | 51.877 0.000 148.123 100.000 ; 25.939 0.000 74.061 200.000 ; 37.351 0.000 106.649 144.000
|
||||
leftRightCircularArrow | 12500 1142319 20457681 11942319 12500 | 33.709 19.064 166.291 80.936 ; 19.064 33.709 80.936 166.291 ; 27.452 27.452 116.548 116.548
|
||||
leftRightRibbon | 50000 50000 16667 | 50.000 16.666 150.000 83.334 ; 46.875 33.333 53.125 166.667 ; 67.500 24.000 76.500 120.000
|
||||
leftRightUpArrow | 25000 25000 25000 | 12.500 62.500 187.500 87.500 ; 12.500 162.500 87.500 187.500 ; 18.000 90.000 126.000 126.000
|
||||
leftUpArrow | 25000 25000 25000 | 12.500 62.500 175.000 87.500 ; 12.500 162.500 75.000 187.500 ; 18.000 90.000 108.000 126.000
|
||||
lightningBolt | | 81.083 34.431 128.861 66.097 ; 40.542 68.861 64.431 132.194 ; 58.380 49.580 92.780 95.180
|
||||
line | | 0 0 200 100 ; 0 0 100 200 ; 0 0 144 144
|
||||
lineInv | | 0 0 200 100 ; 0 0 100 200 ; 0 0 144 144
|
||||
mathDivide | 23520 5880 11760 | 26.510 38.240 173.490 61.760 ; 13.255 76.480 86.745 123.520 ; 19.087 55.066 124.913 88.934
|
||||
mathEqual | 23520 11760 | 26.510 20.600 173.490 79.400 ; 13.255 41.200 86.745 158.800 ; 19.087 29.664 124.913 114.336
|
||||
mathMinus | 23520 | 26.510 38.240 173.490 61.760 ; 13.255 76.480 86.745 123.520 ; 19.087 55.066 124.913 88.934
|
||||
mathMultiply | 23520 | 42.776 13.499 157.224 86.501 ; 13.499 42.776 86.501 157.224 ; 22.611 22.611 121.389 121.389
|
||||
mathNotEqual | 23520 6600000 11760 | 26.510 20.600 173.490 79.400 ; 13.255 41.200 86.745 158.800 ; 19.087 29.664 124.913 114.336
|
||||
mathPlus | 23520 | 26.510 38.240 173.490 61.760 ; 13.255 88.240 86.745 111.760 ; 19.087 55.066 124.913 88.934
|
||||
moon | 50000 | 29.291 23.949 100.000 76.051 ; 14.645 47.898 50.000 152.102 ; 21.089 34.487 72.000 109.513
|
||||
nonIsoscelesTrapezoid | 25000 25000 | 16.667 8.333 183.333 100.000 ; 16.667 33.333 83.333 200.000 ; 24.000 24.000 120.000 144.000
|
||||
noSmoking | 18750 | 29.289 14.645 170.711 85.355 ; 14.645 29.289 85.355 170.711 ; 21.088 21.088 122.912 122.912
|
||||
notchedRightArrow | 50000 50000 | 25.000 25.000 175.000 75.000 ; 25.000 50.000 75.000 150.000 ; 36.000 36.000 108.000 108.000
|
||||
octagon | 29289 | 14.645 14.645 185.356 85.356 ; 14.645 14.645 85.356 185.356 ; 21.088 21.088 122.912 122.912
|
||||
parallelogram | 25000 | 27.083 13.542 172.917 86.458 ; 18.750 37.500 81.250 162.500 ; 27.000 27.000 117.000 117.000
|
||||
pentagon | 105146 110557 | 38.197 23.607 161.803 100.000 ; 19.098 47.213 80.902 199.999 ; 27.502 33.994 116.498 144.000
|
||||
pie | 0 16200000 | 29.289 14.645 170.711 85.355 ; 14.645 29.289 85.355 170.711 ; 21.088 21.088 122.912 122.912
|
||||
pieWedge | | 58.579 29.289 200.000 100.000 ; 29.289 58.579 100.000 200.000 ; 42.177 42.177 144.000 144.000
|
||||
plaque | 16667 | 11.785 11.785 188.215 88.215 ; 11.785 11.785 88.215 188.215 ; 16.971 16.971 127.029 127.029
|
||||
plaqueTabs | | 11.180 11.180 188.820 88.820 ; 11.180 11.180 88.820 188.820 ; 10.182 10.182 133.818 133.818
|
||||
plus | 25000 | 0.000 25.000 200.000 75.000 ; 25.000 0.000 75.000 200.000 ; 36.000 0.000 108.000 144.000
|
||||
quadArrow | 22500 22500 22500 | 11.250 38.750 188.750 61.250 ; 11.250 88.750 88.750 111.250 ; 16.200 55.800 127.800 88.200
|
||||
quadArrowCallout | 18515 18515 18515 48123 | 51.877 25.939 148.123 74.061 ; 25.939 51.877 74.061 148.123 ; 37.351 37.351 106.649 106.649
|
||||
rect | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
ribbon | 16667 50000 | 50.000 16.667 150.000 100.000 ; 25.000 33.334 75.000 200.000 ; 36.000 24.000 108.000 144.000
|
||||
ribbon2 | 16667 50000 | 50.000 0.000 150.000 83.333 ; 25.000 0.000 75.000 166.666 ; 36.000 0.000 108.000 120.000
|
||||
rightArrow | 50000 50000 | 0.000 25.000 175.000 75.000 ; 0.000 50.000 75.000 150.000 ; 0.000 36.000 108.000 108.000
|
||||
rightArrowCallout | 25000 25000 25000 64977 | 0.000 0.000 129.954 100.000 ; 0.000 0.000 64.977 200.000 ; 0.000 0.000 93.567 144.000
|
||||
rightBrace | 8333 50000 | 0.000 2.441 70.711 97.559 ; 0.000 2.441 35.355 197.559 ; 0.000 3.515 50.912 140.485
|
||||
rightBracket | 8333 | 0.000 2.441 141.421 97.559 ; 0.000 2.441 70.711 197.559 ; 0.000 3.515 101.823 140.485
|
||||
round1Rect | 16667 | 0.000 0.000 195.118 100.000 ; 0.000 0.000 95.118 200.000 ; 0.000 0.000 136.970 144.000
|
||||
round2DiagRect | 16667 0 | 4.882 4.882 195.118 95.118 ; 4.882 4.882 95.118 195.118 ; 7.030 7.030 136.970 136.970
|
||||
round2SameRect | 16667 0 | 4.882 4.882 195.118 100.000 ; 4.882 4.882 95.118 200.000 ; 7.030 7.030 136.970 144.000
|
||||
roundRect | 16667 | 4.882 4.882 195.118 95.118 ; 4.882 4.882 95.118 195.118 ; 7.030 7.030 136.970 136.970
|
||||
rtTriangle | | 16.667 58.333 116.667 91.667 ; 8.333 116.667 58.333 183.333 ; 12.000 84.000 84.000 132.000
|
||||
smileyFace | 4653 | 29.289 14.645 170.711 85.355 ; 14.645 29.289 85.355 170.711 ; 21.088 21.088 122.912 122.912
|
||||
snip1Rect | 16667 | 0.000 8.334 191.666 100.000 ; 0.000 8.334 91.666 200.000 ; 0.000 12.000 132.000 144.000
|
||||
snip2DiagRect | 0 16667 | 8.334 8.334 191.666 91.666 ; 8.334 8.334 91.666 191.666 ; 12.000 12.000 132.000 132.000
|
||||
snip2SameRect | 16667 0 | 8.334 8.334 191.666 100.000 ; 8.334 8.334 91.666 200.000 ; 12.000 12.000 132.000 144.000
|
||||
snipRoundRect | 16667 16667 | 4.882 4.882 191.666 100.000 ; 4.882 4.882 91.666 200.000 ; 7.030 7.030 132.000 144.000
|
||||
squareTabs | | 11.180 11.180 188.820 88.820 ; 11.180 11.180 88.820 188.820 ; 10.182 10.182 133.818 133.818
|
||||
star10 | 42533 105146 | 27.638 25.000 172.362 75.000 ; 13.819 49.999 86.181 150.001 ; 19.900 35.999 124.100 108.001
|
||||
star12 | 37500 | 46.967 23.483 153.033 76.517 ; 23.483 46.967 76.517 153.033 ; 33.816 33.816 110.184 110.184
|
||||
star16 | 37500 | 46.967 23.483 153.033 76.517 ; 23.483 46.967 76.517 153.033 ; 33.816 33.816 110.184 110.184
|
||||
star24 | 37500 | 46.967 23.483 153.033 76.517 ; 23.483 46.967 76.517 153.033 ; 33.816 33.816 110.184 110.184
|
||||
star32 | 37500 | 46.967 23.483 153.033 76.517 ; 23.483 46.967 76.517 153.033 ; 33.816 33.816 110.184 110.184
|
||||
star4 | 12500 | 82.322 41.161 117.678 58.839 ; 41.161 82.322 58.839 117.678 ; 59.272 59.272 84.728 84.728
|
||||
star5 | 19098 105146 110557 | 61.804 38.197 138.196 76.393 ; 30.902 76.394 69.098 152.785 ; 44.499 55.003 99.501 110.005
|
||||
star6 | 28868 115470 | 33.332 25.000 166.668 75.000 ; 16.666 49.999 83.334 150.001 ; 23.999 35.999 120.001 108.001
|
||||
star7 | 34601 102572 105210 | 44.504 19.806 155.496 75.302 ; 22.252 39.613 77.748 150.605 ; 32.043 28.521 111.957 108.435
|
||||
star8 | 37500 | 30.709 15.355 169.291 84.645 ; 15.355 30.709 84.645 169.291 ; 22.110 22.110 121.890 121.890
|
||||
straightConnector1 | | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
stripedRightArrow | 50000 50000 | 15.625 25.000 175.000 75.000 ; 15.625 50.000 75.000 150.000 ; 22.500 36.000 108.000 108.000
|
||||
sun | 25000 | 64.645 32.323 135.355 67.677 ; 32.323 64.645 67.677 135.355 ; 46.545 46.545 97.455 97.455
|
||||
swooshArrow | 25000 16667 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
teardrop | 100000 | 29.289 14.645 170.711 85.355 ; 14.645 29.289 85.355 170.711 ; 21.088 21.088 122.912 122.912
|
||||
trapezoid | 25000 | 16.667 8.333 183.333 100.000 ; 16.667 33.333 83.333 200.000 ; 24.000 24.000 120.000 144.000
|
||||
triangle | 50000 | 50.000 50.000 150.000 100.000 ; 25.000 100.000 75.000 200.000 ; 36.000 72.000 108.000 144.000
|
||||
upArrowCallout | 25000 25000 25000 64977 | 0.000 35.023 200.000 100.000 ; 0.000 70.046 100.000 200.000 ; 0.000 50.433 144.000 144.000
|
||||
upDownArrow | 50000 50000 | 50.000 25.000 150.000 75.000 ; 25.000 25.000 75.000 175.000 ; 36.000 36.000 108.000 108.000
|
||||
upArrow | 50000 50000 | 50.000 25.000 150.000 100.000 ; 25.000 25.000 75.000 200.000 ; 36.000 36.000 108.000 144.000
|
||||
upDownArrowCallout | 25000 25000 25000 48123 | 0.000 25.939 200.000 74.061 ; 0.000 51.877 100.000 148.123 ; 0.000 37.351 144.000 106.649
|
||||
uturnArrow | 25000 25000 25000 43750 75000 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
verticalScroll | 12500 | 12.500 12.500 187.500 93.750 ; 12.500 12.500 87.500 193.750 ; 18.000 18.000 126.000 135.000
|
||||
wave | 12500 0 | 0.000 25.000 200.000 75.000 ; 0.000 50.000 100.000 150.000 ; 0.000 36.000 144.000 108.000
|
||||
wedgeEllipseCallout | -20833 62500 | 29.289 14.645 170.711 85.355 ; 14.645 29.289 85.355 170.711 ; 21.088 21.088 122.912 122.912
|
||||
wedgeRectCallout | -20833 62500 | 0.000 0.000 200.000 100.000 ; 0.000 0.000 100.000 200.000 ; 0.000 0.000 144.000 144.000
|
||||
wedgeRoundRectCallout | -20833 62500 16667 | 4.882 4.882 195.118 95.118 ; 4.882 4.882 95.118 195.118 ; 7.030 7.030 136.970 136.970
|
||||
@@ -0,0 +1,46 @@
|
||||
//! Every preset against ECMA-376's presetShapeDefinitions.xml, evaluated by
|
||||
//! `fixtures/make_preset_table.py`: default adjust values and the text rectangle at three sizes.
|
||||
|
||||
use craft_drawingml::preset::{adjust_defaults as defaults, build, info};
|
||||
|
||||
#[test]
|
||||
fn presets_follow_preset_shape_definitions() {
|
||||
let table = include_str!("fixtures/preset_table.txt");
|
||||
let sizes = [(200.0, 100.0), (100.0, 200.0), (144.0, 144.0)];
|
||||
let mut bad = vec![];
|
||||
let mut checked = 0;
|
||||
for line in table.lines().filter(|l| !l.starts_with('#') && !l.trim().is_empty()) {
|
||||
let f: Vec<&str> = line.split(" | ").collect();
|
||||
let name = f[0];
|
||||
// Connectors the gallery doesn't list still build, with the standard's defaults.
|
||||
if info(name).is_none() && !name.contains("Connector") {
|
||||
bad.push(format!("{name}: not in the catalogue"));
|
||||
continue;
|
||||
}
|
||||
let have = defaults(name);
|
||||
checked += 1;
|
||||
let want: Vec<f64> = f[1].split_whitespace().map(|v| v.parse().unwrap()).collect();
|
||||
// The standard lists stretch factors (star5's hf/vf…) after the adjust handles; the
|
||||
// catalogue keeps handles only, so compare what both have.
|
||||
for (k, (a, b)) in have.iter().zip(&want).enumerate() {
|
||||
if (a - b).abs() > 0.5 {
|
||||
bad.push(format!("{name}: adj{} default {a} (standard {b})", k + 1));
|
||||
}
|
||||
}
|
||||
let count_mismatch = if name.contains("Connector") { have.len() != want.len() } else { have.len() > want.len() };
|
||||
if count_mismatch {
|
||||
bad.push(format!("{name}: {} defaults (standard {})", have.len(), want.len()));
|
||||
}
|
||||
for ((w, h), r) in sizes.iter().zip(f[2].split(" ; ")) {
|
||||
let r: Vec<f64> = r.split_whitespace().map(|v| v.parse().unwrap()).collect();
|
||||
let g = build(name, *w, *h, &[]).unwrap_or_else(|| panic!("{name} builds"));
|
||||
let t = g.text_rect;
|
||||
let got = [t.x0, t.y0, t.x1, t.y1];
|
||||
if got.iter().zip(&r).any(|(a, b)| (a - b).abs() > 0.6) {
|
||||
bad.push(format!("{name} {w}x{h}: text rect {got:.1?} (standard {r:.1?})"));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(checked > 180, "only {checked} presets checked");
|
||||
assert!(bad.is_empty(), "{} mismatches:\n{}", bad.len(), bad.join("\n"));
|
||||
}
|
||||
@@ -123,7 +123,9 @@ pub fn geometry(w: &mut Writer, g: &Geom) {
|
||||
w.empty("a:avLst", &[]);
|
||||
w.close();
|
||||
} else {
|
||||
craft_drawingml::custgeom::write_cust_geom(w, paths);
|
||||
// `craft-pptx` does not yet retain custom-geometry connection sites.
|
||||
// Zero extent keeps the previous path-only output until the model carries them.
|
||||
craft_drawingml::custgeom::write_cust_geom(w, paths, &[], (0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user