DOCX: VML freeforms whose path uses formulas aren't drawn

Formulas (@n, from v:formulas) aren't evaluated; reading them as 0 drew stray lines. Parity row states the fill-opacity limit (none at 0, otherwise opaque, as for DrawingML fills).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Mauro Mazzieri
2026-10-13 15:39:39 +02:00
co-authored by Claude Opus 5.5
parent b7a6cc70f9
commit d8fa665a71
2 changed files with 11 additions and 3 deletions
+7 -3
View File
@@ -100,8 +100,12 @@ const VML_TWO: [&str; 11] = ["nf", "ns", "ae", "al", "at", "ar", "wa", "wr", "qx
/// relative), `l`/`r` line, `c`/`v` cubic Bézier (flattened), `x` close, `e` end, `nf`/`ns` no
/// fill / no outline; arcs (`at`, `ar`, `wa`, `wr`) and quadrants (`qx`, `qy`) are drawn straight
/// to their end, `qb` straight to its last point, and ellipses (`ae`, `al`) are skipped. An empty
/// parameter is 0, and so is a formula reference (`@n`). `None` when the path has no points.
/// parameter is 0. `None` when the path has no points, or uses formulas (`@n`, from `v:formulas`,
/// which aren't evaluated): such a shape isn't drawn rather than drawn wrong.
pub fn vml_path(path: &str, coordsize: Option<&str>, coordorigin: Option<&str>, w: f32, h: f32) -> Option<VmlPath> {
if path.contains('@') {
return None;
}
let pair = |v: Option<&str>| -> Option<(f64, f64)> {
let v = v?;
let (a, b) = v.split_once(',').unwrap_or((v, ""));
@@ -227,8 +231,8 @@ impl Draw {
/// The commands of a VML path with their parameters: a command is a letter (or one of
/// [`VML_TWO`]) followed by numbers separated by commas or spaces. An empty parameter (nothing
/// before a comma) is 0, and so is a formula reference (`@n`); fractions are dropped. Anything
/// else is skipped.
/// before a comma) is 0, and so is a formula reference (`@n`, not expected here: see
/// [`vml_path`]); fractions are dropped. Anything else is skipped.
fn vml_commands(path: &str) -> Vec<(String, Vec<f64>)> {
let mut out: Vec<(String, Vec<f64>)> = Vec::new();
let mut chars = path.chars().take(MAX_VML_PATH).peekable();
+4
View File
@@ -2149,6 +2149,10 @@ fn hostile_vml_paths_stay_finite() {
r#"<w:p><w:r><w:pict><v:shape style="width:1e30pt;height:-4pt" coordsize="{size}" coordorigin="{origin}" path="{path}" strokecolor="black"/></w:pict></w:r></w:p>"#
);
let d = read_body(&body);
// A path with formulas isn't drawn (they aren't evaluated).
if path.contains('@') {
assert!(paras(&d)[0].objects.iter().all(|o| !matches!(o, InlineObject::Shape { freeform: Some(_), .. })), "{path}");
}
for o in &paras(&d)[0].objects {
if let InlineObject::Shape { w, h, freeform: Some(f), .. } = o {
assert!(w.is_finite() && h.is_finite() && f.alpha.is_finite(), "{path}");