pylopdf
PDF editing, rendering, extraction, and generation for Python — a pymupdf-style API on a pure-Rust core, MIT licensed, with no mandatory Python dependencies.
Documentation · Getting started · API reference · pymupdf migration guide · Benchmarks
pylopdf combines the 2026 Rust PDF ecosystem behind one Python API: lopdf for editing, hayro (the pure-Rust PDF renderer adopted by Typst) for rendering and positioned extraction, and krilla with HarfRust for shaped, subset-embedded text and form appearances.
Why pylopdf?
| pylopdf | pymupdf | pypdf | pypdfium2 | pdf_oxide | pikepdf | |
|---|---|---|---|---|---|---|
| License | MIT | AGPL / commercial | BSD | Apache/BSD | MIT/Apache-2.0 | MPL-2.0 |
| Wheel size (MiB) | ~5.0–5.8 | ~17.5–24.7 | small (pure Python) | ~2.7–5.0 | ~9.7–10.9 | ~1.9–4.6 |
| Editing (merge / split / rotate / outlines) | ✅ | ✅ | ✅ | limited | ✅ | ✅ (structure-focused) |
| Rendering (PNG / SVG) | ✅ | ✅ | ❌ | ✅ (PNG) | ❌ | ❌ (docs point to other tools) |
| Text extraction | ✅ (positioned text, tables, Markdown) | ✅ (advanced) | ✅ | ✅ | ✅ (advanced, table detection / Markdown) | ❌ (docs point to other tools) |
| Encryption (AES-256) | ✅ read & write | ✅ | ✅ | read only | undocumented | ✅ (via qpdf) |
| Locale-aware multilingual fonts | ✅ (optional extras) | ✅ | — | manual | — | — |
| Implementation | pure Rust | C/C++ | Python | C++ (PDFium) | Rust | C++ (qpdf) |
Wheel sizes are the ranges of published files for pylopdf 0.10.0, pymupdf 1.28.0, pypdfium2 5.12.1, pdf-oxide 0.3.75, and pikepdf 10.10.0 on 2026-07-25.
- AGPL-free: covers the common pymupdf use cases under the MIT license
- Small and self-contained: fits size-constrained environments such as AWS Lambda and Cloudflare Workers
- One wheel per platform: abi3 covers CPython 3.10–3.14, plus a native
cp314twheel for free-threaded Python - Familiar: the API is modeled after pymupdf, with a migration guide
Install
pip install pylopdf
Optional extras:
pip install "pylopdf[jp]" # Japanese rendering fallback and text generation
pip install "pylopdf[ko]" # Korean
pip install "pylopdf[zh-cn]" # Simplified Chinese
pip install "pylopdf[zh-tw]" # Traditional Chinese
pip install "pylopdf[ar]" # Arabic generation (also: he, hi, th)
pip install "pylopdf[ocr]" # offline PP-OCRv6 recognition, pure Rust,
# no system executables or network at runtime
The locale-specific CJK extras provide both non-embedded PDF fallback and
generation fonts. The ar, he, hi, and th extras provide generation
fonts for Arabic, Hebrew, Devanagari, and Thai. Choose jp, ko, zh-cn, or
zh-tw explicitly for the required regional glyph conventions; there is no
generic CJK font extra.
Building from source requires a Rust toolchain: uv sync.
Quickstart
import pylopdf
doc = pylopdf.open("input.pdf") # or pylopdf.open(stream=pdf_bytes)
# Extract: plain text, positioned words, search, tables, Markdown
text = doc.get_page_text(0)
words = doc[0].get_text("words") # (x0, y0, x1, y1, word, block, line, word_no)
hits = doc[0].search_for("tax") # case-insensitive, list[Rect]
tables = doc[0].find_tables() # bordered grids incl. merged cells
markdown = doc.to_markdown() # RAG / LLM preprocessing
# Render: PNG / SVG / raw pixels
png: bytes = doc.render_page(0, dpi=300)
batch = doc.render_pages([0, 1, 2], scale=2, workers=4)
svg: str = doc.render_page_svg(0)
pix = doc[0].get_pixmap(dpi=144) # immutable RGBA8 for NumPy / PIL
# Edit: reorganize pages, merge documents
doc.delete_pages([1, 2])
doc.select([2, 0]) # keep / reorder / duplicate
merged = pylopdf.Document()
merged.insert_pdf(pylopdf.open("a.pdf"))
merged.insert_pdf(pylopdf.open("b.pdf"), from_page=0, to_page=2)
# Generate: images, overlays, shaped subset-embedded multilingual text
page = doc[0]
page.insert_image((72, 72, 200, 200), filename="logo.png")
page.show_pdf_page(page.rect, letterhead) # vector overlay from another PDF
page.insert_text((40, 80), "社外秘", fontsize=20, color=(0.8, 0, 0))
page.insert_text((40, 120), "简体中文", fontsize=20, font_language="zh-CN")
page.replace_text("DRAFT", "FINAL")
page.add_highlight_annot(page.search_for("important"))
# Headers / footers / page numbers (standard-14 fonts)
for i, p in enumerate(doc):
p.insert_text((p.rect.width - 90, p.rect.height - 30), f"Page {i + 1}", fontsize=9)
# Forms (AcroForm)
doc.set_form_field("customer", "Taro Yamada")
doc.set_form_field("agree", True)
# OCR: add a searchable text layer offline (pip install "pylopdf[ocr]")
engine = pylopdf.OcrEngine(threads=4)
page.apply_ocr(engine=engine) # skips pages with existing text
# Save: optimized, or AES-256 encrypted
doc.save("out.pdf", garbage=True, deflate=True, object_streams=True)
doc.save("locked.pdf", user_pw="secret", permissions=pylopdf.Permissions.PRINT)
# Encrypted input and fast probing
doc = pylopdf.open("locked.pdf", password="secret")
info = pylopdf.peek_metadata("input.pdf") # page count / encryption, no full parse
The getting started guide walks through each area; the API reference documents every method, default, and resource boundary.
Features
- Editing — merge, split, select/reorder, rotate, page boxes, blank and copied pages, table of contents, page labels, metadata, file attachments, AES-256 encrypted save, atomic file replacement
- Extraction — plain and positioned text (
words/blocks/dict), case-insensitive search, bordered and opt-in borderless table detection with merged-cell reconstruction, image and vector-drawing extraction, Markdown conversion, vertical and rotated CJK reading order - Rendering — PNG and SVG via hayro, page-region clips, DPI control,
background fills, ordered parallel
render_pages - Generation — JPEG-passthrough/PNG image insertion, vector page overlays
(
show_pdf_page), HarfRust-shapedinsert_text/insert_textboxwith subset embedding and UAX #14 wrapping, highlight and link annotations, AcroForm filling with regenerated appearances, bounded simple-font text replacement - OCR — offline PP-OCRv6 engine (
[ocr]extra), positioned words, idempotent invisible searchable layers, clockwise rotation correction - Untrusted input policy —
DocumentLimits.web()bounds file size, pages, objects, decompression, glyph and output budgets in one opt-in profile; every documented cap raises a typedLimitErrorinstead of degrading silently (security model) - WebAssembly — a static PyEmscripten wheel runs on Cloudflare Python Workers, verified end-to-end every release (guide)
- Concurrency — heavy operations release the GIL, distinct
Documentobjects work in parallel, and a nativecp314twheel supports free-threaded Python 3.14 (contract)
Limitations
pylopdf documents its boundaries instead of guessing. Multicolumn reading order follows deterministic whitespace-gutter rules; borderless table detection is opt-in because aligned prose is geometrically ambiguous. CJK text generation selects one font per run (not per-glyph fallback). Pure RTL lines and lines with one Latin or numeric token bracketed by RTL text are restored to logical Unicode order. Edge tokens, multiple tokens, and general mixed bidirectional paragraph layout remain in producer visual order. Ruby, warichu, and mixed-orientation Japanese typography are not interpreted semantically. Typesetting, PDF/A output, and digital signatures are intentionally out of scope — see the ecosystem recipes below. The API stability policy defines what may change before and after v1.0.
Ecosystem recipes
pylopdf stays a lightweight core; adjacent concerns pair with established libraries. These recipes are covered by integration tests and detailed in the ecosystem guide.
Typesetting and PDF/A output — typst (via typst-py):
import typst
import pylopdf
pdf_bytes = typst.compile("report.typ") # typesetting: typst
doc = pylopdf.open(stream=pdf_bytes) # editing / extraction: pylopdf
pdf_a: bytes = typst.compile("report.typ", pdf_standards="a-2b")
Digital signatures (PAdES) — pyHanko (MIT), which signs with an incremental update so pylopdf's bytes remain untouched:
import io
from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter
from pyhanko.sign import signers
signer = signers.SimpleSigner.load("key.pem", "cert.pem")
out = signers.sign_pdf(
IncrementalPdfFileWriter(io.BytesIO(doc.tobytes())),
signers.PdfSignatureMetadata(field_name="Signature1"),
signer=signer,
)
Validating existing PDFs against PDF/A remains veraPDF's job.
Benchmarks
A reproducible benchmark suite ships with the repository — same corpus, same tasks, medians, and wins and losses published as-is: latest results, free-threaded extraction, and the native OCR report with strict and NFKC character error rates on licensed Japanese fixtures.
uv sync --all-extras --group bench && uv run python bench/run.py
Architecture
pylopdf.Document (Python, pymupdf-style API)
└─ _Document (PyO3)
├─ lopdf 0.44 … editing: open → modify → save
├─ hayro 0.7 … rendering and positioned extraction
└─ krilla 0.8 + HarfRust 0.12
… shaped, subset-embedded text and form appearances
rust/ # PyO3 bindings
src/pylopdf/ # Python high-level API
tests/ # pytest (Rust behavior is verified through Python tests)
Every release installs each published wheel — five abi3-py310, five CPython
3.14t, the sdist, and the PyEmscripten wheel — on architecture-matched runners
and exercises creation, saving, extraction, and rendering before publishing
with build provenance.
Contributing
Bug reports and focused contributions are welcome. See CONTRIBUTING.md for development commands, test expectations, and the rules for sharing PDF regression files:
uv sync # build + install dependencies
uv run pytest # tests
uv run ruff check . # lint
uv run mypy src tests # type check
Report security vulnerabilities privately through GitHub Security Advisories.
License
MIT. Bundled Rust dependencies keep their own permissive licenses (lopdf and HarfRust are MIT; hayro and krilla are MIT/Apache-2.0) — see NOTICE.md.
Metadata
Release files for pylopdf 0.14.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pylopdf-0.14.0.tar.gz | 251.8 kB | Details |
Built distributions (wheels)
Total release size: 82.1 MB
Release files / pylopdf-0.14.0.tar.gz
| Download URL | pylopdf-0.14.0.tar.gz |
|---|---|
| Size | 251.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b29a522aea5a106c5442eaf8a0d84293892935678addb5bea972ddf6121b128f
|
|
BLAKE2b-256 checksum How to use checksums |
fc99200d68caebac20e18cdc54eb6daa58659ee0931e89d0b2d98b28bd68c23c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / pylopdf-0.14.0-cp314-cp314t-win_amd64.whl
| Download URL | pylopdf-0.14.0-cp314-cp314t-win_amd64.whl |
|---|---|
| Size | 7.5 MB |
| Tags | CPython 3.14 CPython 3.14 free-threading Windows x86-64 |
|
SHA-256 checksum How to use checksums |
9574774a1ea697abb0ef68347085d5fda3c8e8ba0a8d37bb50aa9b4beea75ed1
|
|
BLAKE2b-256 checksum How to use checksums |
7c6a8fa0fe938d5638f60b425cef8a3762832035d9e9197590756ac2cd9c499d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / pylopdf-0.14.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | pylopdf-0.14.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 8.3 MB |
| Tags | CPython 3.14 CPython 3.14 free-threading Linux glibc 2.17+ x86-64 |
|
SHA-256 checksum How to use checksums |
f49ae168305a591b93428006f809be498fcf1b962a2b25ff1dd7ef7b61f323df
|
|
BLAKE2b-256 checksum How to use checksums |
851e26680cd4d3f52cda0916375954ffe5a05cc6ab965885548c22047e93e44e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / pylopdf-0.14.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | pylopdf-0.14.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 8.1 MB |
| Tags | CPython 3.14 CPython 3.14 free-threading Linux glibc 2.17+ ARM64 |
|
SHA-256 checksum How to use checksums |
b9761d7090277706bd79767ccfa46c6982d505048f505385f3aac49e70b4c056
|
|
BLAKE2b-256 checksum How to use checksums |
78169ae3a69af637da04c04f8d517dd36d7bf257c7c42b54953d3fa0dfd47274
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / pylopdf-0.14.0-cp314-cp314t-macosx_11_0_arm64.whl
| Download URL | pylopdf-0.14.0-cp314-cp314t-macosx_11_0_arm64.whl |
|---|---|
| Size | 7.2 MB |
| Tags | CPython 3.14 CPython 3.14 free-threading macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
93f2a7146edaa3f3c55f9087e9c91d6bca80348b888dc19b2dcfe5750bd86848
|
|
BLAKE2b-256 checksum How to use checksums |
5216f3fa7a3babab645b160f6904b05fb1071bde008fc6eb4ff75225ffbd8fe8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / pylopdf-0.14.0-cp314-cp314t-macosx_10_12_x86_64.whl
| Download URL | pylopdf-0.14.0-cp314-cp314t-macosx_10_12_x86_64.whl |
|---|---|
| Size | 7.8 MB |
| Tags | CPython 3.14 CPython 3.14 free-threading macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
1dbdfe6918bb75e25f2e7a03a55f546644b3e582a6c992fca2f4357b2bde443e
|
|
BLAKE2b-256 checksum How to use checksums |
8ffc609490e1e361117c220c9e69adf67f477033139c6c5579ae29b3fbf990d8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / pylopdf-0.14.0-cp310-abi3-win_amd64.whl
| Download URL | pylopdf-0.14.0-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 7.5 MB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
d320a85d46c95fa2e478729ae7d1b043c085091f8045511a9c7e64fcdf82de42
|
|
BLAKE2b-256 checksum How to use checksums |
8d1f389c0da1f9f87fe5eb053b2f5cadc41c7d345e604574b864684ea49e2a25
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / pylopdf-0.14.0-cp310-abi3-pyemscripten_2025_0_wasm32.whl
| Download URL | pylopdf-0.14.0-cp310-abi3-pyemscripten_2025_0_wasm32.whl |
|---|---|
| Size | 4.1 MB |
| Tags | CPython 3.10 PyEmscripten 2025.0+ WebAssembly abi3 |
|
SHA-256 checksum How to use checksums |
f4cc719e792816986237e8e77650995d9968624ad378fefdb26aae4a5f135966
|
|
BLAKE2b-256 checksum How to use checksums |
c7b66cc99df9bc3ca00e9849ba2a412ebd0831755a2d3dfe516c743d992ee347
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / pylopdf-0.14.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | pylopdf-0.14.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 8.3 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
8551d53f58db38798919c07759a46c455a9b0d0b3b7f0899b2792fd4082aa2bd
|
|
BLAKE2b-256 checksum How to use checksums |
049907e453563dca0cc9c5cea4ffd0ced98f030b61254950b062af6129da6235
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / pylopdf-0.14.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | pylopdf-0.14.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 8.1 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
2905c26accc0438bb076ddb537023574e3075d5724e5ede44a81d1364b74473f
|
|
BLAKE2b-256 checksum How to use checksums |
0414fd934340079596e1c9696c7ccf41abd7d89e60f844dbb4bfc4c2d486b79c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / pylopdf-0.14.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | pylopdf-0.14.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 7.2 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
cced24850bd2642eeb182ea57e10a8696a3329971331ccf321e7c9cecf942269
|
|
BLAKE2b-256 checksum How to use checksums |
ac6636d5407619e06035c2d9648cb9ad1b1acf20df0df210e714041b49028fea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency logRelease files / pylopdf-0.14.0-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | pylopdf-0.14.0-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 7.8 MB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
ed33529c63e51b2574ee5164041571402de1a8c88a69597c125e9d65eb88d46a
|
|
BLAKE2b-256 checksum How to use checksums |
2b92d3db9201b0ce205e0f608d61a2ceb042e9203d80535168b75956794c15b7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 13, 2026.
Transparency log