1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//! Linear-gradient fill → Pattern Type 2 / Function Type 2 stitching.
use oxideav_core::time::TimeBase;
use oxideav_core::vector::{
FillRule, GradientStop, Group, LinearGradient, Node, Paint, Path, PathNode, Point, Rgba,
SpreadMethod, VectorFrame,
};
#[test]
fn linear_gradient_emits_pattern_type_2_function_type_2() {
let mut p = Path::new();
p.move_to(Point::new(0.0, 0.0))
.line_to(Point::new(100.0, 0.0))
.line_to(Point::new(100.0, 50.0))
.line_to(Point::new(0.0, 50.0))
.close();
let gradient = LinearGradient {
start: Point::new(0.0, 0.0),
end: Point::new(100.0, 0.0),
stops: vec![
GradientStop::new(0.0, Rgba::opaque(255, 0, 0)),
GradientStop::new(1.0, Rgba::opaque(0, 0, 255)),
],
spread: SpreadMethod::Pad,
};
let frame = VectorFrame {
width: 100.0,
height: 50.0,
view_box: None,
root: Group {
children: vec![Node::Path(PathNode {
path: p,
fill: Some(Paint::LinearGradient(gradient)),
stroke: None,
fill_rule: FillRule::NonZero,
})],
..Group::default()
},
pts: None,
time_base: TimeBase::new(1, 1),
};
let bytes = oxideav_pdf::write_pdf(&frame).unwrap();
// The PDF binary marker (`%\xE2\xE3\xCF\xD3`) sits in the header
// and is not valid UTF-8 — `from_utf8` would fail. The marker is
// the only non-UTF-8 byte run in a round-1 vector PDF (everything
// else is plain ASCII operators), so a lossy conversion is fine
// for the assertion strings we're searching for.
let s = String::from_utf8_lossy(&bytes);
// Pattern resource registered.
assert!(s.contains("/Pattern"), "expected /Pattern resource");
assert!(
s.contains("/PatternType 2"),
"expected PatternType 2 (shading pattern)"
);
// Shading is type 2 (axial) for linear gradients.
assert!(s.contains("/ShadingType 2"));
// The two-stop function: one Type-2 segment + a Type-3 stitcher.
// (Two stops collapse to a single function call but the writer
// wraps it in a Type-3 stitch even with only one segment to keep
// the multi-stop and two-stop paths uniform.)
assert!(s.contains("/FunctionType 2"));
assert!(s.contains("/FunctionType 3"));
// The content stream should select the Pattern colour space and
// then name the pattern resource.
assert!(s.contains("/Pattern cs"));
assert!(s.contains("/Pat0 scn"));
// Even-odd vs non-zero fill rule round-trip — gradient fill
// defaults to non-zero -> `f`.
assert!(s.contains("\nf\n"));
}