
x-ray
CLIFree Law Project 出品,检查 PDF 里「黑框盖住但底下文字仍能选中复制」的无效涂黑,按页输出泄露的文字与位置(JSON),适合律师在提交或公开文书前做保密自查。需要 Python 3.10 以上;可能漏检,重要文件仍需人工复核。

x-ray is a Python library for finding bad redactions in PDF documents.
Why?
At Free Law Project, we collect millions of PDFs. An ongoing problem is that people fail to properly redact things. Instead of doing it the right way, they just draw a black rectangle or a black highlight on top of black text and call it a day. Well, when that happens you just select the text under the rectangle, and you can read it again. Not great.
After witnessing this problem for years (our favorite is the doc that shared Taylor Swift's personal phone number), we decided it would be good to do something about it.
This tool is our answer. You give the tool the path to a PDF. It tells you if it has worthless redactions in it and whether to call Taylor (don't).
What next?
Right now, x-ray works pretty well and we are using it to analyze documents
in our collections. It could be better though. Bad redactions take many forms.
See the issues tab for other examples we don't yet support. We'd love your
help solving some of tougher cases.
Installation
With uv, do:
uv add x-ray
With pip, that'd be:
pip install x-ray
Usage
uvx lets you run this without even installing it. For example, here's an amicus brief we filed that doesn't have any bad redactions, so it returns an empty dict:
uvx --from x-ray xray https://storage.courtlistener.com/recap/gov.uscourts.ca3.125346/gov.uscourts.ca3.125346.45.0.pdf
{}
Once you do install x-ray, you can easily use it on the command line. Once installed, just:
% xray path/to/your/file.pdf
{
"1": [
{
"bbox": [
58.550079345703125,
72.19873046875,
75.65007781982422,
739.3987426757812
],
"text": "The Ring travels by way of Cirith Ungol"
}
]
}
Or if you have the file on a server somewhere, give it a URL. If it starts with https://, it will be interpreted as a PDF to download. Here's congressional testimony our director made (it doesn't have any bad redactions):
% xray https://free.law/pdf/congressional-testimony-michael-lissner-free-law-project-hearing-on-ethics-and-transparency-2021-10-26.pdf
{}
A fun trick you can do is to make a file with one URL per line, call it urls.txt. Then you can run this to check each URL:
xargs -n 1 xray < urls.txt
However you run xray on the command line, you'll get JSON as output. When you have that, you can use it with tools like jq. The format is as follows:
- It's a dict.
- The keys are page numbers.
- Each page number maps to a list of dicts.
- Each of those dicts maps to two keys.
- The first key is
bbox. This is a four-tuple that indicates the x,y positions of the upper left corner and then lower right corners of the bad redaction. - The second key is
text. This is the text under the bad rectangle.
Simple enough.
You can also use it as a Python module, if you prefer the long-form:
% python -m xray some-file.pdf
But that's not as easy to remember.
In an async function or a notebook that supports top-level await, use xray
in Python:
from pprint import pprint
import xray
bad_redactions = await xray.inspect("some/path/to/your/file.pdf")
pprint(bad_redactions)
{
1: [
{
"bbox": (
58.550079345703125,
72.19873046875,
75.65007781982422,
739.3987426757812,
),
"text": "Aragorn is the one true king.",
}
]
}
The output is the same as above, except it's a Python object, not a JSON object.
inspect() is asynchronous for every input type, including pathlib.Path.
In a synchronous script, import asyncio and use
asyncio.run(xray.inspect(...)) at the entry point. The command-line usage
stays the same.
URL downloads use httpx2 with HTTP/2 enabled, redirects followed, and a 10-second timeout. PDF analysis runs synchronously after downloading.
If you already have the file contents as a bytes object, that'll work too:
import httpx2
import xray
async with httpx2.AsyncClient(
http2=True, follow_redirects=True, timeout=10
) as client:
response = await client.get("https://lotr-secrets.com/some-doc.pdf")
response.raise_for_status()
bad_redactions = await xray.inspect(response.content)
Note that because the inspect method uses the same signature no matter what,
the type of the object you give it is essential:
| Input | xray's Assumption |
|---|---|
str or Pathlib Path | local file |
str that starts with https:// | URL to download |
bytes | PDF in memory |
This means that if you provide the filename on disk as a bytes object instead
of a str, it's not going to work. This will fail:
await xray.inspect(b"some-file-path.pdf")
That's pretty much it. There are no configuration files or other variables to learn. You give it a file name. If there is a bad redaction in it, you'll soon find out.
How it works
Under the covers, xray uses the high-performant PyMuPDF project to parse PDFs. It has been a wonderful project to work with.
You can read the source to see how it works, but the general idea is to:
-
Find rectangles in a PDF.
-
Find letters in the same location
-
Render the rectangle as an image
-
Inspect the rectangle to see if it's all one color. If it is, then that's a bad redaction. If not, then we assume you can see a mix of text and drawings, indicating a redaction that's OK.
The PDF format is a big and complicated one, so it's difficult to do all this perfectly. We do our best, but there's always more to do to make it better. Donations and sponsored work help.
Contributions
Please see the issues list on Github for things we need, or start a conversation if you have questions. Before you do your first contribution, we'll need a signed contributor license agreement. See the template in the repo.
Deployment
Releases happen automatically via Github Actions. To trigger an automated build:
-
Update CHANGES.md
-
Update the version in pyproject.toml
-
Tag the commit with something like "v0.0.0".
If you wish to create a new version manually, the process is:
-
Update version info in
pyproject.toml -
Configure your Pypi credentials with Poetry
-
Build and publish the version:
poetry publish --build
License
This repository is available under the permissive BSD license, making it easy and safe to incorporate in your own libraries.
Pull and feature requests welcome. Online editing in GitHub is possible (and easy!).
相關資源

factur-x
CLI法德电子发票标准 Factur-X(即 ZUGFeRD 2.x)的 Python 库,自带命令行:把普通 PDF 发票和 XML 合成为 Factur-X 发票、从发票 PDF 中取出 XML,并按官方 XSD 校验;也支持 Order-X 订单与 UBL 格式。需本机安装 Python 3。
akretion/factur-x311
OCRmyPDF
CLI给扫描版 PDF 加上可搜索、可复制的文字层,可自动纠偏、旋转并优化体积,默认输出 PDF/A。基于 Tesseract,支持 100 多种语言,完全本地运行。
ocrmypdf/OCRmyPDF35.1k
pdf2docx
CLI把 PDF 转成可编辑的 Word 文档,尽量还原段落、表格、图片和版面,也能单独抽取表格,适合修改只拿到 PDF 版的合同、公文和试卷。需 Python 环境;只适用于电子版 PDF,扫描件需先 OCR。Artifex 已不再主动维护,靠社区修复。
ArtifexSoftware/pdf2docx3.5k
AI Legal Assistant
Skill面向自由职业者和小企业的合同助手:审查合同并给出安全评分和逐条风险,把条款翻成白话,对比两个版本,起草保密协议、隐私政策和谈判反提案,可导出 PDF 报告。用仓库的安装脚本装进 Claude Code;偏美国法语境,结论须由律师复核,导出 PDF 需本地 Python。
zubair-trabzada/ai-legal-claude1.8k
PDF
Skill读取与提取 PDF 文本和表格,合并、拆分、旋转页面,加水印、填写表单,并能对扫描件做 OCR。驱动 Claude 文档能力的 Anthropic 官方技能,源码可见但非开源。
anthropics/skills182k
Docling MCP
MCPDocling 项目官方 MCP:把 PDF、Word、PPT 等文件转成结构化文档,识别版面与表格、对扫描件做 OCR,也能让 agent 逐段生成新文档并导出 Markdown。本地模式需 Python 与 uv,首次运行会下载版面识别模型;也可改连自建的 Docling Serve 服务。
docling-project/docling-mcp773