# v10

## Breaking changes for v10.x
- Dropped Python 3.9 compatibility, since it is end of life. Python 3.10 through
  3.14 are supported. (Python 3.10 was later dropped in v10.14.0.)
- Dropped macOS 13 support, since it is end of life.
- Dropped macOS 14 Intel wheels, because GitHub doesn't provide a way to build
  them - macOS 15 Intel works fine.
- Dropped macOS Intel wheels and macOS 14 wheels in v10.14.0. Binary wheels now
  require macOS 15 or newer on Apple silicon.
- Dropped deprecated method `Pdf.check()` (use `.check_pdf_syntax()`).

pikepdf supports free-threaded (no-GIL) CPython. Starting with v10.8.0, pikepdf
publishes free-threaded CPython 3.14 (`cp314t`) wheels to PyPI; before v10.8.0,
free-threaded use required building from source. As always, coordinating
concurrent modification of the same object across threads requires a lock -- see
the architecture notes on thread safety.

## v10.18.0

- {meth}`pikepdf.Pdf.add_pages_from` now carries layers (optional content) with
  the pages it copies. The optional content groups that the copied pages
  depend on are listed in the destination's `/OCProperties`, with the default
  visibility, place in the layer list, lock, radio button groups and automatic
  states that the source's default configuration gave them. Previously the
  groups were copied but not listed, so viewers ignored them: the layer list
  was empty and content hidden in the source was shown. {issue}`118`
- {meth}`pikepdf.Pdf.add_pages_from` now carries bookmarks. Outline items of
  the source that lead to a copied page are appended to the destination's
  outline, keeping their hierarchy, titles and style; items that lead to pages
  that were not copied are dropped. {issue}`281`
- Both are on by default, which changes what existing callers of
  `add_pages_from` get: the destination may gain an `/OCProperties` and
  outline items it did not gain before. Pass `layers='ignore'` or
  `outlines='ignore'` for the previous behaviour, for example if you build
  the merged outline yourself. {class}`pikepdf.PageCopyResult` reports
  `layers_added`, `outline_items_added` and `outline_items_dropped`.
- {meth}`pikepdf.Pdf.check_pdf_syntax` now performs the same checks as
  `qpdf --check` and reports the same problems. It checks the linearization
  data and hint tables of linearized files, which previously required a
  separate call to {meth}`pikepdf.Pdf.check_linearization`, and checks the
  interactive form, embedded files, page labels and outlines. A document with
  no pages is reported as `ERROR: file does not contain any pages`, and a page
  whose content stream cannot be read is reported as `ERROR: page N: ...`
  along with the other problems found, where it previously raised
  {class}`pikepdf.PdfError`. {issue}`515`
- Inserting a {class}`pikepdf.Dictionary` or {class}`pikepdf.Array` that
  refers to another `Pdf` into a `Pdf` now raises
  {exc}`pikepdf.ForeignObjectError`, naming the key or index where the
  foreign object was found, and leaves the target unchanged. This covers an
  indirect object of another `Pdf` held anywhere in the value, such as
  `pdf3.Root.X = Dictionary(P=pdf1.Root.Y)`, and a direct dictionary or array
  that another `Pdf` already contains. Previously the value was accepted:
  the first kind made the `Pdf` impossible to save, failing part way through
  writing, and the second left two documents sharing one object. It applies
  to every way of inserting a value, including {meth}`pikepdf.Pdf.make_indirect`,
  {meth}`pikepdf.Page.add_resource`, adding a page built from a dictionary,
  name and number trees, and stream dictionaries. Use
  {meth}`pikepdf.Pdf.copy_foreign` to bring the foreign object over first.
- A password containing a NUL character now raises {exc}`ValueError`, both
  when opening a PDF and when encrypting one. Previously the password was
  silently cut short at the NUL, so a wrong password could open a file, and an
  encrypted file could be written with a shorter password than requested.
  PDF passwords may not contain NUL. {issue}`756`
- Fixed threads interfering with each other inside one `Pdf` on GIL-enabled
  builds, which could cause an object to be read as `None`. The per-`Pdf` lock
  is now in force on every build, and {meth}`pikepdf.Pdf.lock` works on
  GIL-enabled builds too. {commit}`7c761d23`
- Methods implemented in Python that change a `Pdf` in several steps, such as
  {meth}`pikepdf.Page.add_overlay`, {meth}`pikepdf.Pdf.add_pages_from` and
  the {mod}`pikepdf.sanitize` functions, now hold its lock until they finish.
  The thread safety section of the architecture notes now says which calls are
  atomic. {commit}`0872a6e8`, {commit}`b10671c3`
- Two threads that each hold the lock of one `Pdf` and then need the other's
  no longer deadlock; one of them gets a {exc}`RuntimeError` instead. What a
  callback such as a {class}`pikepdf.TokenFilter` may do is now documented:
  see {ref}`callback-restrictions`. {commit}`11ef776b`
- Fixed a crash, a native stack overflow that no Python `except` can catch,
  when reading, saving or checking a PDF in which a JBIG2 image's
  `/JBIG2Globals` stream is itself JBIG2-encoded and leads back to the image,
  directly or through other streams. Such an image is now reported as
  unfilterable, as JBIG2-encoded globals without a cycle already were:
  reading it raises {exc}`~pikepdf.exceptions.PdfError`, extracting it raises
  {exc}`~pikepdf.exceptions.UnsupportedImageTypeError`, and saving writes it
  unchanged with a warning. {issue}`753`
- A JBIG2 image's `/JBIG2Globals` are now read only when the image is being
  JBIG2-decoded, rather than every time qpdf checks whether the image can be
  decoded, which included every save.
- Reading the label of a page whose page label style is an unknown name, such
  as `/S /Z`, now warns and keeps the label prefix, as an invalid non-name
  style already did. Previously it raised `KeyError`.
- Reading the label of a page whose number cannot be written in its page
  label style, such as `/S /R /St 0` or a Roman numeral above 5000, now warns
  and writes the number in decimal digits. Previously it raised `ValueError`.
- Extracting an image whose `/CalGray` or `/CalRGB` colour space array lacks
  its parameter dictionary now decodes it as the corresponding device space,
  without an ICC profile, as a missing `/WhitePoint` already did. Previously
  it raised `IndexError`.
- An ICCBased image whose profile stream omits the required `/N`, or gives
  one that is not an integer, now takes its mode and default `/Decode` from
  the profile's colour space. Previously its mode raised `KeyError`.
- Fixed {meth}`pikepdf.form.SignatureField.stamp_overlay` stamping the
  overlay twice when the signature widget names its page with `/P`.
- Fixed a `Pdf` returned by {meth}`pikepdf.Job.create_pdf` not being set up
  like other `Pdf` objects: setting its `conversion_mode` raised
  {class}`pikepdf.PdfError`, and on free-threaded Python none of its methods
  were protected by a lock.
- `Matrix.__array__(copy=False)` now raises `ValueError`, as numpy expects of
  an object that cannot share its memory. The check for this was written in a
  way that never fired.
- A `progress` argument to {meth}`pikepdf.Pdf.save` that is not callable is
  now rejected with `TypeError` before anything is written. Previously the
  error was raised part of the way through writing the file.
- Fixed a small memory leak each time
  {meth}`pikepdf.AcroForm.get_fields_with_qualified_name` or
  {meth}`pikepdf.AcroForm.fix_copied_annotations` returned a field.
- Iterating over a {class}`pikepdf.NameTree` or {class}`pikepdf.NumberTree`
  now iterates over the keys it had when the iteration began. Previously the
  tree was read as the iteration went on, so changing it meanwhile gave
  undefined results.
- Putting an object that belongs to another `Pdf` in a
  {class}`pikepdf.NameTree` or {class}`pikepdf.NumberTree` now raises
  {class}`pikepdf.ForeignObjectError` at once, as it does for a dictionary or
  an array. Previously the error was not raised until the `Pdf` was saved.
- `x in tree` is now `False` for an `x` that cannot be a key of a
  {class}`pikepdf.NameTree`, such as an `int`, and for `None` in a
  {class}`pikepdf.NumberTree`. Previously these raised `TypeError`.
- Corrected the documentation and type hints of {class}`pikepdf.NameTree`,
  which said that names are `bytes`. Names are, and have always been, `str`:
  qpdf reads each name as a PDF text string and compares names by that text.
- Fixed building from source on Android (Termux), where importing pikepdf
  failed with `cannot locate symbol "_Py_TrueStruct"` since v10.6.0. The
  extension module is now linked against libpython when the build interpreter
  is an Android Python, as that platform requires. Android is not a tested
  platform. {issue}`752`

### Free-threaded Python fixes

Many crashes and data races that occurred when several threads used the same
`Pdf` at once have been fixed. The test suite now runs under ThreadSanitizer
in continuous integration, which is how most of them were found
({commit}`4831f6fc`).

- Every binding that reads or changes a `Pdf` now takes that `Pdf`'s lock,
  and the lock of any other `Pdf` among its arguments, enforced at compile
  time. Many bindings previously did not, notably most methods of
  {class}`pikepdf.NameTree`, {class}`pikepdf.NumberTree`, attachments,
  annotations and {class}`pikepdf.AcroForm`. {commit}`3c5b2bc4`
- Fixed crashes and data races in {class}`pikepdf.Page` methods, content
  stream parsing, adding pages from one `Pdf` to another, and dropping the
  last reference to a `Pdf` while another thread used one of its objects.
  {commit}`04999faf`
- A {class}`pikepdf.Dictionary` or {class}`pikepdf.Array` that belongs to no
  `Pdf` is now protected by a lock shared by all such objects. Threads that
  each build objects of their own contend for it, so add a `Dictionary` or
  `Array` to its `Pdf` as soon as it is created rather than building large
  structures before adding them. {commit}`5e11a240`, {commit}`b10671c3`
- Fixed "dictionary changed size during iteration" when several threads
  opened a `Pdf`'s metadata for the first time at once. {commit}`d52a9a11`
- Threads that take turns using the same `Pdf` are no longer slowed to a
  crawl when every CPU is busy. {commit}`a3e2b882`

Free-threaded wheels are not provided for Windows in this release. They crash
in the test suite, for reasons that have not yet been found, so free-threaded
Python on Windows is not supported for now. Linux and macOS free-threaded
wheels are provided as before. {issue}`757`

## v10.17.0

- Added {class}`pikepdf.ObjectSet`, which records the objects that a walk of
  a PDF's object graph has already visited. Objects in a PDF are shared and
  may refer back to their ancestors, so a search that only limits its depth
  can take exponential time on quite ordinary files. `ObjectSet.add()` reports
  whether an object is new, and tells apart direct objects and objects of
  different `Pdf`, which a `set` of `objgen` cannot. See
  {ref}`walking-object-graphs`.
- Added {meth}`pikepdf.Object.is_same_object_as`, which tests whether two
  references lead to the same object. Neither `is`, `==` nor comparing
  `objgen` answers this reliably.
- Added {meth}`pikepdf.Pdf.content_scopes` and
  {meth}`pikepdf.Page.content_scopes`, which iterate over every content stream
  that pages can draw, each with the resource dictionary it uses: the page
  itself, form XObjects, annotation appearance streams, tiling patterns,
  Type 3 glyph procedures and soft masks, however they are nested or shared.
  This is the way to find every font, image or colour space a document uses.
  Each {class}`pikepdf.ContentScope` is reported once for the document;
  `pages=` selects pages and `kinds=` selects what is reported.
- Added {meth}`pikepdf.Page.get_resources`, which returns the resources of a
  page or form XObject for reading. Unlike {attr}`pikepdf.Page.resources`, it
  finds resources inherited from the page tree, does not create an empty
  dictionary when there are none, and returns `None` instead of raising when
  `/Resources` is malformed.
- Fixed comparing two objects with `==` taking exponential time when the
  objects being compared share children, such as the same file opened twice.
  Each pair of objects is now compared once.
- Fixed {meth}`pikepdf.Pdf.add_pages_from` hanging on a page with an action
  whose `/Next` actions are shared or form a loop.
- Fixed {meth}`pikepdf.Pdf.open_outline` reading only the first item at each
  level of an outline whose items are direct objects. Such items are now made
  indirect when the outline is saved, as the PDF specification requires.
- Reading a stream that will not decode now raises
  {exc}`~pikepdf.exceptions.DataDecodingError` with the reason, whether the
  stream's data was read from a file or set from memory. Previously a stream
  read from a file raised a {exc}`~pikepdf.exceptions.PdfError` that said only
  "read_bytes called on unfilterable stream", and the reason was available
  only from {meth}`pikepdf.Pdf.get_warnings`. `DataDecodingError` is a
  `PdfError`, so existing handlers are unaffected. {issue}`182`
- An exception raised by a JBIG2 decoder now reaches the caller of
  {meth}`pikepdf.PdfImage.as_pil_image`, {meth}`pikepdf.PdfImage.extract_to`
  and {meth}`pikepdf.PdfImage.read_bytes` unchanged, with its own type,
  message and traceback. Previously, for an image read from a file, it was
  replaced by a generic `DataDecodingError` or `PdfError`. {issue}`182`
- Added {exc}`~pikepdf.exceptions.QpdfRuntimeError`, raised for errors from
  qpdf that pikepdf has no more specific exception for. These were previously
  raised as a bare `RuntimeError`, which `except pikepdf.PikepdfError` did not
  catch. The new exception derives from both
  {exc}`~pikepdf.exceptions.PikepdfError` and `RuntimeError`, so handlers
  written for `RuntimeError` still work. {issue}`240`
- The empty name `/` is now supported. The PDF 1.7 and 2.0 specifications
  define a lone slash as a valid name made of zero characters, but
  `pikepdf.Name('/')` raised `ValueError`, and dictionaries refused `/` as a
  key. This broke {meth}`pikepdf.Pdf.add_pages_from` on files
  with an empty-named destination such as `/Dests << / 3 0 R >>`. Such names
  can now be created, used as dictionary keys, and migrated during page
  copying. `bool(Name('/'))` is `False`, like an empty {class}`pikepdf.String`.
  `Name('')`, which lacks the leading slash, is still rejected. PDF/A does not
  forbid empty names, and the PDF/A validator accepts them. Thanks to
  @sjudd for the report and @DSeaStar for the analysis. {issue}`745`
- `AcroFormField.get_inheritable_field_value_as_name()` now returns a
  {class}`pikepdf.Name`, as documented, instead of a `str`. When the value is
  missing or not a name, it returns the empty name `Name('/')`, which is falsy,
  instead of `''`. Comparisons such as `== '/Tx'` are unaffected. {issue}`748`
- Fixed an out-of-bounds read when opening a PDF from a Python file object
  whose `readinto()` reports more bytes than were requested. This now raises
  `ValueError`, as writing to such a stream already did. Non-blocking streams,
  whose `readinto()` returns `None`, now raise `ValueError` instead of hanging.
  Thanks to @Nayana-Naik73. {issue}`747`
- Fixed building from source on PyPy, which failed to compile since v10.0.
  PyPy now reads Python file objects through `readinto()`, like CPython,
  instead of copying the result of `read()`.
- {func}`pikepdf.open` reads directly from the file descriptor when given a
  filename, instead of calling the `seek()` and `readinto()` methods of a
  Python file object for every read. Opening and reading a large file with
  many objects is about four to five times faster, matching the speed of
  memory mapping (`access_mode=AccessMode.mmap`). Streams passed to
  {func}`pikepdf.open`, including plain file objects, are read through their
  methods as before.
- Documented {class}`pikepdf.TokenFilter` with worked examples that shorten
  numbers and convert RGB colors to grayscale, and explained when to use a
  token filter instead of {func}`pikepdf.parse_content_stream`. The API
  reference no longer says that the parser should not be used to edit content
  streams. The `TokenFilter.handle_token` documentation now says correctly that
  exceptions it raises propagate to the caller. {issue}`62`

## v10.16.0

- Added binary wheels for Windows on ARM64. Thanks to @ndabas. {issue}`744`
- Binary wheels now bundle qpdf 12.4.2, which is also the new minimum version
  of qpdf, since it is the first release with Windows ARM64 binaries.
- {func}`pikepdf.sanitize.remove_javascript`,
  {func}`pikepdf.sanitize.remove_external_access`,
  {func}`pikepdf.sanitize.remove_multimedia` and the matching
  {class}`pikepdf.sanitize.Sanitizer` steps missed actions attached to form
  field dictionaries. Actions survived on a field stored separately from its
  widget annotation, on a hidden field with no widget (such as a calculation
  helper), on nested fields under `/Kids`, and on widgets that appear on no
  page. The sanitizer now walks the whole `/AcroForm` field tree. Thanks to
  Stjorn for the report.
- The same sanitizer steps left an action `/Next` chain untouched past its
  depth limit of 50, so a targeted action (for example a `/JavaScript` action)
  buried deeper survived, and the step that preserves benign downstream actions
  could promote it to a shallower position. The chain is now severed at the
  limit, which also drops any benign actions past it. Thanks to @Nayana-Naik73.
  {issue}`746`
- The sanitizer now also drops anything in a `/Next` chain that is not an
  action dictionary, such as a nested array, instead of passing it through
  unexamined. It raised `AttributeError` or `ValueError` on some such values
  (for example `/Next [42]`), and now runs in explicit conversion mode
  regardless of the caller's settings, so malformed values of the wrong type
  are skipped rather than crashing it.

## v10.15.0

### PDF/A-3 attachments

- Attaching a file through `Pdf.attachments` now lists its file specification
  in the document catalog's `/AF` (associated files) array, and deleting or
  replacing an attachment removes its entry. Together with the
  `/AFRelationship` pikepdf already writes, a file attached to a PDF/A-3
  document now keeps it conformant. qpdf maintains only the `/EmbeddedFiles`
  name tree, so this was previously left to the user. {issue}`463`
- The PDF/A validator now approves embedded files in PDF/A-3b documents: the
  `/EmbeddedFiles` name tree and the catalog's `/AF` array are checked against
  clause 6.8 of ISO 19005-3 (MIME type, `/F` and `/UF`, `/AFRelationship`,
  and that every embedded file is an associated file). Embedded files anywhere
  else, such as in file attachment annotations, are still reported as
  unsupported; they remain violations in PDF/A-1b and unsupported in PDF/A-2b.
  {func}`pikepdf.pdfa.prepare` gives PDF/A-3 embedded files a missing MIME type
  (`application/octet-stream`) and `/AFRelationship` (`/Unspecified`), and
  lists unassociated ones in the catalog's `/AF` array; new
  {class}`pikepdf.pdfa.PrepareResult` fields count these repairs. See
  {ref}`pdfa-attachments`.

### PDF/A named destinations

- The PDF/A validator now checks named destinations, in the `/Dests` name tree
  and in the catalog's `/Dests` dictionary, instead of reporting them as
  unsupported. Named destinations are common: pdfTeX (hyperref), Acrobat
  Distiller and JasperReports write them. It also checks named appearance
  streams (`/Names /AP`) as Form XObjects.
- `/AlternatePresentations` is now reported as a violation of PDF/A-2 and
  PDF/A-3 (clause 6.10), and document-level JavaScript (`/Names /JavaScript`)
  as a violation of the rule forbidding JavaScript actions; both were
  reported as unsupported. {func}`pikepdf.pdfa.prepare` removes them.
- {func}`pikepdf.pdfa.prepare` gives the encoding of a non-symbolic TrueType
  font that has `/Differences` but no `/BaseEncoding` the base encoding
  `/WinAnsiEncoding`, as PDF/A-2 and PDF/A-3 require, when no glyph the
  document draws would change. Older producers such as OpenPDF write such
  encodings. New {class}`pikepdf.pdfa.PrepareResult` fields report these
  repairs.

### PDF/A developer extensions

- The PDF/A validator now checks the Adobe developer extensions dictionary
  (`/Extensions << /ADBE ... >>`) that Acrobat writes in the document catalog,
  instead of reporting every such file as unsupported. qpdf rewrites this
  entry to match the written version and extension level, and removes it for
  PDF/A-1, and the validator checks the entry as it will be written. Other
  developer extensions remain unsupported.
- {func}`pikepdf.pdfa.resolve_save_kwargs` accepts an extension level in
  `min_version` or `force_version` for PDF/A-2 and PDF/A-3, such as
  `('1.7', 8)`.

### Metadata

- New {meth}`pikepdf.models.PdfMetadata.copy_properties` copies named XMP
  properties from other metadata with their whole value: every language of a
  language alternative, array order, structures and qualifiers, which
  assigning a value read from the source would lose. Properties are read from
  every `rdf:Description` whatever its `rdf:about`, and the structure of known
  properties is checked as for assignment. This is useful to restore metadata
  another tool dropped while rewriting a document.

### PDF/A annotations

- {func}`pikepdf.pdfa.repair_annotation_flags`, the step of
  {func}`pikepdf.pdfa.prepare` that removes hidden annotations and sets the
  Print flag on the rest, is now public, with its result
  {class}`pikepdf.pdfa.AnnotationRepairResult`. Running it before handing a file
  to another PDF/A converter such as Ghostscript keeps hyperlinks that producers
  wrote without the Print flag, which Ghostscript's PDF/A mode deletes.

### Fixes

- The PDF/A validator reported the standard stream key `/DL` (decoded length)
  as unsupported on content streams, images, metadata streams and output
  intent profiles, so a file whose producer writes it could not be converted.
  It is now accepted on every stream.
- The PDF/A validator accepted glyph names in a non-symbolic TrueType font's
  `/Differences` only from the Adobe Glyph List for New Fonts, so it rejected
  valid PDF/A-2 and PDF/A-3 files that used other names from the full Adobe
  Glyph List, such as `afii10017` for Cyrillic text. It now accepts every name
  in the full list, as ISO 19005 requires and veraPDF checks. Names outside the
  list, such as `uni0410` or `A.sc`, are still rejected.
- A `Pdf` opened or created with `conversion_mode='explicit'` returned plain
  `int` for some numbers that qpdf writes itself, such as `/Pages /Count`
  after adding or removing a page, `/Rotate` after {meth}`pikepdf.Page.rotate`,
  or the `/BBox` of {meth}`pikepdf.Page.as_form_xobject`. These values now
  follow the document's conversion mode like any other.
- In explicit conversion mode, `Integer` and `Real` now support the rest of
  the Python number protocol, with the same results as the `int` or `Decimal`
  that implicit mode produces: `round()`, `math.trunc()`, `math.floor()`,
  `math.ceil()`, `divmod()`, and format specs such as `f'{x:.2f}'`.
  `int()` of a `Real` now truncates, as `int()` of a `Decimal` does, instead
  of raising `TypeError`; for example `int(page.Rotate)` now works on a
  `/Rotate 90.0`.
- The type stubs for `Matrix` now accept a `pikepdf.Object`, such as a
  `/Matrix` array, and a list of objects, as the runtime always has.


## v10.14.0

Several improvements to explicit conversion mode and `NamePath`, prompted by
the OCRmyPDF project's migration to these APIs
(`pikepdf.explicit_conversion()`, the `as_*` safe accessors, and `NamePath`)
in a production codebase that reads untrusted, often malformed, PDFs.

### Python support

- Dropped support for Python 3.10, which reaches end of life on October 31,
  2026. Python 3.11 through 3.14 are supported, and wheels are no longer built
  for CPython 3.10. pip will keep installing pikepdf 10.13 on Python 3.10.
- Internal code now uses Python 3.11 features: `tomllib`, `enum.StrEnum`,
  `typing.Self`, `datetime.UTC` and the broader ISO 8601 support in
  `datetime.fromisoformat`. The compatibility shims for Python 3.10 are gone.
- CI now tests against CPython 3.15 (release candidate). CPython 3.15 uses the
  existing `cp314-abi3` wheel; free-threaded CPython 3.15 gets its own
  `cp315t` wheel, since the stable ABI does not cover free-threaded builds.

### Platform support

- macOS wheels are now built on GitHub's macos-15 runner, since GitHub is
  deprecating the macos-14 runner. Binary wheels now require macOS 15 or newer.
- Dropped macOS Intel (x86_64) wheels. We no longer test macOS on Intel, since
  the platform is end of life. Intel Mac users can build from source, or keep
  using pikepdf 10.13.

### Conversion mode

- {meth}`pikepdf.Pdf.open` and {meth}`pikepdf.Pdf.new` now accept a
  keyword-only `conversion_mode` argument (`'implicit'` or `'explicit'`), and
  the mode can be read or changed afterward through the new
  {attr}`pikepdf.Pdf.conversion_mode` property. A `Pdf`'s mode travels with
  it across threads and does not affect any other document, which makes it
  the right scope for a library embedded inside a host application. Setting
  the property to `None` reverts to inheriting the context-manager/global
  mode.
- Added {func}`pikepdf.implicit_conversion`, a thread-local context manager
  symmetric with {func}`pikepdf.explicit_conversion`, so implicit mode can be
  forced from inside code that runs under explicit mode.
- Precedence when several scopes are in play: context manager, then per-`Pdf`
  mode, then the global default set by
  {func}`pikepdf.set_object_conversion_mode`. An object with no owning `Pdf`
  (e.g. a bare `pikepdf.Dictionary(...)` you have not yet attached to a
  document) resolves through the context-manager/global scopes; once it is
  inserted into a `Pdf` it follows that document's mode.
- Added {meth}`pikepdf.Object.get_raw`, which behaves like
  {meth}`~pikepdf.Object.get` (key, `Name`, or `NamePath`, plus a `default`)
  but never unboxes the result: it always returns a `pikepdf.Object`
  regardless of the current conversion mode. A null stored inside an *array*
  comes back as a `Null`-typed object rather than `None`; in a *dictionary*,
  qpdf treats a key whose value is null as absent, so `get_raw` returns the
  default for it, exactly as `get` does.
- Added container-level typed getters {meth}`~pikepdf.Object.get_int`,
  {meth}`~pikepdf.Object.get_bool`, {meth}`~pikepdf.Object.get_float`,
  {meth}`~pikepdf.Object.get_decimal`, {meth}`~pikepdf.Object.get_dict`, and
  {meth}`~pikepdf.Object.get_list`, each `(key_or_path, default=None)`. These
  compose `get_raw` with the matching `as_*` accessor, so reading an optional
  value of uncertain type is a mode-independent one-liner instead of a
  hand-written `with pikepdf.explicit_conversion(): ...` wrapper.
- Added `coerce=True` to {meth}`~pikepdf.Object.as_int`,
  {meth}`~pikepdf.Object.as_bool`, {meth}`~pikepdf.Object.as_float`, and
  {meth}`~pikepdf.Object.as_decimal`, and to the corresponding `get_*`
  getters, for reading values that real-world PDFs encode with a "nearby"
  type: `as_int(coerce=True)` accepts a `Real` (truncated toward zero) and a
  numeric `String`; `as_bool(coerce=True)` accepts a nonzero `Integer` or
  `Real` (fixing the common `/Marked 1` case that previously required a
  fallback to `as_int() != 0`); `as_float`/`as_decimal` with `coerce=True`
  accept an `Integer` and a numeric `String`, including exponent notation
  such as `"1e-5"`.
- `pikepdf.Integer` and `pikepdf.Real` now support the ordering comparisons
  `<`, `<=`, `>` and `>=` against Python `int`, `float`, `bool`, `Decimal`,
  and each other, so `box[0] < box[2]`, `sorted()`, `min()` and `max()` work
  on explicit-mode values. `Real` compares by its exact decimal value.
- Arithmetic on `pikepdf.Integer` and `pikepdf.Real` now works between two
  objects (`Real('2.5') + Integer(3)`) and with `Decimal` and `bool`
  operands, and `**` is supported. Results are the native Python type that
  implicit mode would have produced: `int` for `Integer` with `int`/`Integer`,
  `Decimal` whenever a `Real` or `Decimal` is involved, and `float` with a
  `float` operand. In explicit mode, `box[2] - box[0] > 100` therefore works
  without unboxing, while raising `TypeError` if the PDF stored a non-number.
  The result is never a pikepdf object: a number computed from a document is
  not in the document, and returning an object would let one unmigrated read
  propagate into code far from the cause.
- Added {func}`pikepdf.unbox`, which returns the native Python value of an
  `Integer`, `Boolean` or `Real` and passes any other value through
  unchanged. Unlike the `as_*` accessors it does not require knowing which
  numeric type a value has, and it accepts values read in either mode, so it
  is the one-token migration for a read that feeds `isinstance`, `is True`,
  `Decimal()`, `json.dumps` or a function documented to return a native type.
- Added module-level typed conversions {func}`pikepdf.as_int`,
  {func}`~pikepdf.as_bool`, {func}`~pikepdf.as_float`,
  {func}`~pikepdf.as_decimal`, {func}`~pikepdf.as_dict`,
  {func}`~pikepdf.as_list`, {func}`~pikepdf.as_str` and
  {func}`~pikepdf.as_bytes`, each `(value, default=None)`, with keyword-only
  `coerce` for the numeric and boolean ones. They are the value-side twins of
  the `get_*` typed getters, for an array element, content stream operand or
  other value already in hand: a pikepdf object converts exactly as its
  `as_*` method does, and a native value is accepted only if it is the type
  implicit mode would have produced for that PDF type, so the result is the
  same in either conversion mode. `pikepdf.as_int(True)` gives the default,
  because a Boolean is not an Integer.
- Added {meth}`~pikepdf.Object.as_str` and {meth}`~pikepdf.Object.as_bytes`,
  which return a `String`'s decoded text or raw bytes, and raise `TypeError`
  (or return a supplied *default*) for any other type, unlike `str()` and
  `bytes()`, which accept almost anything. Added the matching typed getters
  {meth}`~pikepdf.Object.get_str` and {meth}`~pikepdf.Object.get_bytes`.
- See {doc}`/topics/objects` for the full description of scopes and
  precedence, plus a "Migrating to explicit mode" checklist.
- Added a new topic page, {doc}`/topics/type_safety`, explaining why
  explicit mode exists and setting out the deprecation schedule: v11 warns
  on implicit conversions, v12 makes explicit mode the default, and v13
  removes implicit mode. New code that reads values of uncertain type should
  prefer the mode-independent getters described above.

### NamePath

- `path in obj` now tests whether the path can be traversed to a value, and
  `del obj[path]` deletes the final component after traversing to its
  parent, both for name and array-index components.
- `NamePath` is now iterable, yielding its individual components (`str` for
  names, `int` for indices) in order, e.g. `list(NamePath.A.B[0])` gives
  `['/A', '/B', 0]`.
- `NamePath` instances now compare and hash by their component sequence, so
  `NamePath.A.B == NamePath.A.B` is `True` and a `NamePath` can be used as a
  dict key or stored in a `set`.
- `isinstance(path, pikepdf.NamePath)` now works, and `pikepdf.NamePath` can
  be used directly in type annotations (e.g.
  `def f(key: pikepdf.Name | pikepdf.NamePath)`) without importing a private
  name.

### Metadata and PDF/A

OCRmyPDF tested its PDF/A output against veraPDF, and found files that
pikepdf's metadata handling made invalid.

- XMP language alternatives such as `dc:title` and `dc:description` are now
  read from their `x-default` item, as the XMP specification defines, rather
  than from the first item. Acrobat writes `x-default` last when a document
  has several languages, so `/Title` and `/Subject` were synced from the wrong
  language, and PDF/A-1 validators rejected the result.
- **Behavior change:** Setting a language alternative now updates its
  `x-default` item and keeps the other languages, instead of replacing all of
  them with the new value. An item that held the same text as the old default
  is updated too, as Adobe's XMP toolkit does. Assigning several values to a
  language alternative that pikepdf does not know joins them into one
  `x-default` item, instead of writing several `x-default` items.
- XMP properties are now read from every top-level `rdf:Description`, not only
  those with `rdf:about=""`. Properties in Descriptions with
  `rdf:about="uuid:..."` (written by Distiller and older Acrobat) or with no
  `rdf:about` could not be read or deleted, and setting one added a duplicate,
  which made veraPDF reject the whole packet. Setting a property now removes
  any other occurrences of it, and deleting it removes all of them.
- **Behavior change:** When XMP is written, every top-level `rdf:Description`
  gets the same `rdf:about` value, as the XMP specification requires, and
  empty Descriptions are removed. The value is kept if the non-empty values
  agree, and made empty if they conflict.
- Added {attr}`pikepdf.models.PdfMetadata.recovered` and
  `XmpDocument.recovered`, which are `True` if the XMP was not well-formed and
  had to be repaired or replaced as it was read.
- Added {meth}`pikepdf.PdfInlineImage.read_raw_bytes`, which returns the
  still-encoded data of an inline image exactly as it appears in the content
  stream.
- {meth}`pikepdf.Pdf.save` now corrects `/Count` in each node of the page
  tree to the number of pages beneath it. qpdf repairs a damaged page tree but
  left `/Count` as it was, so a wrong `/Count` in the input was written
  unchanged, and veraPDF could not validate the file at all.

### PDF/A validation and repair

- Added the {mod}`pikepdf.pdfa` module, which prepares, checks and saves
  PDF/A-1b, PDF/A-2b and PDF/A-3b documents. See {ref}`pdfa`.
  - {func}`pikepdf.pdfa.prepare` repairs a document in memory: it installs a
    PDF/A output intent (sRGB by default, or a supplied ICC profile), removes
    image interpolation and hidden annotations, adds `/CIDSet` for PDF/A-1,
    and rewrites the XMP metadata with a PDF/A declaration. It returns a
    {class}`~pikepdf.pdfa.PrepareResult` describing what changed:
    {meth}`~pikepdf.pdfa.PrepareResult.describe` gives a sentence per change,
    {meth}`~pikepdf.pdfa.PrepareResult.messages` the same sentences with a
    suggested log level, and `PrepareResult.xmp_problem` why an XMP packet
    could not be read. XMP properties are kept from every `rdf:Description`
    when all share one `rdf:about`, including the non-empty `uuid:...` value
    that Acrobat and pikepdf's own metadata editor write.
  - {func}`pikepdf.pdfa.check` predicts the verdict for the file that would be
    written, without writing it, by modelling what qpdf's writer changes
    (stream filters, trailer, version, encryption and which objects are
    written).
  - {func}`pikepdf.pdfa.save` prepares the document, writes it to a temporary
    file, reopens and validates the bytes written, and only then moves the file
    into place. If the written file does not pass, it raises
    {class}`~pikepdf.pdfa.PdfaError` with the report (whose `prepared` records
    the repairs made), and the destination is left untouched.
  - {func}`pikepdf.pdfa.resolve_save_kwargs` returns the complete
    {meth}`pikepdf.Pdf.save` settings for a flavour. Settings that PDF/A
    forbids or that would change the bytes after the check (such as
    `encryption`, `normalize_content` and `fix_metadata_version`) are pinned,
    and a conflicting value raises `ValueError`.
- The validator is an allowlist: it approves only constructs it recognizes and
  knows to conform. A {class}`~pikepdf.pdfa.Report` has a verdict of `'pass'`,
  `'fail'` (at least one violation) or `'not_checked'` (only constructs the
  validator does not check, which may be valid PDF/A). `save` accepts only
  `'pass'`. Each {class}`~pikepdf.pdfa.Finding` names a rule: a veraPDF rule id
  such as `ISO_19005_2:6.2.8-3`, or a `pikepdf:` id for local policies and
  unsupported constructs. The validator is not a certification; veraPDF
  remains the reference.
- On the 2,906 files of the veraPDF test corpus, converted with `prepare` and
  checked against veraPDF 1.30 for each of PDF/A-1b, 2b and 3b, the validator
  approved no file that veraPDF rejects. For PDF/A-2b it approved 1,363 files,
  rejected 887 and left 656 not checked; veraPDF accepted 28 of the rejected
  files (the validator is conservative by design). The prediction from `check`
  matched the validation of the written file in every case.
- The validator and the repairs give the same results whether a document is
  read in implicit or explicit conversion mode: they work in explicit mode
  internally, whatever mode the caller uses.
- {mod}`pikepdf.pdfa` needs jsonschema, referencing and fontTools, available as
  the optional extra `pip install 'pikepdf[pdfa]'`. `import pikepdf` does not
  import them; `import pikepdf.pdfa` raises `ImportError` naming the extra if
  they are missing.
- The PDF/A rule catalogue is derived from the veraPDF validation
  profiles (CC BY 4.0, veraPDF Consortium); see
  `third-party-licenses/README.md`.
- Added the internal helper `pikepdf._io.atomic_write_verified`, which writes a
  file to a temporary location, calls a verification function on it, and moves
  it into place only if verification succeeds. `pikepdf.pdfa.save` uses it.

### qpdf limits

- Added {func}`pikepdf.settings.get_qpdf_limits` and
  {func}`pikepdf.settings.set_qpdf_limits`, which read and change qpdf's
  process-wide limits for hardening against malicious or damaged PDFs: parser
  nesting depth, error count and container size, the number of stream filters,
  memory for decoding Flate, DCT, PNG, TIFF and RunLength data, and whether
  corrupt JPEG data is an error. Previously only some of these limits were
  reachable, through {meth}`pikepdf.JobBuilder.limits`. `set_qpdf_limits`
  returns the previous values so they can be restored.
- Added {func}`pikepdf.settings.disable_qpdf_default_limits`, which lifts
  qpdf's optional default limits for the rest of the process, and
  {func}`pikepdf.settings.qpdf_limit_errors`, which counts how many times any
  limit has been exceeded.

### Behavior changes

- The minimum required qpdf version is now 12.4.1, and wheels bundle qpdf
  12.4.1. Changes inherited from qpdf 12.4:
  - Content stream parsing ({func}`pikepdf.parse_content_stream`,
    {meth}`pikepdf.Page.parse_contents`) now stops after 15 syntax errors,
    issuing a warning. Instructions after that point are dropped, so parsing
    and rewriting a badly damaged content stream can lose content. Use
    `pikepdf.settings.set_qpdf_limits(parser_max_errors=0)` to parse as much
    as possible.
  - A page tree nested more than 100 levels deep now raises
    {class}`pikepdf.PdfError` when the pages are accessed.
  - `/Rotate` values outside `[0, 360)`, such as `-90`, are now normalized
    when a page is converted to a form XObject, overlaid or rotation-flattened.
  - JSON output of real numbers no longer includes leading zeroes.
- The `progname` keyword argument of {class}`pikepdf.Job` is deprecated and
  ignored, and passing it issues a `DeprecationWarning`. It was passed to
  qpdf's `QPDFJob::initializeFromArgv` as the name of an environment variable,
  not the program name, and qpdf 12.4 no longer uses it. The program name is
  taken from the first item of `args`, as it always was.
- **Behavior change:** Reading `PdfImage` metadata such as `.width`,
  `.height` or `.colorspace` whose value has the wrong PDF type (for example
  a `/Width` written as a string or name) now raises `TypeError`, with a
  message naming the key and the value found, instead of
  `NotImplementedError: Metadata access for Width`. The exception is also a
  `NotImplementedError`, so existing handlers keep working.
- **Behavior change:** A `pikepdf.String` no longer compares equal to
  `bytes`: `pikepdf.String('abc') == b'abc'` is now `False`. It still
  compares equal to the `str` it decodes to. A `String` compared equal to
  both a `str` and a `bytes` that are unequal to each other, and so could not
  hash like both of them; for non-ASCII text it hashed like neither, so
  `{pikepdf.String('héllo'): 1}['héllo']` raised `KeyError`. A `String` now
  hashes like its `str`. To compare the raw data, use `bytes(s) == b'...'` or
  {meth}`~pikepdf.Object.as_bytes`.
- **Behavior change:** A `pikepdf.Name` no longer compares equal to `bytes`:
  `pikepdf.Name('/Foo') == b'/Foo'` is now `False`. It still compares equal to
  the `str` of its UTF-8 bytes, so `obj.Type == '/Page'` works as before, and
  it now hashes like that `str`, so `{pikepdf.Name('/héllo'): 1}['/héllo']`
  no longer raises `KeyError`. As PDF 2.0 (ISO 32000-2, 7.3.5) requires,
  names are compared byte for byte, with no Unicode normalization. Use
  `bytes(name) == b'...'` to compare the raw bytes.
- **Behavior change:** A `pikepdf.Real` combined with an `int`, `Integer`, or
  `Decimal`, or negated with unary `-`/`+`/`abs()`, now yields a `Decimal`
  (previously a `float`, or `TypeError` for `int` operands other than `/`).
  A `Real` with a `float` operand still yields a `float`. Division of an
  `Integer` or `Real` by zero now raises `ZeroDivisionError`, as for Python
  numbers, instead of `ValueError`.
- **Behavior change:** {class}`pikepdf.Matrix` now raises `TypeError`, not
  `ValueError`, for a `pikepdf.Object` that is not a matrix or an
  `ObjectList` with a non-numeric element, matching what a native argument of
  the wrong type raises, so an `except TypeError` around `Matrix(*operands)`
  behaves the same in either conversion mode. Size errors (`must have 6
  elements`) remain `ValueError`.
- **Behavior change:** `bool()` on a `pikepdf.Integer` or `pikepdf.Real` is
  now by value (`bool(pikepdf.Integer(0))` is `False`), instead of raising
  `NotImplementedError: code is unreachable`.
- **Behavior change:** {meth}`~pikepdf.Object.as_dict` and
  {meth}`~pikepdf.Object.as_list` now accept a `default` argument and raise
  `TypeError` on a type mismatch, instead of raising `pikepdf.PdfError` with
  no way to supply a default.
- **Behavior change:** Objects created in Python are now adopted by the `Pdf`
  they are inserted into, so `pdf.Root.X = 42` followed by
  `pdf.Root.get_raw('/X').is_owned_by(pdf)` is `True`, matching objects parsed
  from a file. Scalars (`Name`, `String`, `Integer`, ...) are adopted as a copy,
  so a handle held in a constant can still be inserted into several documents.
  A `Dictionary` or `Array` is adopted in place and keeps its aliasing with the
  document; inserting the same container into a second `Pdf` now raises
  `ForeignObjectError`, as it already did for containers parsed from a file.
  Construct a new object, or use {meth}`pikepdf.Pdf.copy_foreign`. Adoption is
  not permanent: removing the object from the document -- deleting or
  replacing the dictionary key or array element that held it, or destroying
  the document -- releases the claim, so the object can then be inserted into
  another `Pdf`. If the same object was reachable under two keys, removing
  either one releases it; its value is unaffected and still readable through
  the remaining reference.
- **Behavior change:** Objects copied into a document are adopted by it too.
  The direct children of an object produced by
  {meth}`pikepdf.Pdf.copy_foreign`, {meth}`pikepdf.Object.with_same_owner_as`,
  {meth}`pikepdf.Pdf.make_indirect`, or by appending/inserting a page into
  `Pdf.pages`, and values inserted through `NameTree`/`NumberTree`
  `__setitem__`, now report the destination document as their owner and follow
  its `conversion_mode`.
- **Behavior change:** {meth}`pikepdf.Object.with_same_owner_as` and
  {meth}`pikepdf.Pdf.make_indirect` now raise `ForeignObjectError` for a
  direct object that already belongs to a different `Pdf`, instead of silently
  retagging an object that the other document still references. Remove it from
  that document first, or make it indirect there and use
  {meth}`pikepdf.Pdf.copy_foreign`.
- **Behavior change:** {meth}`~pikepdf.Object.as_int` with `coerce=True` now
  returns a supplied *default* for a value that does not fit in a 64-bit PDF
  integer, instead of raising `OverflowError`. Called without a default it
  still raises `OverflowError`.
- **Behavior change:** `repr()` of a `pikepdf.Object` now honors the
  effective conversion mode of the object being displayed (context manager,
  then its owning `Pdf`, then the global setting), rather than only the
  global setting.
- **Behavior change:** {func}`pikepdf.set_object_conversion_mode` now raises
  `ValueError` for a value other than `'implicit'` or `'explicit'`, instead
  of silently accepting it.

### Performance

- {meth}`pikepdf.Pdf.save` writes directly to the file descriptor when saving
  to a filename or to a plain binary file object from {func}`open`, instead of
  calling the stream's `write()` method for every chunk of output. Saving a large document is about twice as fast. Other streams, such
  as `BytesIO`, pipes and subclasses of the `io` classes, are written through
  `write()` as before.
- `isinstance()` checks against {class}`pikepdf.Dictionary`,
  {class}`pikepdf.Stream`, {class}`pikepdf.Integer` and the other object
  classes are about five times faster.
- {func}`pikepdf.unbox` is now implemented in C++ and is about 40 times
  faster. {func}`pikepdf.as_int`, {func}`pikepdf.as_float` and
  {func}`pikepdf.as_decimal` answer for a native `int` or `Decimal` without
  constructing a PDF object, and converting a Real to a `Decimal` no longer
  imports the `decimal` module each time.

### Fixes

- {func}`pikepdf.models.metadata.decode_pdf_date` now accepts every PDF date
  form the specification allows, in which all fields after the year are
  optional. `D:202001011230` (no seconds) was misread as 12:03, and
  `D:2020010112` (no minutes) and offsets in hours only, such as `-08'`,
  raised `ValueError`, so {attr}`pikepdf.Pdf.docinfo` dates in these forms
  were not synchronized to XMP.
- {meth}`pikepdf.Pdf.save` now raises `ValueError` when the destination is
  the stream the `Pdf` was opened from, or another stream on the same file,
  as it already did for the input file's path. Previously the new file was
  written into the input at the stream's current position, which produced a
  corrupt file and could corrupt the open `Pdf`, since qpdf reads its input
  lazily. Open with `allow_overwriting_input=True` to save over the input.
- Setting an XMP property that a file stored as plain text, such as
  `<dc:title>Old</dc:title>`, now replaces the text. Previously the new value
  was added alongside the old text, which was still returned when the
  property was read back.
- {attr}`pikepdf.PdfImage.filter_decodeparms` and
  {attr}`pikepdf.PdfImage.decode_parms` no longer raise `AttributeError` when
  an image's `/DecodeParms` is a bare number or boolean, in either conversion
  mode. A `/DecodeParms` that is neither a dictionary nor an array is now
  ignored, as qpdf does, so each filter gets empty parameters.
- An image `/Decode` that is not an array (a bare number, boolean, name or
  dictionary) is now ignored in favour of the default decode array, instead of
  raising `AttributeError` when the image is read or converted.
- {meth}`pikepdf.Stream.write` now raises `TypeError` rather than
  `AttributeError` when `filter` or `decode_parms` is a native Python value
  such as an `int`, `str` or `dict`.
- {meth}`pikepdf.Pdf.save` to an existing file that is not a regular file,
  such as `/dev/null`, a FIFO or a character device, now writes into it
  directly. Previously the temporary file was renamed over it, so when run
  as root, saving to `/dev/null` replaced the device with a regular file.
- {meth}`pikepdf.Matrix.inverse` now returns the correct translation for
  matrices whose determinant is not 1. Previously the `e` and `f` terms were
  not divided by the determinant, so the result was not a true inverse for
  any matrix that both scaled and translated. Matrices built only from
  rotations and translations were unaffected. (#743)
- Storing a Python `int` outside the signed 64-bit range of a PDF integer, by
  assignment or in `Array(...)`, `Dictionary(...)` or `Integer(...)`, now
  raises `OverflowError` instead of `RuntimeError: std::bad_cast` (or a
  misleading `TypeError`).
- `PdfImage`, `PdfImageBase`, `PdfJpxImage`, `PdfInlineImage` and
  `PaletteData` once again report their module as `pikepdf.models.image`, the
  path they are imported from, instead of the private
  `pikepdf.models.image._classes` introduced when that module became a package
  in v10.10.
- The examples for {func}`pikepdf.set_object_conversion_mode` and
  {func}`pikepdf.explicit_conversion` no longer open a nonexistent `test.pdf`,
  and the global-mode example restores the default mode when it is done.
- `str()` of a `pikepdf.Integer`, `Boolean` or `Real` now gives the value --
  `'42'`, `'True'`, `'1.50'` -- rather than the object's repr. In implicit
  mode a scalar arrives as `int`/`bool`/`Decimal` and `str()` never reached
  the object, so every f-string, log line and string concatenation in code
  reading PDF values silently changed meaning under explicit mode.
- `hash()` now works on `pikepdf.Integer`, `Boolean` and `Real` (and on a
  null object), instead of raising `RuntimeError: don't know how to hash
  this`. A scalar hashes like the Python value it compares equal to, so
  `Real('1.0')`, `Decimal('1.0')` and `1` agree, and a scalar can be a dict
  key or a set member as it could in implicit mode.
- {meth}`pikepdf.Pdf.make_indirect` and {meth}`pikepdf.Object.with_same_owner_as`
  no longer turn a direct scalar (`Name`, `String`, `Integer`, `Real`,
  `Boolean`, `Operator`) that you pass them into an indirect object in place.
  They make a copy indirect and return it, leaving your object direct and
  unowned. Previously the object you held silently became an indirect object
  of that `Pdf`, after which `hash()` raised and any dict or set already
  holding it broke; a module-level constant such as `FOO = Name.Foo` became
  tied to one document. Arrays, dictionaries and streams are still made
  indirect in place, so the object you passed stays aliased with the one in
  the document.
- In explicit mode, `repr()` of an `Array`, `Dictionary` or `Stream` now
  names the scalars nested inside it -- `pikepdf.Real('42.42')` rather than
  `'42.42'`, which was indistinguishable from a PDF string and did not
  survive `eval(repr(obj))`.
- pikepdf's own higher-level APIs now work under explicit conversion mode.
  They read values out of PDF objects and compute with them, and every such
  read assumed the implicit-mode native type. Fixed in image extraction
  (`PdfImage` raised `NotImplementedError` for any indexed or /DeviceN
  colorspace), page labels (`page.label` restarted numbering at 1 and warned
  about a valid `/St`), outlines (a closed item read as open, `flags` came
  back empty, and saving wrote the wrong `/Count`), form appearance
  generation (`TypeError` laying out multiline and combed text fields),
  `get_objects_with_ctm` (a malformed `cm` operator raised instead of being
  skipped), `SimpleFont` metrics, and `Action.new_window`.
- `PdfImage` metadata now reads the same in either conversion mode. A real
  `/Width` or `/Height` or a boolean `/BitsPerComponent` raised
  `NotImplementedError` in explicit mode, and a real inside a `/ColorSpace`
  array raised it in implicit mode.
- `SimpleFont.ascent`, `descent` and `unscaled_char_width()` now return a
  `Decimal` as documented, in either conversion mode. Font metrics stored as
  PDF integers were previously returned as `int`.
- Iterating a `NamePath` (e.g. `list(path)`) no longer falls back to the
  legacy `__getitem__(0), (1), ...` protocol, which never raised
  `IndexError` and so iterated forever, exhausting memory.
- `NamePath in obj` no longer silently answers `False` for every path; see
  above.
- Fixed a stale `NamePath['/A']['/B'].C[0]` example in the type stub, which
  did not type-check against the runtime behavior (an instance's
  `__getitem__` only accepts an `int`). The correct form, matching the
  compiled docstring and the {doc}`/topics/namepath` documentation, is
  `NamePath['/A']('/B').C[0]`.
- A direct object parsed from a file, then removed from the document (for
  example `box = page.obj.get_raw('/MediaBox')` followed by
  `del page.obj['/MediaBox']`) and used after the document was closed, kept a
  dangling reference to the freed document and could crash the interpreter on
  `repr()` or when inserted elsewhere. Removing an object from a document now
  releases its association with it, and a `Pdf` disconnects every object it
  adopted as it is destroyed.

### Internals

- Linux wheel jobs now cache the compiled qpdf per build image with
  `actions/cache`, so jobs that share an image no longer each download and
  compile the same qpdf. See the build process notes.
- Fixed the Linux wheel build script's AlmaLinux detection, which was a
  malformed shell test that always evaluated false.
- The type stubs for the C++ extension module, until now a single 4,300-line
  `src/pikepdf/_core.pyi`, are a stub-only package `src/pikepdf/_core/` split
  into one stub per translation unit: `_core/_matrix.pyi` covers
  `src/core/matrix.cpp`, `_core/_page.pyi` covers `src/core/page.cpp`, and so
  on, with `_core/__init__.pyi` re-exporting the lot and documenting the
  layout. Nothing changes at runtime or in the public API -- `pikepdf._core`
  is still one extension module and `import pikepdf._core` still resolves to
  it. Type checkers now name a type by its defining stub in messages
  (`pikepdf._core._object.Object` rather than `pikepdf._core.Object`);
  `pikepdf.Object` remains the name to write in annotations.
- API documentation for the C++ extension now lives only in the
  `src/pikepdf/_core/` stubs, which are what Sphinx, type checkers and IDEs
  read. Docstrings attached to the C++ bindings were never published and had
  drifted from the stubs; they are merged into the stubs and removed from C++,
  and a test now fails if a binding gains a docstring or a public C++ name is
  missing from the stubs. `help()` on a C++ method now shows only its
  signature, as it already did for most of them.

### Documentation

- Restored the full {class}`pikepdf.Stream` and {class}`pikepdf.Dictionary`
  documentation, including constructor arguments and examples, which had been
  reduced to a single line when those classes moved to C++.

- Clarified guidance on `Pdf.save(..., deterministic_id=)` and `static_id=`, and
  the matching `JobBuilder.deterministic_id()` and `JobBuilder.static_id()`.
  `deterministic_id` gives reproducible, production-safe `/ID` values;
  `static_id` sets the same dummy `/ID` in every file and is for testing only.
  `static_id` now appears last in the `Pdf.save()` signature. All `save()`
  options are keyword-only, so existing code is unaffected.

## v10.13.0

### Exception hierarchy

pikepdf's exceptions now form a documented hierarchy rooted at the new
`pikepdf.PikepdfError`, with `pikepdf.PikepdfWarning` playing the same role for
warnings. See {doc}`/api/exceptions` for the full tree. {issue}`739`

- **Behavior change:** `pikepdf.DataDecodingError` now derives from
  `pikepdf.PdfError`. A stream that will not decode is a defect in the document,
  and the same call that raises it -- `Object.read_bytes()` -- already raised
  `PdfError` for other kinds of damage, so `except PdfError` was a handler that
  looked correct, passed on healthy files, and let a traceback escape on damaged
  ones. Code catching `DataDecodingError` by name is unaffected. Code that
  orders `except PdfError` *before* `except DataDecodingError` will now take the
  first branch; if the distinction matters, reverse the order.
- **Behavior change:** `pikepdf.PdfParsingError` now derives from
  `pikepdf.PdfError`, for the same reason.
- `pikepdf.PasswordError` remains a sibling of `PdfError`, not a subclass. A
  wrong password does not mean the document is defective, and handlers that
  report the two separately depend on `except PdfError` not catching it.
- `pikepdf.NotExtractableError` is now exported. It was already the base class
  of the exported `HifiPrintImageNotTranscodableError` but could not be caught
  by name.
- Fixed the exceptions documentation, which referenced a nonexistent
  `FormCopyWarning` (the class is `PageCopyWarning`) and omitted
  `ReferenceCycleError`, `PageCopyWarning` and `NotExtractableError`.

### XMP metadata value types

XMP assigns a type to every standard property, and software that reads XMP
discards a property whose type is wrong -- Ghostscript strips these silently,
and PDF/A validators reject them. pikepdf used to choose the RDF container from
the Python type of the value it was handed, so `meta['dc:subject'] = ['a', 'b']`
wrote an `rdf:Seq` where the specification requires an `rdf:Bag`, and
`meta['dc:creator'] = 'Author'` wrote a bare string where an `rdf:Seq` belongs.
pikepdf now knows the type of the properties in the standard schemas and
converts values to it. See {ref}`metadatatypes`. {issue}`555`

- **Behavior change:** a property that holds several values is now written in
  the container the specification requires, regardless of whether a `list` or a
  `set` was assigned. Reading such a property back returns a `list` for an
  ordered property and a `set` for an unordered one, as before.
- **Behavior change:** assigning a plain string to a property that holds several
  values now issues the new `pikepdf.XmpTypeWarning` and stores a single element
  array, rather than writing a bare string that other software discards. The
  `log.error` message pikepdf previously produced for `dc:creator` alone is
  replaced by this warning, which covers every such property.
- **Behavior change:** assigning a `list` or `set` to a property that holds one
  value now joins the values with `; ` (warning that it did so) instead of
  writing an array the specification does not allow there.
- `datetime.datetime` and `datetime.date` may now be assigned to date valued
  properties such as `xmp:CreateDate`, and are encoded as ISO 8601. A time zone
  offset that is not a whole number of minutes is rounded to the minute, since
  XMP allows only `+hh:mm` and `-hh:mm`. `int` may be assigned to `pdfaid:part`
  and `bool` to `xmpRights:Marked`.
- Assigning a `set` to an ordered property such as `dc:creator` now sorts the
  values, so the order written is the same on every run rather than whatever
  order the set happened to iterate in.
- `Pdf.open_metadata(strict=True)` now also raises `TypeError` for a value that
  does not match the type of the property, in addition to its existing effect of
  refusing to repair invalid XMP.
- Assigning to a property whose namespace prefix has not been registered now
  raises `KeyError` naming
  `pikepdf.models.metadata.XmpDocument.register_xml_namespace()`. Previously the
  prefix was silently dropped and the property written into no namespace.
- Properties pikepdf does not know about are unaffected: the container is still
  inferred from the Python type of the value.
- The new `pikepdf.models.metadata.XMP_SCHEMA` maps a property's qualified name
  to its `XmpProperty` type, for callers that want to check types themselves.

### Fixes

- Iterating XMP metadata no longer produces keys that cannot be looked up. When
  XMP fails to parse and is recovered, an element whose namespace prefix was
  never declared survives under its literal name -- `xmp:MetadataDate` rather
  than `{http://ns.adobe.com/xap/1.0/}MetadataDate` -- so iteration yielded a
  name that `__getitem__` resolved to a different one, and any code that walked
  the metadata (such as `dict(meta.items())`) raised `KeyError` or, where the
  name was truncated, `ValueError: Invalid tag name`. pikepdf now rebinds such
  names to the namespace their prefix refers to when it parses XMP, and discards
  the ones it cannot resolve. This also means the XMP pikepdf writes back for
  these documents is well-formed XML; previously it contained undeclared
  prefixes that no strict parser would read. {issue}`634`
- `key in metadata` now reports whether the key is present rather than whether
  its value is truthy, so a property with an empty value is no longer reported
  as missing by `in` while `metadata[key]` returns its value. Iterating the
  metadata likewise now includes a property with an empty value that is stored
  as an attribute of `rdf:Description`.
- XMP dates are now parsed according to the XMP specification when updating
  DocumentInfo, rather than with `datetime.fromisoformat`. On Python 3.10 the
  latter rejects a fraction of a second that is not exactly 3 or 6 digits and
  a time zone offset without a colon, so a valid date such as
  `2024-06-01T12:00:00.5Z` in XMP caused `/ModDate` to be dropped from
  DocumentInfo with a warning. A date-only XMP value such as `2024-06-01` now
  becomes `D:20240601` in DocumentInfo, and back, without a spurious midnight
  time, matching the existing handling of year and year-month values.
- Looking up a malformed key such as `'xmp:'` now raises `KeyError` (and
  `metadata.get()` returns the default) instead of `ValueError` from lxml.
- `pikepdf._core._ObjectList`, the list of operands attached to a content stream
  instruction, now behaves like a list of pikepdf objects. Previously its
  methods only accepted `pikepdf.Object`, but the elements of an operand list
  are usually numbers, which pikepdf decodes to `int`/`bool`/`Decimal` on the
  way out -- so there was no value a caller could pass back in. `==`, `!=`,
  `in`, `count()`, `remove()`, `append()`, `insert()`, `extend()` and
  `__setitem__` now encode their argument the same way `pikepdf.Array` does, so
  `instruction.operands == [0]` is True and `0 in instruction.operands` works.
  {issue}`742`
- Comparisons on `_ObjectList` now compare objects by value, as the rest of
  pikepdf does. They previously used qpdf's C++ `operator==`, which reports only
  whether two handles refer to the same underlying object, so `==`, `in`,
  `count()` and `remove()` gave wrong answers even for operand lists made
  entirely of `pikepdf.Object`.
- `pikepdf._core._ObjectMapping`, returned by `Object.as_dict()` and
  `Page.get_images()`, had all of the same problems and received the same
  treatment: `__setitem__` and `update()` now encode their value, `==` and `!=`
  compare values rather than object identity, and comparing a mapping to a
  `dict` works instead of printing a nanobind conversion warning.
- `_ObjectMapping.__setitem__` and `__delitem__` now accept a `pikepdf.Name`
  key, which `__getitem__` and `__contains__` already did.
- Comparing an `_ObjectList` or `_ObjectMapping` to a list or dict no longer
  prints `nanobind: implicit conversion from type 'list' to type
  'pikepdf._core._ObjectList' failed!` to stderr.
- A warning raised from pikepdf's C++ layer -- `PageCopyWarning`, the
  `Page.rotate()` deprecation warning, and the several warnings issued while
  opening and saving -- is now raised as an exception when a warning filter asks
  for that, such as under `python -W error` or
  `warnings.simplefilter('error')`. Previously the exception was created and
  then discarded, and the call carried on as if the warning had been ignored.
- Decoding a JBIG2 image no longer holds a second copy of the compressed data
  after the decoder has run, and no longer copies the whole image out of an
  internal buffer to hand it to the decoder.
- `repr()` of a deeply nested object and `unparse_content_stream()` no longer
  copy their partial result repeatedly while building it. Output is unchanged.
- `pikepdf.StreamParser` is now exported from the top-level package and included
  in `__all__`. It was always the required argument type of the public
  `Page.parse_contents()`, but previously could only be imported from the
  private `pikepdf._core` module. {issue}`738`

### Internals

- Moved `Page`'s box properties (`mediabox`, `cropbox`, `artbox`, `bleedbox`,
  `trimbox`), the `rotation` property and `rotate()`, and `_ObjectMapping`'s
  key-based methods (`get`, `__getitem__`, `__setitem__`, `__delitem__`,
  `__contains__`) from Python augmentations to C++. Each was a thin Python
  wrapper around a private C++ binding, so its implementation was split across
  two files for no benefit; they are now defined once, in C++, and the private
  `Page._get_mediabox()`, `_get_artbox()`, `_get_bleedbox()`, `_get_cropbox()`,
  `_get_trimbox()` and `_get_rotation()` bindings they delegated to are gone.
  Behavior is unchanged.
- Removed the `augment_override_cpp` decorator from `pikepdf._augments`. A
  Python augmentation may no longer replace a method that C++ already defines;
  where C++ behavior needs to change, change it in C++, so that each method has
  exactly one implementation. With it goes the `_cpp<name>` copy the decorator
  left behind on the augmented class, so private attributes such as
  `Page._cpp__repr__` no longer exist.
- Moved a second group of Python augmentations to C++ for the same reason: the
  `Attachments` mapping methods, `AttachedFileSpec.relationship` and its
  `__repr__`, `AttachedFile.read_bytes()`, `Page.form_xobjects`, `Rectangle`'s
  `__repr__`, `__hash__` and `to_bbox()`, `Token.__repr__`, and `Object`'s
  `as_int()`, `as_bool()`, `as_float()`, `as_decimal()` and
  `_ipython_key_completions_()`. `len(pdf.attachments)` and iterating it no
  longer build a `pikepdf.AttachedFileSpec` for every attached file merely to
  count or name them. The private bindings these delegated to --
  `Attachments._get_all_filespecs()`, `_get_filespec()`, `_attach_data()`,
  `_add_replace_filespec()`, `_remove_filespec()`, `Page._form_xobjects` and
  `Object._get_real_value()` -- are gone.
- A support class passed to `augments` may now subclass an abstract base class
  to pick up its mixin methods and leave the abstract methods to C++;
  previously the abstract stubs were installed over the C++ implementations.
  `pikepdf.Attachments` uses this to get the `MutableMapping` mixins.

## v10.12.0

### Packaging and licensing

- Binary wheels now redistribute the licenses of the compiled third-party
  libraries they bundle, along with an attribution manifest mapping each
  component to its license. {issue}`736`
  - The new `third-party-licenses/` directory documents every vendored binary:
    qpdf and libjpeg-turbo on all platforms; OpenSSL and zlib (both statically
    linked into `qpdf30.dll`) plus the Microsoft Visual C++ runtime on Windows;
    the GnuTLS/Nettle/GMP stack on macOS; and the GCC runtime libraries on
    musllinux. It also records what is deliberately *not* bundled -- notably
    that Linux wheels use qpdf's built-in crypto and link no TLS library at all.
  - These files are declared via PEP 639 `project.license-files`, so they ship
    in `pikepdf-<version>.dist-info/licenses/` and are enumerated in the wheel's
    `License-File` metadata. `License-Expression` remains `MPL-2.0`: pikepdf's
    own license is unchanged, and the bundled libraries are separate works
    redistributed in unmodified binary form.
  - This replaces `licenses-for-wheels.txt`, which has been removed. It had
    grown stale -- it never mentioned OpenSSL, zlib, or the GnuTLS stack -- and
    it was reaching only Windows wheels, because the build backend's default
    license glob happened to match its name on case-insensitive filesystems and
    nowhere else.
- Source distributions no longer contain qpdf's source tree. CI unpacks qpdf
  into `./qpdf` to build libqpdf, and that build prerequisite was being swept
  into the sdist, adding roughly 2900 Apache-2.0 files to a distribution that
  declares itself MPL-2.0. sdists now contain only pikepdf's own code.
- macOS wheels now use qpdf's native crypto provider instead of GnuTLS, and all
  POSIX builds select it explicitly rather than relying on qpdf's implicit
  fallback. macOS wheels lose nine bundled libraries as a result -- GnuTLS,
  Nettle, Hogweed, GMP, libidn2, libunistring, Libtasn1, p11-kit and libintl --
  along with roughly 10 MB and the LGPL obligations that came with them.

  macOS moved to GnuTLS in v8.11.1 to fix legacy encrypted files failing to open
  ({issue}`520`), because Homebrew's OpenSSL had retired the legacy provider
  that supplies the RC4 and MD5 those files need. The Linux builds were assumed
  to be doing the same thing, but were not: their images carry no GnuTLS or
  OpenSSL headers, so they had silently been using native crypto all along, and
  have been opening legacy encrypted files without trouble ever since. Native
  crypto implements MD5, RC4, SHA2 and AES itself, so no upstream deprecation
  policy can withdraw the weak algorithms older PDFs require. There is no
  change to which files pikepdf can open.
- Documentation updates with additional guidance about crypto provider selection
  and the realities of PDF encryption security.

### Behaviour change

- {attr}`pikepdf.PdfInlineImage.icc` now returns ``None`` instead of raising
  {exc}`~pikepdf.exceptions.InvalidPdfImageError`. An inline image's colour
  space cannot be `/ICCBased`, so "no ICC profile" is the correct answer rather
  than an error -- and raising made {attr}`pikepdf.PdfImageBase.palette`
  unusable on indexed inline images, which consults it.

### Fixes

- Fixed inline image name abbreviation handling, which was incomplete in both
  directions. {issue}`206`
  - Abbreviations nested inside arrays are now expanded when an inline image is
    parsed, so `/F [/AHx /Fl]` reports
    `['/ASCIIHexDecode', '/FlateDecode']` from
    {attr}`pikepdf.PdfInlineImage.filters`, and an `/Indexed` colour space
    written as `/CS [/I /RGB 1 <...>]` is now recognized as indexed instead of
    raising `NotImplementedError`. Correspondingly,
    {meth}`pikepdf.PdfInlineImage.unparse` now abbreviates names inside arrays,
    not only at the top level. Contents of `/DecodeParms` dictionaries are left
    alone, since filter parameter names have no abbreviations.
  - Abbreviations are now resolved by position, because the same abbreviation
    can mean different things as a key and as a value: `/I` is `/Interpolate`
    as a key but `/Indexed` as a value (ISO 32000-2 Tables 91 and 92).
    Previously `/I` was always expanded to `/Indexed`, so `/I true` became
    `/Indexed true`.
  - Added the missing abbreviations `/Fl` (`/FlateDecode`), `/D` (`/Decode`)
    and `/L` (`/Length`); none of the three were expanded before.
- A JBIG2 decode failure (jbig2dec present but the payload is invalid) now
  raises {exc}`~pikepdf.exceptions.DataDecodingError` with the decoder's error
  message, instead of a bare `RuntimeError("qpdf will consume this exception")`
  plus an unraisable traceback on stderr. (#735)
- Exceptions other than {exc}`~pikepdf.exceptions.DataDecodingError` raised by
  a JBIG2 decoder now keep their own type. Previously a failure unrelated to
  the image data -- a {exc}`~pikepdf.exceptions.DependencyError` from a custom
  decoder, or `KeyboardInterrupt` -- was reported as if the JBIG2 data were
  corrupt. Implementations of
  {class}`pikepdf.jbig2.JBIG2DecoderInterface.decode_jbig2` should raise
  `DataDecodingError` to report undecodable data.
- When the JBIG2 image comes from a file on disk, qpdf traps the decoder's
  exception and records it as a warning, so the decoder's message is available
  from {meth}`pikepdf.Pdf.get_warnings` rather than in the exception.

## v10.11.0

- Extended PDF outline (bookmark) support to cover more of the spec:
  - {class}`pikepdf.OutlineItem` now supports the outline item dictionary's
    `/C` (color), `/F` (italic/bold flags via {class}`pikepdf.OutlineItemFlag`
    and the `.italic`/`.bold` properties), and `/SE` (structure element
    backlink) entries.
  - Added {class}`pikepdf.Destination`, which parses an existing destination
    array into named accessors (page, fit type, viewport parameters),
    complementing the existing write-only {func}`pikepdf.make_page_destination`.
    {meth}`pikepdf.OutlineItem.resolved_destination` resolves any form of an
    item's destination -- explicit array, page number, or named destination
    (via the document's name tree or legacy `/Dests` dictionary) -- to a
    `Destination`.
  - Added a typed action model ({class}`pikepdf.Action` and subclasses
    `GoToAction`, `GoToRAction`, `GoToEAction`, `GoToDpAction`, `LaunchAction`,
    `URIAction`, `NamedAction`, `SetOCGStateAction`, `JavaScriptAction`) for
    reading and constructing `/A` action dictionaries.
    {attr}`pikepdf.OutlineItem.parsed_action` gives a typed view of an item's
    action.
  - Fixed the outline dictionary's root `/Count` being written as `0` instead
    of omitted when the outline has no entries at all, matching spec Table 150.
- Fixed 1-bit `/Indexed` images losing their palette when extracted.
  {meth}`pikepdf.PdfImage.as_pil_image` and {meth}`pikepdf.PdfImage.extract_to`
  previously ignored the palette of a 1-bit indexed image over a `/DeviceCMYK`
  base (returning a bilevel grayscale image) and of a single-colour palette
  (``hival`` 0). Both now decode correctly, matching the existing behavior for
  2/4/8-bit indexed images.
- A 1-bit `/Indexed` image whose base colour space is unsupported (for example
  `/DeviceN` or `/Separation`) now raises ``NotImplementedError`` instead of
  silently returning an unpalettized image, matching the 2/4/8-bit path.

### Security

-   Image extraction now verifies that the image stream holds enough data for
    the declared ``/Width`` and ``/Height`` before allocating, raising
    {exc}`~pikepdf.exceptions.ImageDecompressionError` when it does not. This
    closes the remaining decompression-bomb gaps left by the
    {attr}`pikepdf.PdfImage.MAX_IMAGE_PIXELS` limit added in v10.10.0: that
    limit bounds the declared size, but said nothing about whether the declared
    size was consistent with the data present, so a 4-bit image declaring
    12000x12000 -- well under the default 500M pixel budget -- still allocated
    ~300 MB from a single byte of stream data, and setting the limit to ``None``
    restored the unbounded case entirely. The 2-bit and 4-bit unpack path and
    the 1-bit path (where Pillow allocates a byte per pixel before it notices
    the data is short) are both covered; 8-bit and 16-bit images already
    failed cleanly. The check applies regardless of ``MAX_IMAGE_PIXELS``.
    (#733, #734)
-   The 2-bit and 4-bit unpack buffer is no longer over-allocated by a factor of
    the packing ratio, reducing peak memory for those images by 4x and 2x
    respectively.

### Behavior change

-   Extracting a 1-, 2-, or 4-bit image whose stream is shorter than its
    declared dimensions require now raises
    {exc}`~pikepdf.exceptions.ImageDecompressionError` rather than returning a
    partially decoded image with a black tail. This matches what 8-bit and
    16-bit images have always done for the same defect.
-   An image with a non-positive ``/Width`` or ``/Height`` raises
    {exc}`~pikepdf.exceptions.InvalidPdfImageError` instead of failing further
    down with an obscure error from Pillow.

## v10.10.0

### Behavior change

-   {meth}`pikepdf.PdfImage.as_pil_image` and {meth}`pikepdf.PdfImage.extract_to`
    now apply an image's soft mask (``/SMask``) or explicit/colour-key mask
    (``/Mask``) by default, returning an image with an alpha channel (``LA`` or
    ``RGBA``) and writing a transparency-capable format (``.png``). Previously
    the mask was silently ignored and the opaque base image was returned. Images
    without a mask are unaffected. Pass ``apply_mask=False`` to recover the old
    behavior and obtain only the opaque base image.

### New features

-   Image masking is now honored when extracting images. ``/SMask`` soft masks,
    ``/Mask`` stencil (explicit) masks, and ``/Mask`` colour-key masks are
    composited into an alpha channel. Soft-mask images whose resolution differs
    from the base image are resampled to match (ISO 32000-2 §8.9.6).
-   Added support for ``/CalRGB``, ``/CalGray`` and ``/CalCMYK`` images, which
    previously raised ``NotImplementedError``. The samples are decoded as their
    device equivalents, and for CalRGB/CalGray an ICC profile synthesized from
    the colour space's ``WhitePoint``/``Gamma``/``Matrix`` is attached to the
    extracted image so the calibration is preserved for colour-managed consumers.
-   Added support for the ``/Lab`` colour space, extracted as a Pillow ``LAB``
    image (saved as TIFF) with the PDF ``L*``/``a*``/``b*`` ranges remapped to
    Pillow's conventions.
-   Added support for 16-bit-per-component images. 16-bit grayscale is extracted
    losslessly as Pillow ``I;16``; 16-bit RGB and CMYK are reduced to 8-bit (with
    a warning) because Pillow has no higher-bit-depth raw mode for them.
-   Extended colour-space handling to several cases that previously raised
    ``NotImplementedError``: ICCBased CMYK images and indexed images now produce
    the correct default ``/Decode`` array, and inline images that name their
    colour space now resolve it from the in-scope ``/Resources`` when obtained via
    {func}`pikepdf.parse_content_stream`.
-   JPEG (``/DCTDecode``) images with a non-default ``/ColorTransform`` -- a YCCK
    CMYK or a non-YCbCr RGB JPEG -- are now decoded via Pillow (which honours the
    JPEG's own markers) and transcoded, instead of failing to extract.
-   Filter chains that wrap a single terminal image codec (``/DCTDecode``,
    ``/CCITTFaxDecode``, ``/JPXDecode``, ``/JBIG2Decode``) in any number of
    generalized/specialized filters (Flate, LZW, ASCII85/Hex, RunLength) are now
    peeled and extracted seamlessly.
-   Added {attr}`pikepdf.PdfImage.MAX_IMAGE_PIXELS`, a settable class-level limit
    on the number of pixels pikepdf will decode from a single image. Until set,
    it defaults to ``max(500_000_000, PIL.Image.MAX_IMAGE_PIXELS)`` -- a floor
    suited to high-DPI scanned PDFs -- and tracks Pillow's setting; once assigned
    it becomes independent of Pillow. Set it to ``None`` to disable the check.
-   {class}`pikepdf.Array` now implements the standard Python ``list`` interface:
    slicing (including ``del`` on slices and slice assignment), and the
    ``clear()``, ``count()``, ``index()``, ``insert()``, ``pop()``, ``remove()``,
    and ``reverse()`` methods.

### Limitations

-   ``/SMaskInData`` (alpha encoded inside a JPEG 2000 stream) is applied only
    when Pillow's JPEG 2000 decoder surfaces the alpha channel itself; a
    pre-multiplied (``SMaskInData 2``) result is not un-premultiplied. A
    ``/Matte`` entry on a soft mask is not undone (a warning is emitted). When an
    image has both an ``/SMask`` and a ``/Mask``, the soft mask takes precedence.
    Colour-key masking is applied only to 8-bit ``L``/``RGB``/``CMYK`` images.
-   A filter chain containing two or more terminal image codecs (for example
    ``[/DCTDecode /CCITTFaxDecode]``) cannot be decoded by any reader and now
    raises {class}`~pikepdf.exceptions.UnsupportedImageTypeError` rather than
    ``NotImplementedError``.

### Security

-   Hardened image extraction against decompression-bomb (memory exhaustion)
    attacks. A malicious PDF could declare an image with enormous ``/Width`` and
    ``/Height`` so that {meth}`pikepdf.PdfImage.as_pil_image` attempted to
    allocate many gigabytes before reading the (tiny) image stream. pikepdf now
    enforces a configurable pixel limit, {attr}`pikepdf.PdfImage.MAX_IMAGE_PIXELS`
    (analogous to ``PIL.Image.MAX_IMAGE_PIXELS``), across every image-decode
    path -- including the 2/4-bit transcoding path, 1-bit and 8-bit images, and
    the Pillow-decoded JPEG/JPEG2000/CCITT path. Oversized images raise
    {exc}`pikepdf.DecompressionBombError` and borderline images emit
    {exc}`pikepdf.DecompressionBombWarning` (both subclass Pillow's equivalents).
    (#733)

### Fixed

- A CCITT fax image preceded by a stripped simple filter (e.g.
  `[/FlateDecode /CCITTFaxDecode]`) now builds its TIFF header from the
  `/CCITTFaxDecode` filter's own `/DecodeParms` rather than the leading filter's,
  which previously produced a corrupt extraction.
- Corrected the documentation of {class}`pikepdf.StreamDecodeLevel`: the
  `specialized` and `all` levels were each described with the other's behavior.

## v10.9.1

- Fixed a crash (`SIGABRT` via `std::terminate`) that could occur when a
  file-backed {class}`pikepdf.Pdf` was deallocated while a Python exception was
  already propagating -- for example when `pikepdf.open(filename)` appears as a
  transient element of a list/tuple literal whose later element raises. Opening
  from a filename closes the file in the input source destructor, which calls
  back into Python; with an exception already in flight that call raised an error
  that escaped the destructor. The in-flight exception is now preserved and
  propagates normally. (#732) Added a guard for a likely non-reproducible related
  case with DecimalPrecision.

## v10.9.0

### New features

- Added {class}`pikepdf.JobBuilder`, a fluent, Pythonic builder for qpdf jobs.
  It assembles a job specification with chained, snake_case methods (``input``,
  ``output``, ``encrypt``, ``add_pages``, ``split_pages``, ``linearize``,
  ``compress``, ``add_attachment``, ``add_overlay``, ``limits``, ...) and runs it
  via the existing {class}`pikepdf.Job`, without hand-writing qpdf's camelCase
  job JSON. Encryption permissions are expressed with the familiar
  {class}`pikepdf.Permissions`/{class}`pikepdf.Encryption` models, and a
  ``.set(**kwargs)`` escape hatch reaches any other job option. Additional
  methods cover image optimization (``optimize_images``,
  ``externalize_inline_images``), page/content transforms
  (``flatten_annotations``, ``flatten_rotation``, ``generate_appearances``,
  ``coalesce_contents``, ``normalize_content``), content removal
  (``remove_metadata``, ``remove_info``, ``remove_acroform``,
  ``remove_structure``, ``remove_page_labels``), page labels
  (``set_page_labels``), version control (``min_version``, ``force_version``),
  and reproducible/inspection helpers (``deterministic_id``, ``static_id``,
  ``check``).
- Exposed several pieces of qpdf functionality that pikepdf had not previously
  bound:
  - Whole-document qpdf JSON: {meth}`pikepdf.Pdf.write_qpdf_json`,
    {meth}`pikepdf.Pdf.from_qpdf_json` and {meth}`pikepdf.Pdf.update_from_qpdf_json`
    serialize and reconstruct an entire PDF as qpdf JSON (the
    `qpdf --json-output`/`--json-input` format, version 2). This complements the
    existing object-level {meth}`pikepdf.Object.to_json`. Added
    {class}`pikepdf.JSONStreamData` to control how stream data is represented.
  - {meth}`pikepdf.Pdf.get_xref_table` returns the cross-reference table as
    structured data ({class}`pikepdf.XrefEntry`), complementing the print-only
    {meth}`pikepdf.Pdf.show_xref_table`.
  - {meth}`pikepdf.Pdf.fix_dangling_references` repairs references to objects
    that are not present in the file.
  - {meth}`pikepdf.Page.flatten_rotation` bakes a page's `/Rotate` value into its
    content stream.
  - {meth}`pikepdf.Page.copy_annotations` copies annotations (and associated form
    fields) from another page, applying a transformation matrix.
  - {meth}`pikepdf.Page.get_matrix_for_transformations` and
    {meth}`pikepdf.Page.get_matrix_for_form_xobject_placement` expose qpdf's
    page/form-XObject placement matrices.
  - {meth}`pikepdf.AcroForm.validate`,
    {meth}`pikepdf.AcroForm.invalidate_cache` and
    {meth}`pikepdf.AcroForm.transform_annotations` for working with interactive
    forms after manual structural edits.
- Added {meth}`pikepdf.Page.get_images`, which by default recurses into nested
  form XObjects to find images. The {attr}`pikepdf.Page.images` property is now
  **deprecated**: it only reports images referenced directly by the page and
  silently omits images drawn through form XObjects, which made it appear as if a
  page "has no images" when it clearly did. Use ``get_images()`` instead, or
  ``get_images(recursive=False)`` for the old behavior.
- Added {attr}`pikepdf.Page.rotation`, a property that reports a page's effective
  clockwise rotation normalized to ``[0, 360)``. Unlike the raw ``page.Rotate``
  attribute, it resolves a ``/Rotate`` value inherited from the page tree and
  reports ``0`` when no rotation is set, instead of raising. Assigning to it sets
  the absolute rotation. This addresses the long-standing confusion between the
  ``page.Rotate`` attribute and the ``page.rotate()`` method (#467).
- {meth}`pikepdf.Page.rotate` now defaults ``relative`` to ``False``, so
  ``page.rotate(90)`` sets an absolute rotation. Passing ``relative`` as a
  *positional* argument is deprecated and emits a ``DeprecationWarning``; pass it
  as a keyword argument instead, e.g. ``page.rotate(90, relative=True)``.
  Positional support will be removed in pikepdf 11.
- Added {meth}`pikepdf.Pdf.add_pages_from` to copy pages between documents while
  preserving interactive AcroForm form fields, returning a
  {class}`pikepdf.PageCopyResult`. Naive `pages.extend()` across documents and
  `save()` of documents with orphaned form widgets now emit
  {class}`pikepdf.PageCopyWarning`. (#670, #207)

  When copying pages, named destinations referenced by the copied pages'
  annotations (e.g. table-of-contents links) are now carried into the
  destination document — both the PDF 1.2 ``Names.Dests`` name tree and the
  legacy PDF 1.1 ``Root.Dests`` dictionary — so internal links keep working
  regardless of merge order. Name collisions are renamed and reported via
  {class}`pikepdf.PageCopyResult` (``named_dests_added``, ``renamed_dests``,
  ``dropped_dests``). Naive ``pages.extend()`` now also warns when copied pages
  reference named destinations. (#148)

### Fixes
- Fixed image extraction ignoring the ``/Decode`` array, which caused colors to
  be inverted (or otherwise mismapped) when a PDF specified a non-default
  ``/Decode`` such as ``[1, 0]``. {meth}`pikepdf.PdfImage.as_pil_image` and
  {meth}`pikepdf.PdfImage.extract_to` now apply ``/Decode`` as a linear
  per-channel mapping for grayscale, RGB and CMYK raster images, matching how a
  PDF viewer renders the image. Previously ``/Decode`` was honored only for
  CCITTFax-encoded images. Thanks to Mark-Joy for the report. {issue}`650`
  Both methods gained an ``apply_decode_array`` parameter (default ``True``).
  Pass ``apply_decode_array=False`` to retrieve the raw stored sample values
  with the least processing -- useful for forensic inspection of the underlying
  image data.
  Some image types are intentionally not affected: Indexed-colorspace images
  (where ``/Decode`` remaps palette indices rather than colors -- a non-identity
  ``/Decode`` there now emits a warning), and DCT (JPEG) / JPX (JPEG 2000)
  images, whose codecs carry their own color semantics (such as the Adobe APP14
  marker for inverted CMYK) that Pillow already honors; re-applying ``/Decode``
  would double-invert them.
- Fixed {meth}`pikepdf.Pdf.save` decompressing streams when called with
  `compress_streams=False` and no explicit `stream_decode_level`. qpdf 11.10
  changed its default stream decode level to `generalized`, which caused such
  saves to decompress (without recompressing) streams and balloon the output
  file. pikepdf now pins the decode level to `none` in this case, restoring the
  documented behavior that `compress_streams=False` alone does not trigger
  decompression. Fixes {issue}`676`.
- The minimum required qpdf version is now 12.3.2. The new
  {meth}`pikepdf.AcroForm.validate` binding calls qpdf's
  `QPDFAcroFormDocumentHelper::validate`, which was added in qpdf 12.3.0, so
  pikepdf no longer builds against older qpdf releases.

### Documentation
- Documented a long-standing page-deletion pitfall: deleting a page unlinks it
  from the page tree, but a page that is still referenced by an outline
  (bookmark), link annotation, or named destination remains in the saved file.
  The {ref}`Deleting pages <deleting_pages>` topic now explains the behavior and
  gives workarounds. Thanks to m-holger. Closes {issue}`196`.
- Documented how to copy metadata between documents, in a new
  {ref}`Copying metadata between documents <copymetadata>` topic, including why
  blindly copying all fields (or the raw XMP stream) can import false conformance
  claims and identifiers. Closes {issue}`188`.

## v10.8.0

- Added {class}`pikepdf.ReferenceCycleError` (a subclass of
  {class}`pikepdf.PdfError`), raised when an operation would create a cycle of
  direct (non-indirect) objects -- a direct object may not contain itself,
  directly or indirectly. Use {meth}`pikepdf.Pdf.make_indirect` to create a
  reference cycle instead. This requires a build of qpdf that prevents
  direct-object cycle construction; on older qpdf the offending operation is
  permitted as before.
- Added a new {mod}`pikepdf.sanitize` module with curated, low-risk helpers for
  removing active or auxiliary content from untrusted PDFs: `remove_javascript`,
  `remove_attachments`, `remove_external_access`, `remove_thumbnails`,
  `remove_search_index`, `remove_multimedia` (Rendition/Movie/Sound/RichMedia/3D
  content), `remove_web_capture` (`/SpiderInfo`), `remove_private_app_data`
  (page-piece dictionaries), and `remove_collection` (PDF portfolio view), plus a
  {class}`pikepdf.sanitize.Sanitizer` builder for chaining these operations. The
  action-based removals now also traverse the document outline (bookmarks) and
  treat embedded go-to (`/GoToE`) as external access, and `remove_attachments`
  now sweeps `/AF` associated-file references from every object (XObjects,
  structure elements, DParts, etc.). Also added a new {ref}`sanitize` topic
  discussing PDF sanitization, threat models, and the limits of programmatic
  redaction. Fixes {issue}`673`.
- pikepdf now publishes free-threaded CPython 3.14 (`cp314t`) binary wheels to
  PyPI for Linux (manylinux and musllinux, x86-64 and aarch64), macOS (x86-64
  and Apple Silicon) and Windows (x86-64). Previously these wheels were not
  published and free-threaded users had to build pikepdf from source.
- Updated the PyPI "Free Threading" trove classifier from "1 - Unstable" to
  "3 - Supported".
- Some of pikepdf's dependencies (such as lxml and Pillow) publish their own
  free-threaded wheels; on less common platforms or when older versions are
  involved, free-threading might require source builds of those dependencies.
- Reimplemented `Page`'s attribute, item and `get` accessors in C++ instead of
  Python. These delegate to the underlying page dictionary and were previously
  implemented as Python augmentations; moving them to C++ removes extra Python
  call frames on these hot paths. Behavior is unchanged.
- Object construction (`Name`, `Array`, `Dictionary`, the `Name.Attr`
  shorthand, the scalar types `Integer`/`Boolean`/`Real`, and `NamePath`)
  is now implemented in C++ for improved performance. Behavior is unchanged.

## v10.7.3

- Upgraded to cibuildwheel 3.4.1 and refreshed pinned GitHub Actions
  (`actions/checkout@v6`, `actions/upload-artifact@v7`,
  `actions/download-artifact@v8`, `codecov/codecov-action@v6`). Dropped the
  CPython 3.15 prerelease test job, since cibuildwheel has not yet shipped a
  stable release with 3.15 support.
- Fixed Windows wheels bundling a fixed-version, un-mangled copy of the Microsoft
  Visual C++ runtime (`msvcp140*.dll`, `vcruntime140*.dll`, `concrt140.dll`)
  inside the package directory. These were inadvertently copied from qpdf's
  prebuilt release alongside `qpdf30.dll`. Shipping them caused a second,
  conflicting copy of the C++ runtime to load in the same process, which could
  corrupt CPython's per-thread state and produce a fatal `PyInterpreterState_Get
  ... the GIL is released (the current Python thread state is NULL)` error or an
  `ImportError` for `pikepdf._core`, typically only in some launch environments
  (e.g. a terminal but not IDLE) or when another extension was imported first.
  The Windows wheel now copies only qpdf's own DLLs; `delvewheel` vendors a
  name-mangled copy of the C++ standard library runtime and uses CPython's own
  `vcruntime140`, so the wheel remains self-contained without an un-mangled
  runtime in the package directory that could collide with the system copy.
  Fixes :issue:`718`.
- Improved the error message raised when `pikepdf._core` fails to import: it now
  reports the active interpreter, version, free-threading status, and (on Windows)
  a hint about the Visual C++ Redistributable and interpreter mismatches.
- Fixed a segmentation fault when comparing two *direct* (non-indirect)
  `Dictionary` or `Array` objects that form a cyclic reference graph, for example
  `a['/Kids'] = [b]; b['/Kids'] = [a]; a == b`. The cycle detector previously keyed
  its bookkeeping on `unparseBinary()`, which itself recurses through the whole
  graph and overflowed the C stack for direct cyclic objects. Equality now detects
  cycles by object identity instead, so such comparisons terminate. Fixes
  :issue:`731`.
- Fixed an `AttributeError` when reading a document outline ("bookmarks") whose
  items are missing the required `/Title` field. By default, `Pdf.open_outline()`
  now quietly treats a missing `/Title` as an empty string; passing
  `strict=True` raises `OutlineStructureError` instead. Fixes :issue:`730`.

## v10.7.2

- Fixed a segmentation fault when an object that is not an `Encryption`, `dict`,
  `bool`, or `None` (for example a `list` or `unittest.mock.MagicMock`) was passed
  to the `encryption` argument of `Pdf.save()`. A `TypeError` is now raised instead.
  Fixes :issue:`727`.
- Fixed a possible segmentation fault in `Page.add_content_token_filter()` if the
  user had previously assigned a non-list value to the private
  `Pdf._token_filter_refs` attribute. The attribute is now reset before use.
- Suppressed nanobind's `leaked instances/types/functions` report at
  interpreter shutdown. Module-scope Python state (e.g. `pytest.mark.parametrize`
  arguments) commonly holds pikepdf objects until the interpreter exits;
  nanobind reports these as leaks even though they are not bugs. Set the
  environment variable `PIKEPDF_NANOBIND_LEAK_WARNINGS=1` before importing
  pikepdf to re-enable the report for debugging. Fixes :issue:`728`.
- Fixed `Array.append(None)` raising `TypeError` instead of inserting a PDF
  null object. This was a nanobind migration regression vs. v10.5. Fixes
  :issue:`725`.
- Fixed `Dictionary.__setattr__(name, None)` (i.e. `d.Key = None`) raising
  `TypeError` instead of the documented `ValueError` advising to use `del` to
  remove the key. Same nanobind migration regression as `Array.append(None)`.
- Fixed macro redefinition warnings on Fedora rawhide (Python 3.14 + glibc
  2.42) by ensuring `Python.h` is included before any standard library headers
  in all translation units. Fixes :issue:`724`.
- Moved the 2-bit and 4-bit subbyte pixel unpack inner loops from Python into
  C++, eliminating per-byte interpreter overhead when decoding low-bit-depth
  images.

## v10.7.1

- Fixed build to continue generating Python version specific wheels for
  3.12 and 3.13 due to open issue in nanobind. Fixes :issue:`723`. Thanks
  @mgorny for reporting.
- Improved CI build to perform more detailed tests using python3-dbg (debug
  build) which has more assertions and would have uncovered this issue.

## v10.7.0

- Yanked release from PyPI due to segfaults on Python 3.12 and 3.13; fixed in
  10.7.1.
- Python 3.12+ are now built with abi3 (the Stable ABI). Earlier versions and
  freethreading builds continue to be built against the specific Python versions.
- Remove manual hack to generate docs/requirements.txt for the readthedocs.org.

## v10.6.0

- Released v10.6.0 with version bump only.

## v10.6.0rc2

- Fixed a regression during nanobind migration (exception hierarchy
  unintentionally changed).

## v10.6.0rc1

- Replaced pybind11 with nanobind and added full freethreading support. pikepdf
  binary size is now both ~20% smaller and about 10% faster thanks to nanobind.

## v10.5.1

- Updated lockfile to avoid a PyJWT CVE. We only depend use PyJWT via pygithub
  for developer release tooling not in pikepdf itself, so this is
  inconsequential for pikepdf users but does silence automated security
  advisories.
- Suppressed GCC ``-Wpsabi`` note about C++17 ABI change for ``std::pair`` in
  pybind11 headers.

## v10.5.0

- Fixed logger in ``ctm`` module using ``__file__`` instead of ``__name__``,
  which produced unhelpful log names. :issue:`712`
- Modernized README.
- Test all README code blocks instead of just one.

## v10.4.0

- Enums are now proper Python ``enum.Enum``/``enum.IntFlag`` types (PEP 435
  compliant), migrated from pybind11's deprecated ``py::enum_`` to
  ``py::native_enum``.
- Reimplemented the PDFDocEncoding codec in pure Python using the standard
  library charmap pattern, removing the C++ dependency on qpdf for encoding.
- Upgraded to qpdf 12.3.2.
- Fixed incorrect docstrings for ``StreamDecodeLevel``. :issue:`708`
- Fixed type stubs: added PEP 570 positional-only markers, and corrected
  ``index()`` signature.

## v10.3.0

- Fixed UnicodeDecodeError when listing keys of a dictionary containing invalid
  UTF-8. Thanks @qooxzuub. :issue:`696`
- Fixed an issue where opening a PDF with duplicate form field names would cause a
  crash. Accessing a duplicate field by name now returns a proxy list of all matching
  fields. Thanks @qooxzuub. :issue:`697`
- Added `.values()` accessor to `Object` for iterating over dictionary values. Thanks @qooxzuub.:issue:`699,697`
- Added `.copy()` and `.update()` methods to `Dictionary`. Thanks @qooxzuub.:issue:`700`
- Improved `Object.copy` implementation and added type stubs. Thanks @qooxzuub.:issue:`702`
- Fixed missing return in `SimpleFont._encode_diffmap()`. Thanks @lachlan.charlick :issue:`706`
- Improved error messages for invalid dictionary access. Thanks @qooxzuub.:issue:`701`
- Lazy load lxml and Pillow to improve import time. Thanks @qooxzuub. :issue:`704`
- Improved `atomic_overwrite` robustness for restricted directories and special files. :issue:`695`

## v10.2.0

- Fixed `unparse_content_stream()` not preserving literal strings when given raw
  Python tuples. :issue:`689`
- The {func}`pikepdf.explicit_conversion` context manager is now thread-local and
  takes precedence over the global setting from {func}`pikepdf.set_object_conversion_mode`.
  Nested context managers are supported via a depth counter.
- Moved explicit conversion functions to their own module for better code organization.
- Improved C++ test coverage to 97.5% (from 96.4% line coverage, 94.9% to 95.1% function coverage).

## v10.1.0

- Added {class}`pikepdf.NamePath` for ergonomic access to deeply nested PDF
  structures. NamePath provides a single-operation traversal with helpful error
  messages showing exactly where traversal failed.
  See {ref}`Accessing nested objects with NamePath <namepath>` for details.
- Added explicit scalar types: {class}`pikepdf.Integer`, {class}`pikepdf.Boolean`,
  and {class}`pikepdf.Real`. When explicit conversion mode is enabled, these types
  are returned instead of Python native types (`int`, `bool`, `Decimal`), enabling
  better type safety and static type checking.
- Added {func}`pikepdf.set_object_conversion_mode` and
  {func}`pikepdf.get_object_conversion_mode` to control conversion behavior globally.
- Added {func}`pikepdf.explicit_conversion` context manager for temporarily enabling
  explicit conversion mode.
- Added safe accessor methods to {class}`pikepdf.Object`: {meth}`~pikepdf.Object.as_int`,
  {meth}`~pikepdf.Object.as_bool`, {meth}`~pikepdf.Object.as_float`, and
  {meth}`~pikepdf.Object.as_decimal` with optional default parameters for type-safe
  access to scalar values.
- `pikepdf.Integer` and `pikepdf.Real` now support full arithmetic operations with
  both `int` and `float` operands, including true division (`/`).

## v10.0.3

- Fixed an issue where `PdfImage.as_pil_image()` would create additional unused objects in the PDF that called it.
- Fixed a shutdown segfault in the alpha release of Python 3.15.
- Fixed `Pdf.show_xref_table()` not actually showing its output.
- Pin test dependencies python-xmp-toolkit to < 2.1.0. python-xmp-toolkit 2.1.0 is effectively a breaking change, requiring a new version of libexempi to be installed that is not available on some cibuildwheel builders. As a workaround, we have pinned the older version. We only use python-xmp-toolkit for testing to confirm correctness--pikepdf has its own XML-based implementation of XMP.

## v10.0.2

- Fixed presentation of strings using `unparse_content_stream` - if the stream can be represented using PdfDocEncoding, it is rendered in that way for ease of reading. :issue:`682`
- Reformatted C++ source.

## v10.0.1

- Fixed issue with performing equality test on dictionaries with cyclic subgraphs.
  :issue:`677`

## v10.0.0

See breaking changes for v10.0.0 above.

