fix(annot): preserve strokes with malformed interior colors (#708)
This commit is contained in:
parent
997b82d49c
commit
fb548a7064
|
|
@ -170,6 +170,10 @@ fn color(d: &Dict, key: &[u8]) -> Option<Option<Rgb>> {
|
|||
})
|
||||
}
|
||||
|
||||
fn interior_color(d: &Dict, fallback: Option<Rgb>) -> Option<Rgb> {
|
||||
color(d, b"IC").flatten().or(fallback)
|
||||
}
|
||||
|
||||
fn border_width(d: &Dict) -> f64 {
|
||||
if let Some(w) = d.get(b"BS").and_then(|b| b.as_dict()).and_then(|b| b.get(b"W")).and_then(|w| w.as_f64()) {
|
||||
return w.max(0.0);
|
||||
|
|
@ -369,7 +373,7 @@ pub fn build(d: &Dict) -> Option<Stream> {
|
|||
}
|
||||
}
|
||||
b"Square" | b"Circle" => {
|
||||
let fill = color(d, b"IC")?;
|
||||
let fill = interior_color(d, None);
|
||||
if stroke.is_none() && fill.is_none() {
|
||||
return Some(form(rect, c.as_bytes(), res));
|
||||
}
|
||||
|
|
@ -403,7 +407,7 @@ pub fn build(d: &Dict) -> Option<Stream> {
|
|||
if ends.len() != 2 || ends.iter().any(|e| !known_ending(e)) {
|
||||
return None;
|
||||
}
|
||||
let fill = color(d, b"IC")?.unwrap_or(col);
|
||||
let fill = interior_color(d, Some(col)).unwrap_or(col);
|
||||
c.push_str(&format!("{}{}{} w 1 J 1 j\n{}", rg_stroke(col), rg(fill), n(w), dash(d)));
|
||||
c.push_str(&format!("{} {} m {} {} l S\n[] 0 d\n", n(l[0]), n(l[1]), n(l[2]), n(l[3])));
|
||||
for (end, (tip, from)) in ends.iter().zip([((l[0], l[1]), (l[2], l[3])), ((l[2], l[3]), (l[0], l[1]))]) {
|
||||
|
|
@ -417,7 +421,7 @@ pub fn build(d: &Dict) -> Option<Stream> {
|
|||
let pts: Vec<(f64, f64)> = v.as_chunks::<2>().0.iter().map(|p| (p[0], p[1])).collect();
|
||||
let col = stroke;
|
||||
let closed = subtype == b"Polygon";
|
||||
let fill = if closed { color(d, b"IC")? } else { None };
|
||||
let fill = if closed { interior_color(d, None) } else { None };
|
||||
let ends = if subtype == b"PolyLine" {
|
||||
match d.get(b"LE") {
|
||||
None => None,
|
||||
|
|
@ -461,7 +465,7 @@ pub fn build(d: &Dict) -> Option<Stream> {
|
|||
let (Some(start), Some(end)) = (ends.first(), ends.get(1)) else { return None };
|
||||
let (Some(&tip0), Some(&from0)) = (pts.first(), pts.get(1)) else { return None };
|
||||
let (Some(&tip1), Some(&from1)) = (pts.last(), pts.get(pts.len().saturating_sub(2))) else { return None };
|
||||
let fill = color(d, b"IC")?.unwrap_or(col);
|
||||
let fill = interior_color(d, Some(col)).unwrap_or(col);
|
||||
c.push_str(&format!("[] 0 d\n{}", rg(fill)));
|
||||
if !line_end(&mut c, start, tip0, from0, w) || !line_end(&mut c, end, tip1, from1, w) {
|
||||
return None;
|
||||
|
|
|
|||
|
|
@ -787,6 +787,51 @@ fn line_dict(coords: [f64; 4], ending: &str) -> pdfcraft_cos::Dict {
|
|||
d
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_optional_interior_color_does_not_hide_shape_strokes() {
|
||||
let mut square = pdfcraft_cos::Dict::new();
|
||||
square.set(b"Subtype".to_vec(), Object::name("Square"));
|
||||
square.set(b"Rect".to_vec(), Object::Array([0.0, 0.0, 40.0, 30.0].into_iter().map(Object::Real).collect()));
|
||||
square.set(b"C".to_vec(), Object::Array([1.0, 0.0, 0.0].into_iter().map(Object::Real).collect()));
|
||||
let malformed = Object::Array(vec![Object::Real(1.0), Object::Real(0.0)]);
|
||||
square.set(b"IC".to_vec(), malformed.clone());
|
||||
let mut circle = square.clone();
|
||||
circle.set(b"Subtype".to_vec(), Object::name("Circle"));
|
||||
|
||||
let mut polygon = pdfcraft_cos::Dict::new();
|
||||
polygon.set(b"Subtype".to_vec(), Object::name("Polygon"));
|
||||
polygon.set(b"Rect".to_vec(), Object::Array([0.0, 0.0, 40.0, 30.0].into_iter().map(Object::Real).collect()));
|
||||
polygon.set(b"C".to_vec(), Object::Array([1.0, 0.0, 0.0].into_iter().map(Object::Real).collect()));
|
||||
polygon.set(b"Vertices".to_vec(), Object::Array([0.0, 0.0, 40.0, 0.0, 20.0, 30.0].into_iter().map(Object::Real).collect()));
|
||||
polygon.set(b"IC".to_vec(), malformed.clone());
|
||||
|
||||
let mut line = line_dict([0.0, 0.0, 40.0, 30.0], "None");
|
||||
line.set(b"IC".to_vec(), malformed.clone());
|
||||
let mut polyline = pdfcraft_cos::Dict::new();
|
||||
polyline.set(b"Subtype".to_vec(), Object::name("PolyLine"));
|
||||
polyline.set(b"Rect".to_vec(), Object::Array([0.0, 0.0, 40.0, 30.0].into_iter().map(Object::Real).collect()));
|
||||
polyline.set(b"C".to_vec(), Object::Array([1.0, 0.0, 0.0].into_iter().map(Object::Real).collect()));
|
||||
polyline.set(b"Vertices".to_vec(), Object::Array([0.0, 0.0, 40.0, 0.0, 20.0, 30.0].into_iter().map(Object::Real).collect()));
|
||||
polyline.set(b"LE".to_vec(), Object::Array(vec![Object::name("None"), Object::name("None")]));
|
||||
polyline.set(b"IC".to_vec(), malformed);
|
||||
|
||||
for (name, annotation) in [("Square", square), ("Circle", circle), ("Polygon", polygon), ("Line", line), ("PolyLine", polyline)] {
|
||||
assert!(appearance::build(&annotation).is_some(), "malformed optional /IC must not hide a valid {name} stroke");
|
||||
}
|
||||
|
||||
let doc = document_of(&[
|
||||
b"<< /Type /Catalog /Pages 2 0 R >>",
|
||||
b"<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 50 40] >>",
|
||||
b"<< /Type /Page /Parent 2 0 R /Annots [4 0 R] >>",
|
||||
b"<< /Type /Annot /Subtype /Square /Rect [5 5 45 35] /C [1 0 0] /IC [1 0] >>",
|
||||
]);
|
||||
let display = with_missing_appearances(&doc).expect("the valid square gets a fallback appearance");
|
||||
let rendered = list(&display, 0).remove(0);
|
||||
let content = ap_content(&display, &rendered);
|
||||
assert!(content.contains("1 0 0 RG") && content.contains(" re\nS\n"), "the stroke remains drawable without a fill: {content}");
|
||||
assert!(list(&doc, 0)[0].get(b"AP").is_none(), "display generation leaves the source document unchanged");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_line_ending_is_drawn() {
|
||||
let filled = [LineEnding::Square, LineEnding::Circle, LineEnding::Diamond, LineEnding::ClosedArrow, LineEnding::RClosedArrow];
|
||||
|
|
|
|||
Loading…
Reference in New Issue