墨消し・サニタイズ
真の墨消しは破壊的です。基礎となるグリフ、画像、パスは、黒い矩形で単に覆うのではなく、コンテンツストリームから物理的に削除されなければなりません。PDF Oxide v0.3.69 は ISO 32000-1:2008 §12.5.6.23 の墨消しを実装しており、redaction_apply は物理的に削除したグリフ数を返し、ページが安全に書き換えられない複合フォント(Type0)を使用している場合は、サイレントな墨消し不足ではなくエラーを返してフェイルクローズします。
このページでは、破壊的墨消しファミリー(redaction_add / redaction_apply / redaction_count / redaction_scrub_metadata)と、ヘッダー・フッター・アーティファクト削除ファミリー(remove_headers / remove_footers / remove_artifacts、およびページ単位の erase_header / erase_footer / erase_artifacts)について説明します。
2つの墨消し方法。 v0.3.69 には、
/Redactアノテーションを表示上のオーバーレイとして焼き込む旧来のアノテーション平坦化パス(apply_page_redactions/apply_all_redactions)も引き続き存在します。コンテンツを完全に消去する必要がある場合は、ここで説明する破壊的ファミリーを優先してください。破壊的なredaction_applyはソース内に既存の/Redactアノテーションも消費するため、両方の方法を組み合わせて使用できます。
バインディングの対応状況。 破壊的墨消しファミリーは Rust、Python、Go、C#、WASM/JavaScript ビルドで公開されています。ヘッダー・フッター・アーティファクトの
remove_*ファミリーは Rust、Python、Go、WASM/JavaScript で公開されています。ページ単位のerase_*ファミリーは Rust、Python、WASM/JavaScript で公開されています。C# は現在remove_*/erase_*のページ装飾ファミリーを公開していません。
PDFからテキストを墨消しするにはどうすればよいですか?
破壊的ワークフローは3つのステップで構成されます:
redaction_addで墨消し矩形をキューに追加します(ページのユーザー空間座標;オプションの DeviceRGB オーバーレイ塗りつぶし色)。ソースに既存の/Redactアノテーションがある場合は自動的に取り込まれます。redaction_applyで適用します。対象のグリフ・画像・パスを物理的に削除し、不透明なオーバーレイを描画し、オプションでドキュメントメタデータを消去し、削除したグリフ数を返します。- 書き換えられた PDF を保存します。元の
/Contentsオブジェクトはハードドロップ(G6)されるため、秘密情報がGC見落とし孤立オブジェクトとして残ることはありません。
墨消し矩形のキュー追加
redaction_add(page, rect, fill) は破壊的矩形をキューに追加します。座標はページのユーザー空間 (x0, y0, x1, y1) で指定し、fill はオプションの DeviceRGB [r, g, b] オーバーレイ色です(デフォルトは黒)。
Rust
use pdf_oxide::editor::{DocumentEditor, EditableDocument};
use pdf_oxide::redaction::RedactionOptions;
let mut editor = DocumentEditor::open("confidential.pdf")?;
// Queue a black redaction box on page 0 (x0, y0, x1, y1 in points).
editor.add_redaction(0, [100.0, 700.0, 300.0, 714.0], None)?;
// Apply destructively, then save. Returns a RedactionReport.
let report = editor.apply_redactions_destructive(RedactionOptions::default())?;
println!("glyphs removed: {}", report.glyphs_removed);
editor.save("redacted.pdf")?;
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("confidential.pdf")
# Queue a black redaction box on page 0 (x0, y0, x1, y1 in points).
doc.add_redaction(0, (100.0, 700.0, 300.0, 714.0))
# Apply destructively (returns a report dict), then save.
report = doc.apply_redactions_destructive()
print("glyphs removed:", report["glyphs_removed"])
doc.save("redacted.pdf")
Go
editor, _ := pdfoxide.OpenEditor("confidential.pdf")
defer editor.Close()
// Queue a redaction box on page 0; pass nil fill for the default black.
_ = editor.AddRedaction(0, [4]float64{100, 700, 300, 714}, nil)
// Apply destructively (scrubMetadata = true). Returns glyphs removed.
glyphs, _ := editor.ApplyRedactions(true)
fmt.Printf("glyphs removed: %d\n", glyphs)
_ = editor.Save("redacted.pdf")
C#
using PdfOxide;
using var editor = DocumentEditor.Open("confidential.pdf");
// Queue a redaction box on page 0 (x1, y1, x2, y2; r, g, b default to black).
editor.AddRedaction(0, 100, 700, 300, 714);
// Apply destructively (scrubMetadata defaults to true). Returns glyphs removed.
int glyphs = editor.ApplyRedactions(scrubMetadata: true);
Console.WriteLine($"glyphs removed: {glyphs}");
editor.Save("redacted.pdf");
JavaScript (WASM)
import { WasmPdfDocument } from "pdf-oxide-wasm";
const doc = new WasmPdfDocument(bytes);
// Queue a redaction box on page 0; pass an [r, g, b] array for a custom fill.
doc.addRedaction(0, 100, 700, 300, 714);
// Apply destructively. Returns a RedactionReport object.
const report = doc.applyRedactionsDestructive(true);
console.log("glyphs removed:", report.glyphs_removed);
const output = doc.save();
doc.free();
Java
import fyi.oxide.pdf.DocumentEditor;
import fyi.oxide.pdf.geometry.BBox;
try (DocumentEditor editor = DocumentEditor.open("confidential.pdf")) {
// Queue a black redaction box on page 0 (x0, y0, x1, y1 in points).
editor.addRedaction(0, new BBox(100.0, 700.0, 300.0, 714.0));
// Apply destructively (scrubs metadata), then save.
editor.applyRedactionsDestructive();
editor.saveTo(java.nio.file.Path.of("redacted.pdf"));
}
Kotlin
import fyi.oxide.pdf.DocumentEditor
import fyi.oxide.pdf.geometry.BBox
DocumentEditor.open("confidential.pdf").use { editor ->
// Queue a black redaction box on page 0 (x0, y0, x1, y1 in points).
editor.addRedaction(0, BBox(100.0, 700.0, 300.0, 714.0))
// Apply destructively (scrubs metadata), then save.
editor.applyRedactionsDestructive()
editor.saveTo(java.nio.file.Path.of("redacted.pdf"))
}
Scala
import fyi.oxide.pdf.DocumentEditor
import fyi.oxide.pdf.geometry.BBox
import scala.util.Using
Using.resource(DocumentEditor.open("confidential.pdf")) { editor =>
// Queue a black redaction box on page 0 (x0, y0, x1, y1 in points).
editor.addRedaction(0, BBox(100.0, 700.0, 300.0, 714.0))
// Apply destructively (scrubs metadata), then save.
editor.applyRedactionsDestructive()
editor.saveTo(java.nio.file.Path.of("redacted.pdf"))
}
Clojure
(require '[pdf-oxide.core :as pdf])
(import '[fyi.oxide.pdf.geometry BBox])
(with-open [ed (pdf/editor "confidential.pdf")]
;; Queue a black redaction box on page 0 (x0, y0, x1, y1 in points).
(pdf/add-redaction ed 0 (BBox. 100.0 700.0 300.0 714.0))
;; Apply destructively (scrubs metadata), then save.
(pdf/apply-redactions ed)
(java.nio.file.Files/write
(java.nio.file.Path/of "redacted.pdf" (into-array String []))
(pdf/editor-save ed)))
PHP
use PdfOxide\DocumentEditor;
$editor = DocumentEditor::open('confidential.pdf');
// Queue a black redaction box on page 0 (x1, y1, x2, y2 in points).
$editor->addRedaction(0, 100.0, 700.0, 300.0, 714.0);
// Apply destructively (scrubMetadata defaults to true). Returns glyphs removed.
$glyphs = $editor->applyRedactionsDestructive(true);
echo "glyphs removed: $glyphs\n";
$editor->saveTo('redacted.pdf');
Ruby
require 'pdf_oxide'
PdfOxide::DocumentEditor.open('confidential.pdf') do |ed|
# Queue a black redaction box on page 0 (x1, y1, x2, y2 in points).
ed.add_redaction(page: 0, rect: [100.0, 700.0, 300.0, 714.0])
# Apply destructively, scrubbing metadata (raises on fail-closed), then save.
ed.apply_redactions!(scrub_metadata: true)
ed.save_to('redacted.pdf')
end
C++
#include <pdf_oxide/pdf_oxide.hpp>
auto editor = pdf_oxide::DocumentEditor::open("confidential.pdf");
// Queue a black redaction box on page 0 (x1, y1, x2, y2; r, g, b in 0..1).
editor.redaction_add(0, 100.0, 700.0, 300.0, 714.0, 0.0, 0.0, 0.0);
// Apply destructively (scrub_metadata = true). Returns glyphs removed.
int glyphs = editor.redaction_apply(/*scrub_metadata=*/true, 0.0, 0.0, 0.0);
std::cout << "glyphs removed: " << glyphs << "\n";
editor.save("redacted.pdf");
Swift
import PdfOxide
let editor = try DocumentEditor.open("confidential.pdf")
// Queue a black redaction box on page 0 (x1, y1, x2, y2; r, g, b in 0..1).
try editor.redactionAdd(0, x1: 100, y1: 700, x2: 300, y2: 714, r: 0, g: 0, b: 0)
// Apply destructively (scrub metadata). Returns glyphs removed.
let glyphs = try editor.redactionApply(scrubMetadata: true, r: 0, g: 0, b: 0)
print("glyphs removed: \(glyphs)")
try editor.save("redacted.pdf")
Dart
import 'package:pdf_oxide/pdf_oxide.dart';
final editor = DocumentEditor.open('confidential.pdf');
// Queue a black redaction box on page 0 (x1, y1, x2, y2; r, g, b in 0..1).
editor.redactionAdd(0, 100, 700, 300, 714);
// Apply destructively, scrubbing metadata. Returns glyphs removed.
final glyphs = editor.redactionApply(scrubMetadata: true);
print('glyphs removed: $glyphs');
editor.save('redacted.pdf');
R
library(pdfoxide)
editor <- pdf_editor_open("confidential.pdf")
# Queue a black redaction box on page 0 (x1, y1, x2, y2; r, g, b in 0..1).
pdf_redaction_add(editor, 0, 100, 700, 300, 714, 0, 0, 0)
# Apply destructively, scrubbing metadata. Returns glyphs removed.
glyphs <- pdf_redaction_apply(editor, scrub_metadata = TRUE, 0, 0, 0)
cat("glyphs removed:", glyphs, "\n")
pdf_editor_save(editor, "redacted.pdf")
Julia
using PdfOxide
editor = open_editor("confidential.pdf")
# Queue a black redaction box on page 0 (x1, y1, x2, y2; r, g, b in 0..1).
redaction_add(editor, 0, 100, 700, 300, 714, 0, 0, 0)
# Apply destructively, scrubbing metadata. Returns glyphs removed.
glyphs = redaction_apply(editor, true, 0, 0, 0)
println("glyphs removed: ", glyphs)
save(editor, "redacted.pdf")
Zig
const pdf_oxide = @import("pdf_oxide");
var editor = try pdf_oxide.DocumentEditor.openEditor("confidential.pdf");
defer editor.deinit();
// Queue a black redaction box on page 0 (x1, y1, x2, y2; r, g, b in 0..1).
try editor.redactionAdd(0, 100, 700, 300, 714, 0, 0, 0);
// Apply destructively, scrubbing metadata. Returns glyphs removed.
const glyphs = try editor.redactionApply(true, 0, 0, 0);
std.debug.print("glyphs removed: {d}\n", .{glyphs});
try editor.save("redacted.pdf");
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXDocumentEditor *editor = [POXDocumentEditor openEditor:@"confidential.pdf" error:&err];
// Queue a black redaction box on page 0 (x1, y1, x2, y2; r, g, b in 0..1).
[editor redactionAddPage:0 x1:100 y1:700 x2:300 y2:714 r:0 g:0 b:0 error:&err];
// Apply destructively, scrubbing metadata. Returns glyphs removed.
int32_t glyphs = [editor redactionApplyScrubMetadata:YES r:0 g:0 b:0 error:&err];
NSLog(@"glyphs removed: %d", glyphs);
[editor saveToPath:@"redacted.pdf" error:&err];
Elixir
{:ok, editor} = PdfOxide.open_editor("confidential.pdf")
# Queue a black redaction box on page 0 (x1, y1, x2, y2; r, g, b in 0..1).
:ok = PdfOxide.redaction_add(editor, 0, 100, 700, 300, 714, 0.0, 0.0, 0.0)
# Apply destructively, scrubbing metadata. Returns {:ok, glyphs_removed}.
{:ok, glyphs} = PdfOxide.redaction_apply(editor, true, 0.0, 0.0, 0.0)
IO.puts("glyphs removed: #{glyphs}")
PdfOxide.editor_save(editor, "redacted.pdf")
キューに追加された領域の数を確認する
redaction_count(page) は、あるページにキューされた領域の数を返します。これは、ソース内の /Redact アノテーションと redaction_add で追加されたプログラム的な矩形の合計です。適用前に墨消し対象が存在することをアサートするために使用します。
let mut editor = DocumentEditor::open("marked.pdf")?;
editor.add_redaction(0, [100.0, 700.0, 300.0, 714.0], None)?;
assert_eq!(editor.redaction_count(0)?, 1);
doc = PdfDocument("marked.pdf")
doc.add_redaction(0, (100.0, 700.0, 300.0, 714.0))
assert doc.redaction_count(0) == 1
editor, _ := pdfoxide.OpenEditor("marked.pdf")
defer editor.Close()
_ = editor.AddRedaction(0, [4]float64{100, 700, 300, 714}, nil)
n, _ := editor.RedactionCount(0) // 1
using var editor = DocumentEditor.Open("marked.pdf");
editor.AddRedaction(0, 100, 700, 300, 714);
int n = editor.RedactionCount(0); // 1
const doc = new WasmPdfDocument(bytes);
doc.addRedaction(0, 100, 700, 300, 714);
const n = doc.redactionCount(0); // 1
墨消しとサニタイズの違いは何ですか?
幾何学的な墨消しは、矩形の下にあるコンテンツを削除します。サニタイズは、墨消し矩形では決してカバーされないドキュメントレベルの秘密情報を取り除きます。具体的には、/Info 辞書、カタログの XMP /Metadata ストリーム、ドキュメントの JavaScript(/OpenAction、/AA、/Names/JavaScript)、および /Names/EmbeddedFiles です。削除されたオブジェクトサブツリーは出力からハード除外されます(G6)。
redaction_apply は scrub_metadata フラグが設定されている場合(各バインディングのデフォルト)に自動的にサニタイズを実行します。redaction_scrub_metadata は同じサニタイズパスを単独で実行します。幾何学的な墨消しは行わず、ドキュメントのスクラブだけが必要な場合に使用します。削除したトップレベル構造の数を返します。
Rust
use pdf_oxide::editor::{DocumentEditor, EditableDocument};
use pdf_oxide::redaction::RedactionOptions;
let mut editor = DocumentEditor::open("input.pdf")?;
let report = editor.sanitize_document(RedactionOptions::default())?;
println!("constructs removed: {}", report.annotations_removed);
editor.save("sanitized.pdf")?;
Python
doc = PdfDocument("input.pdf")
report = doc.sanitize_document()
print("constructs removed:", report["annotations_removed"])
doc.save("sanitized.pdf")
Go
editor, _ := pdfoxide.OpenEditor("input.pdf")
defer editor.Close()
removed, _ := editor.SanitizeDocument() // top-level constructs removed
_ = editor.Save("sanitized.pdf")
C#
using var editor = DocumentEditor.Open("input.pdf");
int removed = editor.SanitizeDocument(); // top-level constructs removed
editor.Save("sanitized.pdf");
JavaScript (WASM)
const doc = new WasmPdfDocument(bytes);
const report = doc.sanitizeDocument(true, true, true); // scrub, removeJS, removeEmbedded
const output = doc.save();
doc.free();
Java
import fyi.oxide.pdf.DocumentEditor;
try (DocumentEditor editor = DocumentEditor.open("input.pdf")) {
editor.scrubMetadata(); // strip /Info, XMP, JS, embedded files — no geometry
editor.saveTo(java.nio.file.Path.of("sanitized.pdf"));
}
Kotlin
import fyi.oxide.pdf.DocumentEditor
DocumentEditor.open("input.pdf").use { editor ->
editor.scrubMetadata() // strip /Info, XMP, JS, embedded files — no geometry
editor.saveTo(java.nio.file.Path.of("sanitized.pdf"))
}
Scala
import fyi.oxide.pdf.DocumentEditor
import scala.util.Using
Using.resource(DocumentEditor.open("input.pdf")) { editor =>
editor.scrubMetadata() // strip /Info, XMP, JS, embedded files — no geometry
editor.saveTo(java.nio.file.Path.of("sanitized.pdf"))
}
Clojure
(require '[pdf-oxide.core :as pdf])
(with-open [ed (pdf/editor "input.pdf")]
(pdf/scrub-metadata ed) ; strip /Info, XMP, JS, embedded files — no geometry
(java.nio.file.Files/write
(java.nio.file.Path/of "sanitized.pdf" (into-array String []))
(pdf/editor-save ed)))
PHP
use PdfOxide\DocumentEditor;
$editor = DocumentEditor::open('input.pdf');
$editor->scrubMetadata(); // strip /Info, XMP, JS, embedded files — no geometry
$editor->saveTo('sanitized.pdf');
Ruby
require 'pdf_oxide'
PdfOxide::DocumentEditor.open('input.pdf') do |ed|
removed = ed.scrub_metadata # top-level constructs removed (no geometry)
puts "constructs removed: #{removed}"
ed.save_to('sanitized.pdf')
end
C++
#include <pdf_oxide/pdf_oxide.hpp>
auto editor = pdf_oxide::DocumentEditor::open("input.pdf");
int removed = editor.redaction_scrub_metadata(); // constructs removed (no geometry)
std::cout << "constructs removed: " << removed << "\n";
editor.save("sanitized.pdf");
Swift
import PdfOxide
let editor = try DocumentEditor.open("input.pdf")
let removed = try editor.redactionScrubMetadata() // constructs removed (no geometry)
print("constructs removed: \(removed)")
try editor.save("sanitized.pdf")
Dart
import 'package:pdf_oxide/pdf_oxide.dart';
final editor = DocumentEditor.open('input.pdf');
final removed = editor.redactionScrubMetadata(); // constructs removed (no geometry)
print('constructs removed: $removed');
editor.save('sanitized.pdf');
R
library(pdfoxide)
editor <- pdf_editor_open("input.pdf")
removed <- pdf_redaction_scrub_metadata(editor) # constructs removed (no geometry)
cat("constructs removed:", removed, "\n")
pdf_editor_save(editor, "sanitized.pdf")
Julia
using PdfOxide
editor = open_editor("input.pdf")
removed = redaction_scrub_metadata(editor) # constructs removed (no geometry)
println("constructs removed: ", removed)
save(editor, "sanitized.pdf")
Zig
const pdf_oxide = @import("pdf_oxide");
var editor = try pdf_oxide.DocumentEditor.openEditor("input.pdf");
defer editor.deinit();
const removed = try editor.redactionScrubMetadata(); // constructs removed (no geometry)
std.debug.print("constructs removed: {d}\n", .{removed});
try editor.save("sanitized.pdf");
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXDocumentEditor *editor = [POXDocumentEditor openEditor:@"input.pdf" error:&err];
int32_t removed = [editor redactionScrubMetadataWithError:&err]; // constructs removed
NSLog(@"constructs removed: %d", removed);
[editor saveToPath:@"sanitized.pdf" error:&err];
Elixir
{:ok, editor} = PdfOxide.open_editor("input.pdf")
{:ok, removed} = PdfOxide.redaction_scrub_metadata(editor) # constructs removed
IO.puts("constructs removed: #{removed}")
PdfOxide.editor_save(editor, "sanitized.pdf")
繰り返しのヘッダーとフッターを削除するにはどうすればよいですか?
ヘッダー、フッター、ページ装飾のアーティファクトは複数のページにわたって繰り返され、テキスト抽出や再公開の前に削除したい定型的な要素です。PDF Oxide は2つの方法で検出します。ISO 32000 準拠の /Artifact タグがある場合はそれを優先し(存在する場合は100%の精度)、ない場合はページの上部または下部15%でテキストが繰り返されることをヒューリスティックに検出します。
remove_headers(threshold) / remove_footers(threshold) / remove_artifacts(threshold) はドキュメント全体に対して動作し、削除した項目数を返します。threshold はヒューリスティックモードで装飾と判定するためにテキストが繰り返す必要があるページの割合(0.0〜1.0)です(デフォルト 0.8)。remove_artifacts はヘッダーとフッターの両方を削除する便利な関数です。
Rust
use pdf_oxide::PdfDocument;
let doc = PdfDocument::open("report.pdf")?;
let headers = doc.remove_headers(0.8)?; // count removed
let footers = doc.remove_footers(0.8)?;
// Or both at once:
let total = doc.remove_artifacts(0.8)?; // headers + footers
println!("removed {} furniture items", total);
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
doc.remove_headers(0.8) # threshold defaults to 0.8
doc.remove_footers(0.8)
total = doc.remove_artifacts(0.8) # headers + footers, returns count
print("removed", total, "furniture items")
doc.save("clean.pdf")
Go
doc, _ := pdfoxide.Open("report.pdf")
defer doc.Close()
headers, _ := doc.RemoveHeaders(0.8) // count removed
footers, _ := doc.RemoveFooters(0.8)
total, _ := doc.RemoveArtifacts(0.8) // headers + footers
fmt.Printf("removed %d furniture items\n", total)
JavaScript (WASM)
import { WasmPdfDocument } from "pdf-oxide-wasm";
const doc = new WasmPdfDocument(bytes);
doc.removeHeaders(0.8); // returns count removed
doc.removeFooters(0.8);
const total = doc.removeArtifacts(0.8); // headers + footers
const output = doc.save();
doc.free();
C++
#include <pdf_oxide/pdf_oxide.hpp>
auto doc = pdf_oxide::Document::open("report.pdf");
int headers = doc.remove_headers(0.8f); // count removed
int footers = doc.remove_footers(0.8f);
int total = doc.remove_artifacts(0.8f); // headers + footers
std::cout << "removed " << total << " furniture items\n";
Swift
import PdfOxide
let doc = try Document.open("report.pdf")
let headers = try doc.removeHeaders(threshold: 0.8) // count removed
let footers = try doc.removeFooters(threshold: 0.8)
let total = try doc.removeArtifacts(threshold: 0.8) // headers + footers
print("removed \(total) furniture items")
Dart
import 'package:pdf_oxide/pdf_oxide.dart';
final doc = PdfDocument.open('report.pdf');
doc.removeHeaders(0.8); // returns count removed
doc.removeFooters(0.8);
final total = doc.removeArtifacts(0.8); // headers + footers
print('removed $total furniture items');
R
library(pdfoxide)
doc <- pdf_open("report.pdf")
headers <- pdf_remove_headers(doc, 0.8) # count removed
footers <- pdf_remove_footers(doc, 0.8)
total <- pdf_remove_artifacts(doc, 0.8) # headers + footers
cat("removed", total, "furniture items\n")
Julia
using PdfOxide
doc = open_document("report.pdf")
headers = remove_headers(doc, 0.8) # count removed
footers = remove_footers(doc, 0.8)
total = remove_artifacts(doc, 0.8) # headers + footers
println("removed ", total, " furniture items")
Zig
const pdf_oxide = @import("pdf_oxide");
var doc = try pdf_oxide.Document.open("report.pdf");
defer doc.deinit();
const headers = try doc.removeHeaders(0.8); // count removed
const footers = try doc.removeFooters(0.8);
const total = try doc.removeArtifacts(0.8); // headers + footers
std.debug.print("removed {d} furniture items\n", .{total});
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"report.pdf" error:&err];
int32_t headers = [doc removeHeaders:0.8 error:&err]; // count removed
int32_t footers = [doc removeFooters:0.8 error:&err];
int32_t total = [doc removeArtifacts:0.8 error:&err]; // headers + footers
NSLog(@"removed %d furniture items", total);
Elixir
{:ok, doc} = PdfOxide.open("report.pdf")
{:ok, headers} = PdfOxide.remove_headers(doc, 0.8) # count removed
{:ok, footers} = PdfOxide.remove_footers(doc, 0.8)
{:ok, total} = PdfOxide.remove_artifacts(doc, 0.8) # headers + footers
IO.puts("removed #{total} furniture items")
単一ページの装飾を消去する
装飾がどのページにあるかが明確にわかっている場合は、ページ単位の erase_* ファミリーを使用します。ページをまたいだ繰り返し分析なしに、ヘッダーエリア(上部15%)、フッターエリア(下部15%)、またはその両方を消去対象としてマークします。ページインデックスはゼロベースです。
Rust
use pdf_oxide::PdfDocument;
let doc = PdfDocument::open("report.pdf")?;
doc.erase_header(0)?; // erase the top 15% of page 0
doc.erase_footer(0)?; // erase the bottom 15% of page 0
doc.erase_artifacts(0)?; // erase both header and footer of page 0
Python
doc = PdfDocument("report.pdf")
doc.erase_header(0) # erase the top 15% of page 0
doc.erase_footer(0) # erase the bottom 15% of page 0
doc.erase_artifacts(0) # erase both header and footer of page 0
doc.save("clean.pdf")
JavaScript (WASM)
const doc = new WasmPdfDocument(bytes);
doc.eraseHeader(0); // erase the top 15% of page 0
doc.eraseFooter(0); // erase the bottom 15% of page 0
doc.eraseArtifacts(0); // erase both header and footer of page 0
const output = doc.save();
doc.free();
C++
#include <pdf_oxide/pdf_oxide.hpp>
auto doc = pdf_oxide::Document::open("report.pdf");
doc.erase_header(0); // erase the top 15% of page 0
doc.erase_footer(0); // erase the bottom 15% of page 0
doc.erase_artifacts(0); // erase both header and footer of page 0
Swift
import PdfOxide
let doc = try Document.open("report.pdf")
try doc.eraseHeader(0) // erase the top 15% of page 0
try doc.eraseFooter(0) // erase the bottom 15% of page 0
try doc.eraseArtifacts(0) // erase both header and footer of page 0
Dart
import 'package:pdf_oxide/pdf_oxide.dart';
final doc = PdfDocument.open('report.pdf');
doc.eraseHeader(0); // erase the top 15% of page 0
doc.eraseFooter(0); // erase the bottom 15% of page 0
doc.eraseArtifacts(0); // erase both header and footer of page 0
R
library(pdfoxide)
doc <- pdf_open("report.pdf")
pdf_erase_header(doc, 0) # erase the top 15% of page 0
pdf_erase_footer(doc, 0) # erase the bottom 15% of page 0
pdf_erase_artifacts(doc, 0) # erase both header and footer of page 0
Julia
using PdfOxide
doc = open_document("report.pdf")
erase_header(doc, 0) # erase the top 15% of page 0
erase_footer(doc, 0) # erase the bottom 15% of page 0
erase_artifacts(doc, 0) # erase both header and footer of page 0
Zig
const pdf_oxide = @import("pdf_oxide");
var doc = try pdf_oxide.Document.open("report.pdf");
defer doc.deinit();
_ = try doc.eraseHeader(0); // erase the top 15% of page 0
_ = try doc.eraseFooter(0); // erase the bottom 15% of page 0
_ = try doc.eraseArtifacts(0); // erase both header and footer of page 0
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"report.pdf" error:&err];
[doc eraseHeader:0 error:&err]; // erase the top 15% of page 0
[doc eraseFooter:0 error:&err]; // erase the bottom 15% of page 0
[doc eraseArtifacts:0 error:&err]; // erase both header and footer of page 0
Elixir
{:ok, doc} = PdfOxide.open("report.pdf")
{:ok, _} = PdfOxide.erase_header(doc, 0) # erase the top 15% of page 0
{:ok, _} = PdfOxide.erase_footer(doc, 0) # erase the bottom 15% of page 0
{:ok, _} = PdfOxide.erase_artifacts(doc, 0) # erase both header and footer of page 0
完全な墨消しワークフロー
この例では、機密領域を検索し、破壊的墨消しをキューに追加し、メタデータのスクラブとともに適用して、クリーンなファイルを書き出します。
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("sensitive-report.pdf")
# Step 1: locate sensitive text and queue a destructive box for each match.
for i in range(doc.page_count()):
page = doc.page(i)
for t in page.find_text_containing("SSN"):
x, y, w, h = t.bbox # (x, y, width, height)
doc.add_redaction(i, (x, y, x + w, y + h))
# Step 2: apply destructively + scrub metadata (the default).
report = doc.apply_redactions_destructive(scrub_metadata=True)
print("regions:", report["regions"], "glyphs removed:", report["glyphs_removed"])
# Step 3: save the rewritten document.
doc.save("report-redacted.pdf")
Rust
use pdf_oxide::editor::{DocumentEditor, EditableDocument};
use pdf_oxide::redaction::RedactionOptions;
let mut editor = DocumentEditor::open("sensitive-report.pdf")?;
// Step 1: queue destructive boxes (x0, y0, x1, y1 in points).
editor.add_redaction(0, [100.0, 700.0, 300.0, 714.0], None)?;
editor.add_redaction(0, [100.0, 680.0, 300.0, 694.0], None)?;
// Step 2: apply destructively with safe defaults (scrubs metadata).
let report = editor.apply_redactions_destructive(RedactionOptions::default())?;
println!(
"regions: {}, glyphs removed: {}",
report.regions, report.glyphs_removed
);
// Step 3: save.
editor.save("report-redacted.pdf")?;
Java
import fyi.oxide.pdf.*;
import fyi.oxide.pdf.search.SearchMatch;
try (PdfDocument doc = PdfDocument.open("sensitive-report.pdf");
DocumentEditor editor = DocumentEditor.open("sensitive-report.pdf")) {
// Step 1: locate sensitive text and queue a destructive box for each hit.
for (SearchMatch m : doc.search("SSN")) {
editor.addRedaction(m.pageIndex(), m.bbox()); // bbox is x0,y0,x1,y1
}
// Step 2: apply destructively + scrub metadata (the default).
editor.applyRedactionsDestructive();
// Step 3: save.
editor.saveTo(java.nio.file.Path.of("report-redacted.pdf"));
}
Kotlin
import fyi.oxide.pdf.*
PdfDocument.open("sensitive-report.pdf").use { doc ->
DocumentEditor.open("sensitive-report.pdf").use { editor ->
// Step 1: locate sensitive text and queue a box for each hit.
for (m in doc.search("SSN")) {
editor.addRedaction(m.pageIndex(), m.bbox()) // bbox is x0,y0,x1,y1
}
// Step 2: apply destructively + scrub metadata (the default).
editor.applyRedactionsDestructive()
// Step 3: save.
editor.saveTo(java.nio.file.Path.of("report-redacted.pdf"))
}
}
Scala
import fyi.oxide.pdf.{PdfDocument, DocumentEditor, searchSeq}
import scala.util.Using
Using.resource(PdfDocument.open("sensitive-report.pdf")) { doc =>
Using.resource(DocumentEditor.open("sensitive-report.pdf")) { editor =>
// Step 1: locate sensitive text and queue a box for each hit.
for (m <- doc.searchSeq("SSN"))
editor.addRedaction(m.pageIndex, m.bbox) // bbox is x0,y0,x1,y1
// Step 2: apply destructively + scrub metadata (the default).
editor.applyRedactionsDestructive()
// Step 3: save.
editor.saveTo(java.nio.file.Path.of("report-redacted.pdf"))
}
}
Clojure
(require '[pdf-oxide.core :as pdf])
(with-open [doc (pdf/open "sensitive-report.pdf")
ed (pdf/editor "sensitive-report.pdf")]
;; Step 1: locate sensitive text and queue a box for each hit.
(doseq [m (pdf/search doc "SSN")]
(pdf/add-redaction ed (.pageIndex m) (.bbox m))) ; bbox is x0,y0,x1,y1
;; Step 2: apply destructively + scrub metadata (the default).
(pdf/apply-redactions ed)
;; Step 3: save.
(java.nio.file.Files/write
(java.nio.file.Path/of "report-redacted.pdf" (into-array String []))
(pdf/editor-save ed)))
Ruby
require 'pdf_oxide'
doc = PdfOxide::PdfDocument.open('sensitive-report.pdf')
PdfOxide::DocumentEditor.open('sensitive-report.pdf') do |ed|
# Step 1: locate sensitive text and queue a box for each hit.
doc.search('SSN').each do |m|
b = m[:bbox] # { x:, y:, width:, height: }
ed.add_redaction(page: m[:page], rect: [b[:x], b[:y], b[:x] + b[:width], b[:y] + b[:height]])
end
# Step 2: apply destructively + scrub metadata (raises on fail-closed).
ed.apply_redactions!(scrub_metadata: true)
# Step 3: save.
ed.save_to('report-redacted.pdf')
end
C++
#include <pdf_oxide/pdf_oxide.hpp>
auto doc = pdf_oxide::Document::open("sensitive-report.pdf");
auto editor = pdf_oxide::DocumentEditor::open("sensitive-report.pdf");
// Step 1: locate sensitive text and queue a box for each hit.
for (const auto& m : doc.search_all("SSN", /*case_sensitive=*/false)) {
editor.redaction_add(m.page, m.bbox.x, m.bbox.y,
m.bbox.x + m.bbox.width, m.bbox.y + m.bbox.height,
0.0, 0.0, 0.0);
}
// Step 2: apply destructively + scrub metadata.
int glyphs = editor.redaction_apply(/*scrub_metadata=*/true, 0.0, 0.0, 0.0);
std::cout << "glyphs removed: " << glyphs << "\n";
// Step 3: save.
editor.save("report-redacted.pdf");
Swift
import PdfOxide
let doc = try Document.open("sensitive-report.pdf")
let editor = try DocumentEditor.open("sensitive-report.pdf")
// Step 1: locate sensitive text and queue a box for each hit.
for m in try doc.searchAll("SSN", false) {
try editor.redactionAdd(m.page,
x1: m.bbox.x, y1: m.bbox.y,
x2: m.bbox.x + m.bbox.width, y2: m.bbox.y + m.bbox.height,
r: 0, g: 0, b: 0)
}
// Step 2: apply destructively + scrub metadata.
let glyphs = try editor.redactionApply(scrubMetadata: true, r: 0, g: 0, b: 0)
print("glyphs removed: \(glyphs)")
// Step 3: save.
try editor.save("report-redacted.pdf")
Dart
import 'package:pdf_oxide/pdf_oxide.dart';
final doc = PdfDocument.open('sensitive-report.pdf');
final editor = DocumentEditor.open('sensitive-report.pdf');
// Step 1: locate sensitive text and queue a box for each hit.
for (final m in doc.searchAll('SSN', false)) {
editor.redactionAdd(m.page, m.bbox.x, m.bbox.y,
m.bbox.x + m.bbox.width, m.bbox.y + m.bbox.height);
}
// Step 2: apply destructively + scrub metadata.
final glyphs = editor.redactionApply(scrubMetadata: true);
print('glyphs removed: $glyphs');
// Step 3: save.
editor.save('report-redacted.pdf');
R
library(pdfoxide)
doc <- pdf_open("sensitive-report.pdf")
editor <- pdf_editor_open("sensitive-report.pdf")
# Step 1: locate sensitive text and queue a box for each hit.
for (m in pdf_search_all(doc, "SSN", FALSE)) {
b <- m$bbox # list(x=, y=, width=, height=)
pdf_redaction_add(editor, m$page, b$x, b$y, b$x + b$width, b$y + b$height, 0, 0, 0)
}
# Step 2: apply destructively + scrub metadata.
glyphs <- pdf_redaction_apply(editor, scrub_metadata = TRUE, 0, 0, 0)
cat("glyphs removed:", glyphs, "\n")
# Step 3: save.
pdf_editor_save(editor, "report-redacted.pdf")
Julia
using PdfOxide
doc = open_document("sensitive-report.pdf")
editor = open_editor("sensitive-report.pdf")
# Step 1: locate sensitive text and queue a box for each hit.
for m in search_all(doc, "SSN", false)
b = m.bbox
redaction_add(editor, m.page, b.x, b.y, b.x + b.width, b.y + b.height, 0, 0, 0)
end
# Step 2: apply destructively + scrub metadata.
glyphs = redaction_apply(editor, true, 0, 0, 0)
println("glyphs removed: ", glyphs)
# Step 3: save.
save(editor, "report-redacted.pdf")
Zig
const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;
var doc = try pdf_oxide.Document.open("sensitive-report.pdf");
defer doc.deinit();
var editor = try pdf_oxide.DocumentEditor.openEditor("sensitive-report.pdf");
defer editor.deinit();
// Step 1: locate sensitive text and queue a box for each hit.
const hits = try doc.searchAll(a, "SSN", false);
for (hits) |m| {
try editor.redactionAdd(@intCast(m.page), m.bbox.x, m.bbox.y,
m.bbox.x + m.bbox.width, m.bbox.y + m.bbox.height, 0, 0, 0);
}
// Step 2: apply destructively + scrub metadata.
const glyphs = try editor.redactionApply(true, 0, 0, 0);
std.debug.print("glyphs removed: {d}\n", .{glyphs});
// Step 3: save.
try editor.save("report-redacted.pdf");
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"sensitive-report.pdf" error:&err];
POXDocumentEditor *editor = [POXDocumentEditor openEditor:@"sensitive-report.pdf" error:&err];
// Step 1: locate sensitive text and queue a box for each hit.
for (POXSearchResult *m in [doc searchAll:@"SSN" caseSensitive:NO error:&err]) {
POXBbox b = m.bbox;
[editor redactionAddPage:m.page x1:b.x y1:b.y x2:b.x + b.width y2:b.y + b.height
r:0 g:0 b:0 error:&err];
}
// Step 2: apply destructively + scrub metadata.
int32_t glyphs = [editor redactionApplyScrubMetadata:YES r:0 g:0 b:0 error:&err];
NSLog(@"glyphs removed: %d", glyphs);
// Step 3: save.
[editor saveToPath:@"report-redacted.pdf" error:&err];
Elixir
{:ok, doc} = PdfOxide.open("sensitive-report.pdf")
{:ok, editor} = PdfOxide.open_editor("sensitive-report.pdf")
# Step 1: locate sensitive text and queue a box for each hit.
{:ok, hits} = PdfOxide.search_all(doc, "SSN", false)
Enum.each(hits, fn m ->
b = m.bbox
PdfOxide.redaction_add(editor, m.page, b.x, b.y, b.x + b.width, b.y + b.height, 0.0, 0.0, 0.0)
end)
# Step 2: apply destructively + scrub metadata.
{:ok, glyphs} = PdfOxide.redaction_apply(editor, true, 0.0, 0.0, 0.0)
IO.puts("glyphs removed: #{glyphs}")
# Step 3: save.
PdfOxide.editor_save(editor, "report-redacted.pdf")
メソッドリファレンス
破壊的墨消し
| 標準名(C ABI) | Rust (DocumentEditor) |
Python (PdfDocument) |
Go (DocumentEditor) |
C# (DocumentEditor) |
JS (WASM) |
|---|---|---|---|---|---|
pdf_redaction_add |
add_redaction(page, [x0,y0,x1,y1], Option<[r,g,b]>) -> Result<()> |
add_redaction(page, rect, fill=None) |
AddRedaction(page, [4]float64, *[3]float64) error |
AddRedaction(pageIndex, x1, y1, x2, y2, r=0, g=0, b=0) |
addRedaction(page, x0, y0, x1, y1, fill?) |
pdf_redaction_count |
redaction_count(page) -> Result<usize> |
redaction_count(page) -> int |
RedactionCount(page) (int, error) |
RedactionCount(pageIndex) -> int |
redactionCount(page) -> number |
pdf_redaction_apply |
apply_redactions_destructive(RedactionOptions) -> Result<RedactionReport> |
apply_redactions_destructive(scrub_metadata=True, remove_javascript=True, remove_embedded_files=True, fill=(0,0,0)) -> dict |
ApplyRedactions(scrubMetadata bool) (int, error) |
ApplyRedactions(scrubMetadata=true, r=0, g=0, b=0) -> int |
applyRedactionsDestructive(scrubMetadata?) -> RedactionReport |
pdf_redaction_scrub_metadata |
sanitize_document(RedactionOptions) -> Result<RedactionReport> |
sanitize_document(scrub_metadata=True, remove_javascript=True, remove_embedded_files=True) -> dict |
SanitizeDocument() (int, error) |
SanitizeDocument() -> int |
sanitizeDocument(scrub?, removeJS?, removeEmbedded?) -> RedactionReport |
Swift ラッパーは
DocumentEditor上でredactionAdd、redactionCount、redactionApply(scrubMetadata:r:g:b:)、redactionScrubMetadata()として同じファミリーを公開しています。
ヘッダー・フッター・アーティファクト削除
| 標準名(C ABI) | Rust (PdfDocument) |
Python (PdfDocument) |
Go (PdfDocument) |
JS (WASM) |
|---|---|---|---|---|
pdf_document_remove_headers |
remove_headers(threshold: f32) -> Result<usize> |
remove_headers(threshold=0.8) -> int |
RemoveHeaders(threshold float32) (int, error) |
removeHeaders(threshold) -> number |
pdf_document_remove_footers |
remove_footers(threshold: f32) -> Result<usize> |
remove_footers(threshold=0.8) -> int |
RemoveFooters(threshold float32) (int, error) |
removeFooters(threshold) -> number |
pdf_document_remove_artifacts |
remove_artifacts(threshold: f32) -> Result<usize> |
remove_artifacts(threshold=0.8) -> int |
RemoveArtifacts(threshold float32) (int, error) |
removeArtifacts(threshold) -> number |
pdf_document_erase_header |
erase_header(page: usize) -> Result<()> |
erase_header(page) -> None |
— | eraseHeader(page) |
pdf_document_erase_footer |
erase_footer(page: usize) -> Result<()> |
erase_footer(page) -> None |
— | eraseFooter(page) |
pdf_document_erase_artifacts |
erase_artifacts(page: usize) -> Result<()> |
erase_artifacts(page) -> None |
— | eraseArtifacts(page) |
Swift は
Document上でremoveHeaders(threshold:)、removeFooters(threshold:)、removeArtifacts(threshold:)、eraseHeader(_:)、eraseFooter(_:)、eraseArtifacts(_:)を公開しています。Go のPdfDocumentはRemove*ファミリーを公開していますが、ページ単位のErase*ファミリーは公開していません。C# は現在どちらのページ装飾ファミリーも公開していません。
RedactionReport について
redaction_apply と redaction_scrub_metadata は、パスが実際に機能したことをアサートできるようにレポートを返します:
| フィールド | 意味 |
|---|---|
regions |
適用された領域の数。 |
glyphs_removed |
コンテンツストリームから物理的に削除されたグリフ数。 |
images_modified / images_removed |
カバーされたピクセルが上書きされた画像数 / 完全に削除された画像数。 |
paths_pruned |
削除またはジオメトリクリップされたパスのサブパス数。 |
annotations_removed |
削除されたトップレベル項目数(墨消し用のアノテーション、または sanitize_document でサニタイズされたルート)。 |
fonts_scrubbed |
/Widths / /ToUnicode がスクラブされたフォント数。 |
bytes_removed |
削除されたバイト数のベストエフォート推定値。 |
重要事項
- 破壊的であり、外観的なものではありません。
redaction_applyは ISO 32000-1:2008 §12.5.6.23 に従って、対象コンテンツを書き換えられたファイルから物理的に削除します。秘密情報のテキストは単にオーバーレイで覆われるのではなく、消去されます。元の/Contentsオブジェクトはハードドロップされるため、GC見落とし孤立オブジェクトとして残ることはありません。 - フェイルクローズ。 墨消し対象のページに複合フォント(Type0)またはその他の書き換え不能なフォントのテキストが存在する場合、
redaction_applyはサイレントな墨消し不足のリスクを避けるためにエラーを返します。エラーを処理してください。成功を前提としてはいけません。 - 安全なデフォルト値。
RedactionOptions::default()はメタデータをスクラブし、ドキュメントの JavaScript と埋め込みファイルを削除し、非表示の任意コンテンツレイヤーを除去し、ソースの/Redactアノテーションに/IC色が指定されていない場合でも不透明な黒いオーバーレイを描画します。 - 不可逆。 保存後、削除は永続的です。必ず元のドキュメントのコピーで作業してください。
- パフォーマンス。 墨消しとサニタイズは、平均0.8ms・合格率100%の抽出を実現する同じパーサー上で動作するため、墨消しパスのオーバーヘッドはコンテンツ書き換え自体を超えません。
よくある質問
PDF Oxide の墨消しは本当に破壊的ですか?それともテキストの上に黒い箱を置くだけですか?
本当に破壊的です。redaction_apply(apply_redactions_destructive)は ISO 32000-1:2008 §12.5.6.23 に従って、カバーされたグリフ、画像、パスジオメトリをコンテンツストリームから削除し、元の /Contents オブジェクトを出力からハードドロップします。黒いオーバーレイはコンテンツ削除の代わりではなく、それに加えて描画されます。
複合フォントを使用しているページで安全に墨消しできない場合はどうなりますか?
墨消しはフェイルクローズします。redaction_apply は部分的に墨消しして回収可能な断片を残すのではなく、エラーを返します。エラーをキャッチして、そのようなコンテンツを墨消しする必要がある場合はページをラスタライズする方法にフォールバックしてください。
メタデータを消去するだけのために領域を墨消しする必要がありますか?
不要です。ページのジオメトリに触れずに /Info、XMP /Metadata、ドキュメント JavaScript、埋め込みファイルを消去する単独パスには redaction_scrub_metadata(sanitize_document)を呼び出してください。
ヘッダーとフッターはどのように検出されて削除されますか?
PDF Oxide は ISO 32000 の /Artifact タグを優先します(存在する場合は100%正確)。ない場合は、ページの上部または下部15%で少なくとも threshold の割合のページにわたってテキストが繰り返されることをヒューリスティックに検出します(デフォルトは0.8)。
関連ページ
- アノテーション編集 — アノテーションの操作
- ページ操作 — 代替手段としての矩形コンテンツ消去
- テキスト編集 — 墨消し対象テキストの検索
- 暗号化・セキュリティ — 墨消し後のアクセス制限
- PDF/A・コンプライアンス — サニタイズ後のアーカイブ出力