Implements the complete graphics state per PDF spec section 8.4:
- Color enum with 5 variants (DeviceGray/RGB/CMYK, Spot, Other)
- Color::to_css_hex() for JSON serialization (returns None for Spot/Other)
- GraphicsState struct with all 13 fields (ctm, text_matrix, text_line_matrix,
font, font_size, char_spacing, word_spacing, horiz_scaling, leading,
text_rise, text_rendering_mode, fill_color, stroke_color)
- GraphicsState::initial() returning default state (identity CTM, black colors)
- Matrix operations: scale(), translate(), rotate(), invert()
- Manual Debug impl for GraphicsState (Font doesn't implement Debug)
All acceptance criteria PASS:
- initial() has identity CTM, font_size 0.0, fill_color DeviceGray(0.0)
- Clone produces deep-equal value
- Color::DeviceRGB([1.0, 0.0, 0.0]).to_css_hex() == Some("#ff0000")
- Color::Spot returns None
- Matrix multiply identity*identity within 1e-10
Closes: pdftract-44f6
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
3.8 KiB
3.8 KiB
pdftract-44f6: GraphicsState struct + Color enum + matrix initialization
Summary
Implemented the complete GraphicsState struct with all 13 fields per PDF spec section 8.4, plus the Color enum with CSS hex serialization and matrix operations.
Changes Made
File: crates/pdftract-core/src/graphics_state.rs
-
Added
Colorenum with 5 variants:DeviceGray(f32)- single grayscale componentDeviceRGB([f32; 3])- RGB colorDeviceCMYK([f32; 4])- CMYK colorSpot(Arc<str>, f32)- spot color with tintOther- other color spaces
-
Added
Color::to_css_hex()method:- Converts DeviceGray/RGB/CMYK to "#rrggbb" format
- Returns None for Spot/Other
- Includes clamp for safety
- CMYK→RGB uses naive formula: R=(1-C)*(1-K)
-
Extended
GraphicsStatestruct with all 13 fields:ctm: Matrix3x3- current transformation matrixtext_matrix: Matrix3x3- Tmtext_line_matrix: Matrix3x3- Tlmfont: Option<Arc<Font>>- current fontfont_size: f64- Tfchar_spacing: f64- Tcword_spacing: f64- Twhoriz_scaling: f64- Tz (default 100)leading: f64- TLtext_rise: f64- Tstext_rendering_mode: u8- Tr (0-7)fill_color: Color- fill colorstroke_color: Color- stroke color
-
Added
GraphicsState::initial()returning default state:- Identity CTM, text_matrix, text_line_matrix
- font: None
- font_size: 0.0
- All spacings: 0.0
- horiz_scaling: 100.0
- text_rendering_mode: 0
- fill/stroke_color: DeviceGray(0.0) (black)
-
Added matrix operations to
Matrix3x3:scale(sx, sy)- create scale matrixtranslate(tx, ty)- create translation matrixrotate(angle)- create rotation matrix (angle in radians)invert()- invert matrix, returns None if singular
-
Implemented
Debugmanually forGraphicsState:- Font doesn't implement Debug, so we show
<Arc<Font>>placeholder
- Font doesn't implement Debug, so we show
-
Added comprehensive tests:
test_gstate_initial_ctm_is_identitytest_gstate_initial_font_size_is_zerotest_gstate_initial_fill_color_is_blacktest_gstate_initial_horiz_scaling_is_100test_gstate_initial_text_matrices_are_identitytest_gstate_initial_font_is_nonetest_gstate_initial_text_rendering_mode_is_0test_gstate_clone_deep_equaltest_color_device_rgb_to_css_hex- verifies#ff0000for redtest_color_device_gray_to_css_hextest_color_device_cmyk_to_css_hextest_color_spot_to_css_hex_nonetest_color_other_to_css_hex_nonetest_color_device_rgb_clampedtest_matrix_scaletest_matrix_translatetest_matrix_rotatetest_matrix_invert_identitytest_matrix_invert_translationtest_matrix_invert_singulartest_identity_multiply_identitytest_multiply_within_tolerance
Acceptance Criteria Status
- ✅
GraphicsState::initial()returns state with ctm == identity - ✅
GraphicsState::initial()returns state with font_size == 0.0 - ✅
GraphicsState::initial()returns state with fill_color == DeviceGray(0.0) - ✅ Clone produces a deep-equal value (Arc cloned cheaply)
- ✅
Color::DeviceRGB([1.0, 0.0, 0.0]).to_css_hex() == Some("#ff0000".into()) - ✅
Color::Spot("PANTONE".into(), 0.5).to_css_hex() == None - ✅ Matrix multiply test: identity * identity == identity within 1e-10
Notes
- Font doesn't implement Debug, so GraphicsState has a manual Debug impl that shows
<Arc<Font>>placeholder for the font field - All matrix operations use f64 for precision (INV-30)
- Color uses f32 for components per PDF spec convention
- GraphicsState is Clone thanks to Arc (cheap clone for q/Q operators)
References
- Plan section: Phase 3.1 State struct fields (lines 1444-1460), Color type (lines 1463-1471), CSS hex rules (line 1473)