M1: distinguish unresolved vector segment style metadata in .fig import

The vector network decoder now keeps each segment's style id. Import reports a segment style that names a row of the layer's style table as metadata that was not interpreted, and one that names no row as a reference to a missing style entry, each at most once per file. The earlier note claimed that stroke styles were left out, which overstated what is known about the metadata. Segment geometry, paint and width are unchanged, and the style ids are not kept in the native vector model.
This commit is contained in:
Skyfuller1303
2026-10-17 13:45:55 +07:00
committed by bflatastic
parent d2606b3f51
commit d9e79d604b
4 changed files with 83 additions and 25 deletions
+7 -1
View File
@@ -1669,7 +1669,13 @@ impl<'d> Exporter<'d> {
let segments = net
.segments
.iter()
.map(|sg| network::Segment { a: sg.a, ta: (finite(sg.ta.0), finite(sg.ta.1)), b: sg.b, tb: (finite(sg.tb.0), finite(sg.tb.1)) })
.map(|sg| network::Segment {
style: 0,
a: sg.a,
ta: (finite(sg.ta.0), finite(sg.ta.1)),
b: sg.b,
tb: (finite(sg.tb.0), finite(sg.tb.1)),
})
.collect();
let regions = net
.regions
+20 -5
View File
@@ -1192,12 +1192,29 @@ impl<'a> Mapper<'a> {
fn network(&mut self, o: &mut Obj, v: &View<'a>) -> bool {
let Some(vd) = v.get("vectorData") else { return false };
let Some(blob) = num(vd, "vectorNetworkBlob").and_then(|i| self.blobs.get(i as usize)).copied() else { return false };
let Some((net, styled_segments)) = network::decode_styled(blob) else {
let Some((net, _)) = network::decode_styled(blob) else {
self.warn("damaged vector networks import as outlines");
return false;
};
if styled_segments > 0 {
self.warn("stroke styles of single vector network segments were left out");
let styles: BTreeMap<u64, &Kv> =
vd.get("styleOverrideTable").map(Kv::list).unwrap_or_default().iter().filter_map(|s| Some((num(s, "styleID")? as u64, s))).collect();
let mut has_uninterpreted = false;
let mut has_missing = false;
for segment in &net.segments {
if segment.style == 0 {
continue;
}
if styles.contains_key(&u64::from(segment.style)) {
has_uninterpreted = true;
} else {
has_missing = true;
}
}
if has_uninterpreted {
self.warn("style settings of some vector segments aren't supported; those segments use the layer's stroke");
}
if has_missing {
self.warn("some vector segments refer to style settings missing from the file; they use the layer's stroke");
}
// An open path shows its fill, which regions can't hold: the outline keeps the look.
let fills_shown =
@@ -1205,8 +1222,6 @@ impl<'a> Mapper<'a> {
if net.segments.is_empty() || (net.regions.is_empty() && fills_shown && o.contains_key("fillGeometry")) {
return false;
}
let styles: BTreeMap<u64, &Kv> =
vd.get("styleOverrideTable").map(Kv::list).unwrap_or_default().iter().filter_map(|s| Some((num(s, "styleID")? as u64, s))).collect();
let style = |id: u32| styles.get(&u64::from(id)).copied().filter(|_| id != 0);
let scale = |k: &str| {
let size = v.size().map(|s| if k == "x" { s.0 } else { s.1 });
+6 -5
View File
@@ -17,6 +17,7 @@ pub(super) struct Vertex {
}
pub(super) struct Segment {
pub style: u32,
pub a: usize,
pub ta: (f64, f64),
pub b: usize,
@@ -70,8 +71,7 @@ pub(super) fn decode(b: &[u8]) -> Option<Network> {
decode_styled(b).map(|(net, _)| net)
}
/// The network in blob `b` and how many of its segments have a style id of their own (per-segment
/// stroke overrides, which the document model can't hold); `None` as for `decode`.
/// The network in blob `b` and how many of its segments have a nonzero style id; `None` as for `decode`.
pub(super) fn decode_styled(b: &[u8]) -> Option<(Network, usize)> {
let mut r = Reader { b, pos: 0 };
let (nv, ns, nr) = (r.u32()? as usize, r.u32()? as usize, r.u32()? as usize);
@@ -84,10 +84,11 @@ pub(super) fn decode_styled(b: &[u8]) -> Option<(Network, usize)> {
}
let mut styled = 0usize;
for _ in 0..ns {
if r.u32()? != 0 {
let style = r.u32()?;
if style != 0 {
styled += 1;
}
net.segments.push(Segment { a: r.index(nv)?, ta: (r.f32()?, r.f32()?), b: r.index(nv)?, tb: (r.f32()?, r.f32()?) });
net.segments.push(Segment { style, a: r.index(nv)?, ta: (r.f32()?, r.f32()?), b: r.index(nv)?, tb: (r.f32()?, r.f32()?) });
}
let mut entries = 0usize;
for _ in 0..nr {
@@ -120,7 +121,7 @@ pub(super) fn encode(net: &Network) -> Vec<u8> {
fl(v.y, &mut out);
}
for s in &net.segments {
u(0, &mut out);
u(s.style, &mut out);
u(s.a as u32, &mut out);
fl(s.ta.0, &mut out);
fl(s.ta.1, &mut out);
+50 -14
View File
@@ -9,7 +9,7 @@ use super::super::network::{self, Network, Region, Segment, Vertex};
use super::*;
fn seg(a: usize, b: usize, ta: (f64, f64), tb: (f64, f64)) -> Segment {
Segment { a, ta, b, tb }
Segment { style: 0, a, ta, b, tb }
}
fn vtx(style: u32, x: f64, y: f64) -> Vertex {
@@ -264,21 +264,33 @@ fn vectors_import_as_editable_networks() {
#[test]
fn per_segment_stroke_styles_are_reported() {
let mut blob = square_blob();
// The first segment's style id follows the counts and the vertices (12 bytes each).
blob[12 + 4 * 12..12 + 4 * 12 + 4].copy_from_slice(&7u32.to_le_bytes());
let (net, styled) = network::decode_styled(&blob).unwrap();
assert_eq!((net.segments.len(), styled), (4, 1));
let styled_import = |segment_style: u32| {
let mut blob = square_blob();
// The first segment's style id follows the counts and the vertices (12 bytes each).
blob[12 + 4 * 12..12 + 4 * 12 + 4].copy_from_slice(&segment_style.to_le_bytes());
let (net, styled) = network::decode_styled(&blob).unwrap();
assert_eq!((net.segments.len(), net.segments[0].style, styled), (4, segment_style, 1));
let mut message = tail_message();
let Kv::Object(fields) = &mut message else { panic!() };
let Kv::Array(blobs) = &mut fields.iter_mut().find(|(key, _)| *key == "blobs").unwrap().1 else { panic!() };
blobs[1] = o(vec![("bytes", Kv::Bytes(blob))]);
let (doc, warnings) = import(&canvas(&schema(), &message), "Tail").unwrap();
assert_eq!(network_of(&doc, "Wave").segments.len(), 4);
warnings
};
assert_eq!(network::decode_styled(&square_blob()).unwrap().1, 0);
let plain = import(&tail_canvas(), "Tail").unwrap().1;
assert!(!plain.iter().any(|w| w.contains("stroke styles of single")), "{plain:?}");
let mut message = tail_message();
let Kv::Object(fields) = &mut message else { panic!() };
let Kv::Array(blobs) = &mut fields.iter_mut().find(|(key, _)| *key == "blobs").unwrap().1 else { panic!() };
blobs[1] = o(vec![("bytes", Kv::Bytes(blob))]);
let (doc, warnings) = import(&canvas(&schema(), &message), "Tail").unwrap();
assert!(warnings.iter().any(|w| w.contains("stroke styles of single vector network segments were left out")), "{warnings:?}");
assert_eq!(network_of(&doc, "Wave").segments.len(), 4);
assert!(!plain.iter().any(|w| w.contains("vector segments")), "{plain:?}");
let uninterpreted = |w: &String| w.contains("style settings of some vector segments aren't supported; those segments use the layer's stroke");
let missing = |w: &String| w.contains("some vector segments refer to style settings missing from the file; they use the layer's stroke");
// Style 1 has a row in the layer's override table; its contents are not applied.
let known = styled_import(1);
assert_eq!(known.iter().filter(|w| uninterpreted(w)).count(), 1, "{known:?}");
assert!(!known.iter().any(missing), "{known:?}");
// Style 7 names no row at all.
let unknown = styled_import(7);
assert_eq!(unknown.iter().filter(|w| missing(w)).count(), 1, "{unknown:?}");
assert!(!unknown.iter().any(uninterpreted), "{unknown:?}");
}
#[test]
@@ -385,6 +397,30 @@ fn damaged_network_blobs_never_panic() {
}
}
#[test]
fn segment_styles_round_trip_without_changing_geometry() {
let original = Network {
vertices: vec![vtx(0, 2.0, 3.0), vtx(0, 10.0, 20.0), vtx(0, 40.0, 50.0), vtx(0, 60.0, 70.0)],
segments: vec![
seg(0, 1, (1.0, 2.0), (3.0, 4.0)),
Segment { style: 17, a: 1, ta: (5.0, 6.0), b: 2, tb: (7.0, 8.0) },
Segment { style: 27, a: 2, ta: (9.0, 10.0), b: 3, tb: (11.0, 12.0) },
],
regions: vec![],
};
let bytes = network::encode(&original);
let (decoded, styled) = network::decode_styled(&bytes).unwrap();
assert_eq!(styled, 2);
assert_eq!(decoded.segments.len(), 3);
for (got, want) in decoded.segments.iter().zip(&original.segments) {
assert_eq!(got.style, want.style);
assert_eq!((got.a, got.ta, got.b, got.tb), (want.a, want.ta, want.b, want.tb));
}
for cut in 0..bytes.len() {
assert!(network::decode_styled(&bytes[..cut]).is_none(), "truncated at {cut}");
}
}
#[test]
fn mangled_tail_files_never_panic() {
let c = tail_canvas();