Repository navigation
Redaction Library Implementation
This page tracks the implementation status of the PdfEditor.Redaction library - a modular TRUE glyph-level redaction engine.
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
- PDF 32000-1:2008 - PDF Reference (Section 9 covers text)
- PDF Operators Cheat Sheet - PDF Association quick reference
- PDF Text Operators - Our detailed operator documentation
Target: Redact text from the Washington State birth certificate request form.
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 |
| Task | Status | GitHub Issue |
|---|---|---|
| Project infrastructure | ✅ Done | #55 |
| Content stream parser | 🔄 In Progress | #55 |
| Birth certificate test | 📋 Pending | #60 |
| Main app integration | 📋 Pending | #59 |
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 |
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
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
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; }
// ...
}Original PDF: "SSN: 123-45-6789"
Black box: "SSN: ████████████" ← Text still in PDF!
Text extraction: "SSN: 123-45-6789" ← Security failure!
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
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 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-
Create handler class in appropriate subdirectory:
public class TdLowerOperatorHandler : IOperatorHandler { public string OperatorName => "Td"; public PdfOperation? Handle(IReadOnlyList<object> operands, PdfParserState state) { // Implementation } }
-
Register in
OperatorRegistry.CreateDefault():registry.Register(new TdLowerOperatorHandler());
-
Add unit tests in new test file:
public class TdLowerOperatorTests { [Fact] public void TdHandler_UpdatesTextMatrix() { ... } }
-
Update this wiki page with status
-
Close the GitHub issue when tests pass
Last updated: 2024-12-22