use super::super::iso_test;
use crate::verification::{parser::parse_pdf, VerificationLevel};
use crate::{Color, Document, Font, Page, Result as PdfResult};
iso_test!(
test_operator_parsing_level_3,
"8.7.5",
VerificationLevel::ContentVerified,
"Content stream operator parsing with specific operator validation",
{
let mut doc = Document::new();
doc.set_title("Operator Parsing Test");
let mut page = Page::a4();
page.text()
.set_font(Font::Helvetica, 14.0)
.at(100.0, 700.0)
.write("Operator Parsing Test")?;
page.graphics()
.save_state() .set_fill_color(Color::rgb(0.8, 0.2, 0.4))
.rectangle(150.0, 650.0, 80.0, 40.0)
.fill()
.restore_state();
page.graphics()
.move_to(100.0, 600.0) .line_to(200.0, 600.0) .line_to(150.0, 550.0) .close_path() .stroke();
doc.add_page(page);
let pdf_bytes = doc.to_bytes()?;
let parsed = parse_pdf(&pdf_bytes)?;
let pdf_string = String::from_utf8_lossy(&pdf_bytes);
let has_save_state = pdf_string.contains("q");
let has_restore_state = pdf_string.contains("Q");
let state_ops_balanced = pdf_string.matches("q").count() == pdf_string.matches("Q").count();
let has_moveto =
pdf_string.contains("100") && pdf_string.contains("600") && pdf_string.contains("m");
let has_lineto = pdf_string.contains("l");
let has_closepath = pdf_string.contains("h");
let has_stroke = pdf_string.contains("S");
let has_text_positioning = pdf_string.contains("100") && pdf_string.contains("700");
let has_font_selection = pdf_string.contains("Tf");
let has_rectangle =
pdf_string.contains("150") && pdf_string.contains("650") && pdf_string.contains("re");
let has_fill = pdf_string.contains("f");
let has_valid_structure = parsed.catalog.is_some() && parsed.page_tree.is_some();
let sufficient_content = pdf_bytes.len() > 1200;
let passed = has_save_state
&& has_restore_state
&& state_ops_balanced
&& has_moveto
&& has_lineto
&& has_closepath
&& has_stroke
&& has_text_positioning
&& has_font_selection
&& has_rectangle
&& has_fill
&& has_valid_structure
&& sufficient_content;
let level_achieved = if passed { 3 } else { 2 };
let notes = if passed {
format!("Operator parsing verified: q/Q: {}/{}, path ops: m:{} l:{} h:{} S:{}, text: pos:{} font:{}, rect/fill: {}/{}, {} bytes",
has_save_state, has_restore_state, has_moveto, has_lineto, has_closepath, has_stroke,
has_text_positioning, has_font_selection, has_rectangle, has_fill, pdf_bytes.len())
} else {
format!("Operator parsing incomplete: q/Q: {}/{} balanced:{}, path: m:{} l:{} h:{} S:{}, text: {}/{}, rect/fill: {}/{}",
has_save_state, has_restore_state, state_ops_balanced, has_moveto, has_lineto, has_closepath, has_stroke,
has_text_positioning, has_font_selection, has_rectangle, has_fill)
};
Ok((passed, level_achieved, notes))
}
);
iso_test!(
test_text_positioning_operators_level_3,
"8.7.6",
VerificationLevel::ContentVerified,
"Text positioning operators (Td, TD, Tm) with coordinate validation",
{
let mut doc = Document::new();
doc.set_title("Text Positioning Test");
let mut page = Page::a4();
page.text()
.set_font(Font::Helvetica, 12.0)
.at(72.0, 750.0)
.write("Position 1: (72, 750)")?;
page.text()
.set_font(Font::TimesRoman, 12.0)
.at(150.0, 720.0)
.write("Position 2: (150, 720)")?;
page.text()
.set_font(Font::Courier, 10.0)
.at(200.0, 690.0)
.write("Position 3: (200, 690)")?;
page.graphics()
.set_stroke_color(Color::rgb(0.5, 0.5, 0.5))
.move_to(72.0, 660.0)
.line_to(500.0, 660.0)
.stroke();
doc.add_page(page);
let pdf_bytes = doc.to_bytes()?;
let parsed = parse_pdf(&pdf_bytes)?;
let pdf_string = String::from_utf8_lossy(&pdf_bytes);
let has_coord_72_750 = pdf_string.contains("72") && pdf_string.contains("750");
let has_coord_150_720 = pdf_string.contains("150") && pdf_string.contains("720");
let has_coord_200_690 = pdf_string.contains("200") && pdf_string.contains("690");
let has_text_positioning =
pdf_string.contains("Td") || pdf_string.contains("TD") || pdf_string.contains("Tm");
let has_text_blocks = pdf_string.contains("BT") && pdf_string.contains("ET");
let has_font_operators = pdf_string.contains("Tf");
let has_text_showing = pdf_string.contains("Tj") || pdf_string.contains("TJ");
let font_changes = pdf_string.matches("Tf").count();
let has_multiple_fonts = font_changes >= 2;
let has_valid_structure = parsed.catalog.is_some() && parsed.page_tree.is_some();
let has_fonts = !parsed.fonts.is_empty();
let sufficient_content = pdf_bytes.len() > 1300;
let passed = has_coord_72_750
&& has_coord_150_720
&& has_coord_200_690
&& has_text_positioning
&& has_text_blocks
&& has_font_operators
&& has_text_showing
&& has_multiple_fonts
&& has_valid_structure
&& has_fonts
&& sufficient_content;
let level_achieved = if passed { 3 } else { 2 };
let notes = if passed {
format!("Text positioning verified: coords (72,750):{} (150,720):{} (200,690):{}, positioning:{}, BT/ET:{}, {} Tf ops, text show:{}, {} fonts, {} bytes",
has_coord_72_750, has_coord_150_720, has_coord_200_690, has_text_positioning, has_text_blocks,
font_changes, has_text_showing, parsed.fonts.len(), pdf_bytes.len())
} else {
format!("Text positioning incomplete: coords {}/{}/{}, positioning:{}, BT/ET:{}, Tf:{}, show:{}, fonts:{}",
has_coord_72_750, has_coord_150_720, has_coord_200_690, has_text_positioning, has_text_blocks,
has_font_operators, has_text_showing, has_fonts)
};
Ok((passed, level_achieved, notes))
}
);
iso_test!(
test_path_painting_operators_level_3,
"8.7.7",
VerificationLevel::ContentVerified,
"Path painting operators (m-l-S, re-f, re-B) with coordinate sequences",
{
let mut doc = Document::new();
doc.set_title("Path Painting Test");
let mut page = Page::a4();
page.text()
.set_font(Font::Helvetica, 16.0)
.at(50.0, 750.0)
.write("Path Painting Operations Test")?;
page.graphics()
.set_stroke_color(Color::rgb(1.0, 0.0, 0.0))
.move_to(100.0, 650.0)
.line_to(150.0, 700.0)
.line_to(200.0, 650.0)
.close_path()
.stroke();
page.graphics()
.set_fill_color(Color::rgb(0.0, 0.8, 0.2))
.rectangle(250.0, 650.0, 60.0, 50.0)
.fill();
page.graphics()
.set_fill_color(Color::rgb(0.2, 0.4, 0.8))
.set_stroke_color(Color::rgb(0.8, 0.4, 0.2))
.rectangle(350.0, 650.0, 60.0, 50.0)
.fill_stroke();
doc.add_page(page);
let pdf_bytes = doc.to_bytes()?;
let parsed = parse_pdf(&pdf_bytes)?;
let pdf_string = String::from_utf8_lossy(&pdf_bytes);
let has_triangle_coords = pdf_string.contains("100")
&& pdf_string.contains("650")
&& pdf_string.contains("150")
&& pdf_string.contains("700")
&& pdf_string.contains("200");
let has_moveto = pdf_string.contains("m");
let has_lineto = pdf_string.contains("l");
let has_closepath = pdf_string.contains("h");
let has_stroke = pdf_string.contains("S");
let has_rectangle_250 =
pdf_string.contains("250") && pdf_string.contains("60") && pdf_string.contains("50");
let has_rectangle_350 = pdf_string.contains("350");
let has_rectangle_op = pdf_string.contains("re");
let has_fill = pdf_string.contains("f");
let has_fill_stroke = pdf_string.contains("B") || pdf_string.contains("b");
let has_stroke_color = pdf_string.contains("RG");
let has_fill_color = pdf_string.contains("rg");
let has_valid_structure = parsed.catalog.is_some() && parsed.page_tree.is_some();
let uses_colors = parsed.uses_device_rgb;
let sufficient_content = pdf_bytes.len() > 1400;
let passed = has_triangle_coords
&& has_moveto
&& has_lineto
&& has_closepath
&& has_stroke
&& has_rectangle_250
&& has_rectangle_350
&& has_rectangle_op
&& has_fill
&& has_stroke_color
&& has_fill_color
&& uses_colors
&& has_valid_structure
&& sufficient_content;
let level_achieved = if passed { 3 } else { 2 };
let notes = if passed {
format!("Path painting verified: triangle coords:{}, m-l-h-S:{}/{}/{}/{}, rect coords: 250:{} 350:{}, re-f-B:{}/{}/{}, colors: RG:{} rg:{}, RGB usage:{}, {} bytes",
has_triangle_coords, has_moveto, has_lineto, has_closepath, has_stroke,
has_rectangle_250, has_rectangle_350, has_rectangle_op, has_fill, has_fill_stroke,
has_stroke_color, has_fill_color, uses_colors, pdf_bytes.len())
} else {
format!("Path painting incomplete: triangle:{}, m-l-h-S: {}/{}/{}/{}, rect: {}/{} re:{} f:{}, colors: RG:{} rg:{}",
has_triangle_coords, has_moveto, has_lineto, has_closepath, has_stroke,
has_rectangle_250, has_rectangle_350, has_rectangle_op, has_fill, has_stroke_color, has_fill_color)
};
Ok((passed, level_achieved, notes))
}
);
iso_test!(
test_color_operator_values_level_3,
"8.7.8",
VerificationLevel::ContentVerified,
"Color operator values verification with specific RGB components",
{
let mut doc = Document::new();
doc.set_title("Color Operator Values Test");
let mut page = Page::a4();
page.text()
.set_font(Font::Helvetica, 14.0)
.at(50.0, 750.0)
.write("Color Values Test")?;
page.graphics()
.set_fill_color(Color::rgb(1.0, 0.0, 0.0))
.rectangle(50.0, 700.0, 50.0, 30.0)
.fill();
page.graphics()
.set_stroke_color(Color::rgb(0.0, 1.0, 0.0))
.rectangle(120.0, 700.0, 50.0, 30.0)
.stroke();
page.graphics()
.set_fill_color(Color::rgb(0.0, 0.0, 1.0))
.rectangle(190.0, 700.0, 50.0, 30.0)
.fill();
page.graphics()
.set_stroke_color(Color::rgb(0.5, 0.5, 0.5))
.rectangle(260.0, 700.0, 50.0, 30.0)
.stroke();
page.graphics()
.set_fill_color(Color::rgb(0.8, 0.2, 0.6))
.rectangle(330.0, 700.0, 50.0, 30.0)
.fill();
doc.add_page(page);
let pdf_bytes = doc.to_bytes()?;
let parsed = parse_pdf(&pdf_bytes)?;
let pdf_string = String::from_utf8_lossy(&pdf_bytes);
let has_red_fill =
pdf_string.contains("1") && pdf_string.contains("0") && pdf_string.contains("rg");
let has_green_stroke =
pdf_string.contains("1") && pdf_string.contains("0") && pdf_string.contains("RG");
let has_blue_values = pdf_string.contains("0") && pdf_string.contains("1");
let has_gray_values = pdf_string.contains("0.5") || pdf_string.contains("5");
let has_mixed_values = pdf_string.contains("0.8")
|| pdf_string.contains("8")
|| pdf_string.contains("0.2")
|| pdf_string.contains("2")
|| pdf_string.contains("0.6")
|| pdf_string.contains("6");
let fill_color_ops = pdf_string.matches("rg").count();
let stroke_color_ops = pdf_string.matches("RG").count();
let has_sufficient_colors = fill_color_ops >= 3 && stroke_color_ops >= 2;
let has_rect_coords = pdf_string.contains("50")
&& pdf_string.contains("120")
&& pdf_string.contains("190")
&& pdf_string.contains("260")
&& pdf_string.contains("330");
let has_valid_structure = parsed.catalog.is_some() && parsed.page_tree.is_some();
let uses_device_rgb = parsed.uses_device_rgb;
let sufficient_content = pdf_bytes.len() > 1500;
let passed = has_red_fill
&& has_green_stroke
&& has_blue_values
&& has_gray_values
&& has_mixed_values
&& has_sufficient_colors
&& has_rect_coords
&& has_valid_structure
&& uses_device_rgb
&& sufficient_content;
let level_achieved = if passed { 3 } else { 2 };
let notes = if passed {
format!("Color values verified: red:{}, green:{}, blue:{}, gray:{}, mixed:{}, rg ops:{}, RG ops:{}, coords:{}, DeviceRGB:{}, {} bytes",
has_red_fill, has_green_stroke, has_blue_values, has_gray_values, has_mixed_values,
fill_color_ops, stroke_color_ops, has_rect_coords, uses_device_rgb, pdf_bytes.len())
} else {
format!("Color values incomplete: red:{}, green:{}, blue:{}, gray:{}, mixed:{}, colors rg:{}/RG:{}, coords:{}, RGB:{}",
has_red_fill, has_green_stroke, has_blue_values, has_gray_values, has_mixed_values,
fill_color_ops, stroke_color_ops, has_rect_coords, uses_device_rgb)
};
Ok((passed, level_achieved, notes))
}
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_content_parser_infrastructure() -> PdfResult<()> {
println!("🔍 Running Content Parser Infrastructure Test");
let mut doc = Document::new();
doc.set_title("Content Parser Infrastructure Test");
let mut page = Page::a4();
page.text()
.set_font(Font::Helvetica, 14.0)
.at(100.0, 700.0)
.write("Parser Test")?;
page.graphics()
.save_state()
.set_fill_color(Color::rgb(0.8, 0.2, 0.4))
.rectangle(150.0, 650.0, 80.0, 40.0)
.fill()
.restore_state();
page.graphics()
.move_to(100.0, 600.0)
.line_to(200.0, 600.0)
.line_to(150.0, 550.0)
.close_path()
.stroke();
doc.add_page(page);
let pdf_bytes = doc.to_bytes()?;
println!(
"✓ Generated PDF with comprehensive operators: {} bytes",
pdf_bytes.len()
);
let pdf_string = String::from_utf8_lossy(&pdf_bytes);
let has_q = pdf_string.contains("q");
let has_q_restore = pdf_string.contains("Q");
let has_m = pdf_string.contains("m");
let has_l = pdf_string.contains("l");
let has_s_stroke = pdf_string.contains("S");
println!(
"✓ Operators found - q: {}, Q: {}, m: {}, l: {}, S: {}",
has_q, has_q_restore, has_m, has_l, has_s_stroke
);
let parsed = parse_pdf(&pdf_bytes)?;
println!("✓ Successfully parsed PDF structure");
assert!(
pdf_bytes.len() > 1000,
"PDF should have comprehensive content"
);
assert!(has_m, "PDF should contain moveto operator");
assert!(has_l, "PDF should contain lineto operator");
let essential_ops = has_m && has_l && has_s_stroke;
assert!(
essential_ops,
"PDF should contain essential path operators (m, l, S)"
);
assert!(parsed.catalog.is_some(), "PDF must have catalog");
println!("✅ Content parser infrastructure test passed");
Ok(())
}
}