Skip to content

Redaction Library Implementation

Marc Jones edited this page Dec 22, 2025 · 4 revisions

Redaction Library Implementation

This page tracks the implementation status of the PdfEditor.Redaction library - a modular TRUE glyph-level redaction engine.

Overview

The Redaction Library (PdfEditor.Redaction) is being developed as a standalone, reusable component for TRUE content-level PDF redaction. Unlike visual overlay redaction (drawing black boxes), this library removes text from the PDF content stream.

Key Design Principles:

  • Modular Operator Handlers: Each PDF operator has its own handler class, allowing independent development and testing
  • PdfPig for Positioning: Use PdfPig as source of truth for letter positions
  • PdfSharpCore for Modification: Use PdfSharpCore for PDF structure modification

References

Milestone: v1.3.0 (Birth Certificate Form)

Target: Redact text from the Washington State birth certificate request form.

v1.3.0 Required Operators

Based on analysis of the actual birth certificate PDF content streams:

Operator Occurrences Description Status GitHub Issue
Tm 33 Set text matrix ✅ Implemented #68
Td 28 Move text position 📋 Pending #73
Tf 17 Set font and size ✅ Implemented #70
TD 7 Move + set leading ✅ Implemented #69
Tj 4 Show text string ✅ Implemented #56
TJ many Show array w/kerning 📋 Pending #57
T* 2 Move to next line 📋 Pending #74
BT/ET - Begin/end text ✅ Implemented #72
Escape sequences - \n, \(, etc. ✅ Implemented #71
Hex strings - <48656C6C6F> ✅ Implemented #67

v1.3.0 Integration Tasks

Task Status GitHub Issue
Project infrastructure ✅ Done #55
Content stream parser 🔄 In Progress #55
Birth certificate test 📋 Pending #60
Main app integration 📋 Pending #59

Future Operators (Post v1.3.0)

These operators are NOT needed for the birth certificate form but will be implemented later:

Operator Description Status GitHub Issue Priority
' Quote (newline + show) 📋 Planned #61 Medium (legacy documents)
" Double quote (spacing + show) 📋 Planned #61 Low (rare)
Form XObjects Text in reusable content 📋 Planned #62 High
CID/CJK fonts Chinese/Japanese/Korean 📋 Planned #63 Medium
Invisible text (Tr=3) OCR layers 📋 Planned #64 Critical for security

Architecture

Directory Structure

PdfEditor.Redaction/
├── ITextRedactor.cs              # Main public interface
├── IContentStreamParser.cs       # Parser interface + operation models
├── IContentStreamBuilder.cs      # Builder interface
├── ContentStream/
│   └── ContentStreamTokenizer.cs # Low-level string/operator parsing
└── Operators/
    ├── IOperatorHandler.cs       # Handler interface
    ├── PdfParserState.cs         # Shared graphics/text state
    ├── OperatorRegistry.cs       # Handler registration
    ├── TextObject/
    │   ├── BtOperatorHandler.cs  # BT operator
    │   └── EtOperatorHandler.cs  # ET operator
    ├── TextState/
    │   └── TfOperatorHandler.cs  # Tf operator
    ├── TextPositioning/
    │   ├── TmOperatorHandler.cs  # Tm operator
    │   └── TdUpperOperatorHandler.cs # TD operator
    └── TextShowing/
        └── TjOperatorHandler.cs  # Tj operator

Modular Operator Pattern

Each operator has its own handler implementing IOperatorHandler:

public interface IOperatorHandler
{
    string OperatorName { get; }
    PdfOperation? Handle(IReadOnlyList<object> operands, PdfParserState state);
}

Handlers are registered in OperatorRegistry:

var registry = OperatorRegistry.CreateDefault();
var handler = registry.GetHandler("Tj");
var operation = handler.Handle(operands, state);

Benefits:

  • Each operator can be developed/tested independently
  • Adding new operators doesn't break existing ones
  • Easy to mock handlers for unit testing

State Tracking

PdfParserState maintains all parsing state:

public class PdfParserState
{
    // Page context
    public double PageHeight { get; }
    public bool InTextObject { get; set; }

    // Graphics state
    public PdfMatrix TransformationMatrix { get; set; }

    // Text state
    public PdfMatrix TextMatrix { get; set; }
    public PdfMatrix TextLineMatrix { get; set; }
    public string? FontName { get; set; }
    public double FontSize { get; set; }
    public double CharacterSpacing { get; set; }
    public double WordSpacing { get; set; }
    public double TextLeading { get; set; }
    // ...
}

TRUE Redaction Strategy

The Problem with Black Box Redaction

Original PDF:    "SSN: 123-45-6789"
Black box:       "SSN: ████████████"  ← Text still in PDF!
Text extraction: "SSN: 123-45-6789"  ← Security failure!

Our Solution: Content Stream Removal

1. Parse content stream → List<PdfOperation>
2. Identify operations containing target text
3. Filter out those operations
4. Rebuild content stream without removed operations
5. Replace page content
6. (Optional) Draw visual marker

After TRUE redaction:

Text extraction: "SSN: "  ← Sensitive data GONE

Verification

Every redaction test MUST verify text is actually removed:

var textAfter = PdfTestHelpers.ExtractAllText(redactedPdf);
textAfter.Should().NotContain("123-45-6789",
    "Text must be REMOVED from PDF structure, not just hidden");

Test Coverage

Test File Purpose Count
OperatorRegistryTests.cs Registry functionality 6
BtEtOperatorTests.cs BT/ET operators 7
TmOperatorTests.cs Tm operator 7
TjOperatorTests.cs Tj operator 13
ContentStreamTokenizerTests.cs String/operator parsing 17
Total 70+

Run tests:

dotnet test PdfEditor.Redaction.Tests

Adding a New Operator

  1. Create handler class in appropriate subdirectory:

    public class TdLowerOperatorHandler : IOperatorHandler
    {
        public string OperatorName => "Td";
    
        public PdfOperation? Handle(IReadOnlyList<object> operands, PdfParserState state)
        {
            // Implementation
        }
    }
  2. Register in OperatorRegistry.CreateDefault():

    registry.Register(new TdLowerOperatorHandler());
  3. Add unit tests in new test file:

    public class TdLowerOperatorTests
    {
        [Fact]
        public void TdHandler_UpdatesTextMatrix() { ... }
    }
  4. Update this wiki page with status

  5. Close the GitHub issue when tests pass


Last updated: 2024-12-22

Clone this wiki locally