Render: a soft mask shrunk below its size is filtered, so thin strokes drawn through it survive (fixes #1649) (#1654)
This commit is contained in:
parent
f9d7aa72b6
commit
e8fcf76c33
|
|
@ -4827,6 +4827,37 @@ trailer << /Root 1 0 R >>
|
|||
assert_eq!(page.rgba.as_chunks::<4>().0.to_vec(), expected);
|
||||
}
|
||||
|
||||
/// Issue #1649: Word can save text as a tiny black image whose `/SMask` is a high-resolution
|
||||
/// bitmap of the glyphs, so the mask alone carries the text. Shrinking that mask by
|
||||
/// nearest-neighbour keeps one sample in N, and a stroke thinner than N mask pixels vanished:
|
||||
/// the text came out broken and aliased. Here a one-column stroke of an 8-column mask is drawn
|
||||
/// 4 pixels wide; nearest reads columns 1, 3, 5 and 7 and misses the stroke in column 2.
|
||||
#[test]
|
||||
fn shrunk_soft_mask_larger_than_its_image_keeps_thin_strokes() {
|
||||
let mask: [u8; 8] = [0, 0, 255, 0, 0, 0, 0, 0];
|
||||
let content = "q 4 0 0 1 0 0 cm /Im0 Do Q";
|
||||
let pdf = format!(
|
||||
"%PDF-1.4\n1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n\
|
||||
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj\n\
|
||||
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 4 1] /Contents 4 0 R /Resources << /XObject << /Im0 5 0 R >> >> >> endobj\n\
|
||||
4 0 obj << /Length {} >> stream\n{content}\nendstream endobj\n\
|
||||
5 0 obj << /Type /XObject /Subtype /Image /Width 1 /Height 1 /ColorSpace /DeviceGray /BitsPerComponent 8 /SMask 6 0 R /Filter /ASCIIHexDecode /Length 3 >> stream\n00>\nendstream endobj\n\
|
||||
6 0 obj << /Type /XObject /Subtype /Image /Width 8 /Height 1 /ColorSpace /DeviceGray /BitsPerComponent 8 /Filter /ASCIIHexDecode /Length {} >> stream\n{}>\nendstream endobj\n\
|
||||
trailer << /Root 1 0 R >>\n%%EOF\n",
|
||||
content.len(),
|
||||
hex(&mask).len() + 1,
|
||||
hex(&mask)
|
||||
);
|
||||
let mut renderer = PageRenderer::new(Arc::new(pdf.into_bytes()), RenderConfig::default());
|
||||
let page = renderer.render(RenderRequest { page: 0, kind: RequestKind::Pixels, scale: 1.0, ..Default::default() });
|
||||
assert!(page.error.is_none(), "{:?}", page.error);
|
||||
assert_eq!((page.width, page.height), (4, 1));
|
||||
let grays: Vec<u8> = page.rgba.as_chunks::<4>().0.iter().map(|px| px[0]).collect();
|
||||
// The stroke covers half of device pixel 1, so it is a mid-tone; the far side stays white.
|
||||
assert!((64..=191).contains(&grays[1]), "the stroke is lost or not a mid-tone: {grays:?}");
|
||||
assert_eq!(grays[3], 255, "{grays:?}");
|
||||
}
|
||||
|
||||
/// Type 3 glyph images used to be sampled bicubically (`ImageQuality::High`) whatever their
|
||||
/// `/Interpolate`, which blends the nearest pre-resize back in. The glyph is 4.6 × 2.3 pixels
|
||||
/// and its image pre-resizes to 5 × 3, so the final draw samples fractionally and bicubic
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -534,7 +534,7 @@ impl Renderer {
|
|||
reach
|
||||
}
|
||||
|
||||
fn draw_image_with_alpha_mask(&mut self, image_data: ImageData, alpha_data: LumaData) {
|
||||
fn draw_image_with_alpha_mask(&mut self, image_data: ImageData, mut alpha_data: LumaData) {
|
||||
// PdfCraft patch: mismatched masks need a dummy RGB staging image. Validate and
|
||||
// reserve it before creating/pushing any mask, so failure skips the whole image.
|
||||
let Some(len) = source_byte_len(alpha_data.width, alpha_data.height, 3, MAX_IMAGE_PIXELS)
|
||||
|
|
@ -555,6 +555,15 @@ impl Renderer {
|
|||
image_data.width() as f64 / alpha_data.width as f64,
|
||||
image_data.height() as f64 / alpha_data.height as f64,
|
||||
);
|
||||
// PdfCraft patch (#1649): a mask that differs in size from its image carries the
|
||||
// shape on its own (Word saves text as a tiny black image with a glyph-bitmap mask).
|
||||
// Shrunk by nearest-neighbour it keeps one sample in N and drops every stroke thinner
|
||||
// than N mask pixels, so a shrunk mask is filtered whatever its `/Interpolate`, which
|
||||
// only asks for smoothing when the mask is enlarged.
|
||||
let (x, y) = x_y_advances(&transform);
|
||||
if x.length() < 1.0 || y.length() < 1.0 {
|
||||
alpha_data.interpolate = true;
|
||||
}
|
||||
let mut renderer = Self {
|
||||
ctx: RenderContext::new_with(
|
||||
self.ctx.width(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue