Metadata-Version: 2.4
Name: opencc-pyo3
Version: 0.12.1
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
License-File: LICENSE
Summary: High-performance Chinese text conversion (Simplified ↔ Traditional) powered by Rust, PyO3, and OpenCC lexicons.
Author-email: laisuk <laisuk@yahoo.com>
License: MIT
Requires-Python: >=3.8
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: ChangeLog, https://github.com/laisuk/opencc_pyo3/blob/master/CHANGELOG.md
Project-URL: Homepage, https://github.com/laisuk/opencc_pyo3
Project-URL: Issues, https://github.com/laisuk/opencc_pyo3/issues

# opencc_pyo3

[![PyPI version](https://img.shields.io/pypi/v/opencc-pyo3.svg)](https://pypi.org/project/opencc-pyo3/)
[![Downloads](https://pepy.tech/badge/opencc-pyo3)](https://pepy.tech/project/opencc-pyo3)
[![Python Versions](https://img.shields.io/pypi/pyversions/opencc-pyo3.svg)](https://pypi.org/project/opencc-pyo3/)
[![License](https://img.shields.io/github/license/laisuk/opencc_pyo3)](https://github.com/laisuk/opencc_pyo3/blob/master/LICENSE)
[![Build Status](https://github.com/laisuk/opencc_pyo3/actions/workflows/build.yml/badge.svg)](https://github.com/laisuk/opencc_pyo3/actions/workflows/build.yml)

`opencc_pyo3` is a Python extension module powered by [Rust](https://www.rust-lang.org/) and [PyO3](https://pyo3.rs/),
providing fast and accurate conversion between different Chinese text variants
using [OpenCC](https://github.com/BYVoid/OpenCC) algorithms.

## Features

- Convert between Simplified, Traditional, Hong Kong, Taiwan, and Japanese Kanji variants with OpenCC-compatible
  configurations.
- Small Seal Script conversion with `s2seal`, `t2seal`, `seal2s`, and `seal2t` (added in v0.12.0).
- High-performance Rust + PyO3 backend for fast, memory-efficient Chinese text conversion in Python.
- Python API with `OpenCC`, `OpenccConfig`, config validation helpers, punctuation conversion, and Chinese text variant
  detection.
- Command-line interface for plain text conversion from files or standard input.
- Office and EPUB document conversion support for `.docx`, `.xlsx`, `.pptx`, `.odt`, `.ods`, `.odp`, and `.epub`.
- PDF text extraction helpers, including PDFium-based page-by-page extraction utilities.
- CJK paragraph reflow helper for cleaning PDF-extracted text before conversion.

## Supported Conversion Configurations

- `s2t`, `t2s`, `s2tw`, `tw2s`, `s2twp`, `tw2sp`, `s2hk`, `hk2s`, `s2hkp`, `hk2sp`, `t2tw`, `tw2t`, `t2twp`,
  `tw2tp`, `t2hk`, `t2hkp`, `hk2t`, `hk2tp`, `t2jp`, `jp2t`, `s2seal`, `t2seal`, `seal2s`, `seal2t`

### Small Seal Script

The four Seal configurations added in v0.12.0 convert between modern Chinese text and Small Seal Script:

| Config   | Conversion                              |
|:---------|:----------------------------------------|
| `s2seal` | Simplified Chinese → Small Seal Script  |
| `t2seal` | Traditional Chinese → Small Seal Script |
| `seal2s` | Small Seal Script → Simplified Chinese  |
| `seal2t` | Small Seal Script → Traditional Chinese |

`s2seal` first converts Simplified to Traditional Chinese. Both forward configurations then bridge modern character
variants to regular-script transcriptions and map those transcriptions to Seal characters. Reverse configurations map
Seal characters to regular-script transcriptions, bridge them to standard Traditional forms, and, for `seal2s`, convert
those forms to Simplified Chinese. Variant bridging preserves character identity rather than applying historical
本字/假借 substitutions.

```python
from opencc_pyo3 import OpenCC, OpenccConfig

seal = OpenCC(OpenccConfig.S2SEAL).convert("你好，小篆「国际编码18」")
print(seal)  # 你𿒛，𽌠𽴖「𾇓𿭖𿛛碼18」
print(OpenCC("t2seal").convert("你好，小篆「國際編碼18」"))  # Same Seal output
print(OpenCC("seal2s").convert(seal))  # 你好，小篆「国际编码18」
print(OpenCC(OpenccConfig.SEAL2T).convert(seal))  # 你好，小篆「國際編碼18」
```

Displaying Seal characters requires a font that covers them; conversion still returns Unicode text when the terminal
or editor cannot display the glyphs.

## Installation

### 1. Install from PyPI

Install the prebuilt platform-specific wheel:

```bash
pip install opencc-pyo3
```

### 2. Install directly from GitHub

Install the latest source from the `master` branch:

```bash
pip install "git+https://github.com/laisuk/opencc_pyo3.git@master"
```

Alternatively, install directly from the GitHub source archive:

```bash
pip install "https://github.com/laisuk/opencc_pyo3/archive/refs/heads/master.zip"
```

The custom PEP 517 build backend automatically bundles the appropriate PDFium native library for the host platform
before building the wheel.

**Requirements:** A working `Rust toolchain` and `Python build environment` are required for source installation.

**Note:** GitHub source installations build against the local system environment and do not apply the compatibility
settings used for official release wheels (such as `Windows 7` support or `Linux manylinux` compatibility).

### 3. Build and install a Python wheel using [maturin](https://github.com/PyO3/maturin)

For a local build, synchronize the PDFium native library before invoking maturin:

```sh
# In the project root
python tools/sync_pdfium_for_wheel.py --local
maturin build --release
pip install ./target/wheels/opencc_pyo3-<version>-cp<pyver>-abi3-<platform>.whl
```

Or for development (a virtual environment is recommended):

```sh
python tools/sync_pdfium_for_wheel.py --local
maturin develop -r
```

Each platform-specific wheel includes only its matching PDFium native library. PyPI releases provide prebuilt wheels
without a source distribution.

See [build.txt](https://github.com/laisuk/opencc_pyo3/blob/master/build.txt) for detailed build and installation
instructions.

---

## Usage

### Python

```python
from opencc_pyo3 import OpenCC

text = "“春眠不觉晓，处处闻啼鸟。”"
opencc = OpenCC("s2t")
converted = opencc.convert(text, punctuation=True)
print(converted)  # 「春眠不覺曉，處處聞啼鳥。」
```

---

### CLI

You can also use the CLI interface via Python module or Python script:  
Sub-Commands are:

- `convert`: Convert Chinese text using OpenCC
- `office`: Convert Office document Chinese text using OpenCC
- `pdf`: Convert extracted PDF document text using OpenCC

---

#### convert

```
python -m opencc_pyo3 convert --help
usage: opencc-pyo3 convert [-h] [-i <file>] [-o <file>] [-c <conversion>] [-p] [-n] [-E] [--detofu [<level>]] [--detofu-file <file>] [-D <slot:mode:path>]
                           [--in-enc <encoding>] [--out-enc <encoding>]

optional arguments:
  -h, --help            show this help message and exit
  -i <file>, --input <file>
                        Read original text from <file>. (default: None)
  -o <file>, --output <file>
                        Write converted text to <file>. (default: None)
  -c <conversion>, --config <conversion>
                        Configuration: s2t|s2tw|s2twp|s2hk|s2hkp|t2s|t2tw|t2twp|t2hk|t2hkp|tw2s|tw2sp|tw2t|tw2tp|hk2s|hk2sp|hk2t|hk2tp|jp2t|t2jp|s2seal|t2seal|seal2s|seal2t (default:
                        None)
  -p, --punct           Enable punctuation conversion. (Default: False) (default: False)
  -n, --norm-compat     Normalize CJK Compatibility Ideographs before conversion. (Default: False) (default: False)
  -E, --norm-compat-extended
                        Normalize extended Unicode compatibility forms before conversion. (Default: False) (default: False)
  --detofu [<level>]    Apply tofu-safe fallback after conversion. Levels: all/ExtB, ExtC, ExtD, ExtE, ExtF, ExtG, ExtH, ExtI. (default: None)
  --detofu-file <file>  Load additional detofu fallback mappings from a UTF-8 text file. Custom mappings override built-in mappings; requires --detofu.
                        (default: None)
  -D <slot:mode:path>, --custom-dict <slot:mode:path>
                        Load custom dictionary file. Format: slot:mode:path, e.g. STPhrases:append:custom.txt. Can be used multiple times. Available slots: STC
                        haracters|STPhrases|STPunctuations|TSCharacters|TSPhrases|TSPunctuations|TWPhrases|TWPhrasesRev|HKPhrases|HKPhrasesRev|TWVariants|TWVar
                        iantsPhrases|TWVariantsRev|TWVariantsRevPhrases|HKVariants|HKVariantsPhrases|HKVariantsRev|HKVariantsRevPhrases|JPSCharacters|JPSCharac
                        tersRev|JPSPhrases|SealCharacters|SealCharactersRev|SealVariants|SealVariantsRev (default: None)
  --in-enc <encoding>   Encoding for input. (Default: UTF-8) (default: UTF-8)
  --out-enc <encoding>  Encoding for output files and redirected stdout. Interactive console output uses the terminal's Unicode stream. (Default: UTF-8)
                        (default: UTF-8)
```

---

#### office

Convert Microsoft Office, OpenDocument, and EPUB files (`.docx`, `.xlsx`, `.pptx`, `.odt`, `.ods`, `.odp`, `.epub`).

Use `-F` to apply the document's conversion settings to the filename stem when the CLI generates the output path.
For example, the following command writes `漢字_converted.docx` beside the input file. An explicit `--output`
takes precedence over filename conversion.

```sh
opencc-pyo3 office -i "汉字.docx" -c s2t --convert-filename
```

```
python -m opencc_pyo3 office --help                                         
usage: opencc-pyo3 office [-h] -i <file> [-o <file>] [-c <conversion>] [-p] [-F] [-n] [-E] [--detofu [<level>]] [--detofu-file <file>] [-f <format>] [-k]        
                          [-D <slot:mode:path>]

optional arguments:
  -h, --help            show this help message and exit
  -i <file>, --input <file>
                        Input Office document from <file>. (default: None)
  -o <file>, --output <file>
                        Output Office document to <file>. (default: None)
  -c <conversion>, --config <conversion>
                        Configuration:
                        s2t|s2tw|s2twp|s2hk|s2hkp|t2s|t2tw|t2twp|t2hk|t2hkp|tw2s|tw2sp|tw2t|tw2tp|hk2s|hk2sp|hk2t|hk2tp|jp2t|t2jp|s2seal|t2seal|seal2s|seal2t
                        (default: None)
  -p, --punct           Enable punctuation conversion. (Default: False) (default: False)
  -F, --convert-filename
                        Convert the input filename stem when generating the default output filename. Ignored when --output is specified. (default: False)
  -n, --norm-compat     Normalize CJK Compatibility Ideographs before conversion. (Default: False) (default: False)
  -E, --norm-compat-extended
                        Normalize extended Unicode compatibility forms before conversion. (Default: False) (default: False)
  --detofu [<level>]    Apply tofu-safe fallback after conversion. Levels: all/ExtB, ExtC, ExtD, ExtE, ExtF, ExtG, ExtH, ExtI. (default: None)
  --detofu-file <file>  Load additional detofu fallback mappings from a UTF-8 text file. Custom mappings override built-in mappings; requires --detofu.
                        (default: None)
  -f <format>, --format <format>
                        Target Office format (e.g., docx, xlsx, pptx, odt, ods, odp, epub) (default: None)
  -k, --keep-font       Preserve font-family information in Office content (default: False)
  -D <slot:mode:path>, --custom-dict <slot:mode:path>
                        Load custom dictionary file. Format: slot:mode:path, e.g. STPhrases:append:custom.txt. Can be used multiple times. Available slots: STC
                        haracters|STPhrases|STPunctuations|TSCharacters|TSPhrases|TSPunctuations|TWPhrases|TWPhrasesRev|HKPhrases|HKPhrasesRev|TWVariants|TWVar
                        iantsPhrases|TWVariantsRev|TWVariantsRevPhrases|HKVariants|HKVariantsPhrases|HKVariantsRev|HKVariantsRevPhrases|JPSCharacters|JPSCharac
                        tersRev|JPSPhrases|SealCharacters|SealCharactersRev|SealVariants|SealVariantsRev (default: None)
```

---

#### PDF

Support PDF files as input, with built-in text extraction and OpenCC-based conversion powered by `opencc-fmmseg`
(available since v0.8.4).

This command allows you to extract Chinese text from PDF documents, optionally apply CJK-aware paragraph reflow, and
convert the result using OpenCC configurations.

> **Note**  
> Only text-embedded (searchable) PDF documents are supported.  
> Scanned or image-only PDFs without an embedded text layer are not currently supported.

```
python -m opencc_pyo3 pdf --help
usage: opencc-pyo3 pdf [-h] -i <file> [-o <file>] [-c <conversion>] [-p] [-H] [-r] [-C] [--timing] [-e] [-n] [-E] [--detofu [<level>]] [--detofu-file <file>]
                       [-D <slot:mode:path>]

optional arguments:
  -h, --help            show this help message and exit
  -i <file>, --input <file>
                        Input PDF file. (default: None)
  -o <file>, --output <file>
                        Output text file (UTF-8). If omitted, defaults to "<input>_converted.txt". (default: None)
  -c <conversion>, --config <conversion>
                        Configuration: s2t|s2tw|s2twp|s2hk|s2hkp|t2s|t2tw|t2twp|t2hk|t2hkp|tw2s|tw2sp|tw2t|tw2tp|hk2s|hk2sp|hk2t|hk2tp|jp2t|t2jp|s2seal|t2seal|seal2s|seal2t (default:
                        None)
  -p, --punct           Enable punctuation conversion. (Default: False) (default: False)
  -H, --header          Preserve page-break-like gaps when reflowing CJK paragraphs (passed as add_pdf_page_header to reflow_cjk_paragraphs). (default: False)
  -r, --reflow          Enable CJK-aware paragraph reflow before conversion. (default: False)
  -C, --compact         Use compact paragraph mode (single newline between paragraphs). (default: False)
  --timing              Show time use for each process workflow. (default: False)
  -e, --extract         Extract PDF text only (skip OpenCC conversion). (default: False)
  -n, --norm-compat     Normalize CJK Compatibility Ideographs before conversion. (Default: False) (default: False)
  -E, --norm-compat-extended
                        Normalize extended Unicode compatibility forms before conversion. (Default: False) (default: False)
  --detofu [<level>]    Apply tofu-safe fallback after conversion. Levels: all/ExtB, ExtC, ExtD, ExtE, ExtF, ExtG, ExtH, ExtI. (default: None)
  --detofu-file <file>  Load additional detofu fallback mappings from a UTF-8 text file. Custom mappings override built-in mappings; requires --detofu.
                        (default: None)
  -D <slot:mode:path>, --custom-dict <slot:mode:path>
                        Load custom dictionary file. Format: slot:mode:path, e.g. STPhrases:append:custom.txt. Can be used multiple times. Available slots: STC
                        haracters|STPhrases|STPunctuations|TSCharacters|TSPhrases|TSPunctuations|TWPhrases|TWPhrasesRev|HKPhrases|HKPhrasesRev|TWVariants|TWVar
                        iantsPhrases|TWVariantsRev|TWVariantsRevPhrases|HKVariants|HKVariantsPhrases|HKVariantsRev|HKVariantsRevPhrases|JPSCharacters|JPSCharac
                        tersRev|JPSPhrases|SealCharacters|SealCharactersRev|SealVariants|SealVariantsRev (default: None)
```

```sh
python -m opencc_pyo3 convert -i input.txt -o output.txt -c s2t --punct

python -m opencc_pyo3 convert -i input.txt -o output.txt -c t2s --norm-compat

python -m opencc_pyo3 convert -i input.txt -o output.txt -c s2t --detofu all --detofu-file custom_detofu.txt

echo "這個細路哥很靈活" | python -m opencc_pyo3 convert -c hk2sp --custom-dict HKVariantsRevPhrases:append:my_hk_dict.txt
# output: 这个小男孩很灵活

echo "天龍八部書裡的喬峰是契丹人" | opencc-pyo3 convert -c t2s --norm-compat
# output: 天龙八部书里的乔峰是契丹人
 
python -m opencc_pyo3 office -c s2t --punct -i input.docx -o output.docx --keep-font

opencc-pyo3 office -c s2tw -p -i input.epub -o output.epub

opencc-pyo3 pdf -i input.pdf -o output.txt -c s2t --punct --reflow --norm-compat

opencc-pyo3 convert -i simplified.txt -o seal.txt -c s2seal

opencc-pyo3 convert -i seal.txt -o traditional.txt -c seal2t

opencc-pyo3 convert -i seal.txt -o traditional.txt -c seal2t -D SealCharacters:append:custom_seal.txt
```

my_hk_dict.txt:

`--custom-dict` accepts `slot:mode:path` and can be passed more than once. The token is validated before conversion:
`slot`, `mode`, and `path` must all be present. Supported merge modes are `append` and `override`; common slots include
`STPhrases`, `TWPhrases`, `HKVariantsRevPhrases`, and `JPSCharactersRev`.

```text
細路哥	小男孩
```

---

## Python API

### `OpenccConfig`

`OpenccConfig` is an enum exported from `opencc_pyo3` for config-safe usage:

```python
from opencc_pyo3 import OpenCC, OpenccConfig

cc = OpenCC(OpenccConfig.S2TW)
print(cc.convert("汉字"))  # 漢字
```

Use `OpenccConfig.parse("S2TW")` to look up a code case-insensitively. Unknown codes raise `ValueError`.
Use `to_canonical_name()` to get the lowercase code.

Available enum values:

- `OpenccConfig.S2T`
- `OpenccConfig.T2S`
- `OpenccConfig.S2TW`
- `OpenccConfig.TW2S`
- `OpenccConfig.S2TWP`
- `OpenccConfig.TW2SP`
- `OpenccConfig.S2HK`
- `OpenccConfig.HK2S`
- `OpenccConfig.T2TW`
- `OpenccConfig.TW2T`
- `OpenccConfig.T2TWP`
- `OpenccConfig.TW2TP`
- `OpenccConfig.T2HK`
- `OpenccConfig.T2HKP`
- `OpenccConfig.HK2T`
- `OpenccConfig.HK2TP`
- `OpenccConfig.T2JP`
- `OpenccConfig.JP2T`
- `OpenccConfig.S2HKP`
- `OpenccConfig.HK2SP`
- `OpenccConfig.S2SEAL`
- `OpenccConfig.T2SEAL`
- `OpenccConfig.SEAL2S`
- `OpenccConfig.SEAL2T`

### `OpenCC`

Core converter class backed by the Rust extension module.

- `OpenCC(config: str | OpenccConfig = "s2t", preserve_ids: bool = False)`
    - `config`: OpenCC conversion configuration. Codes are case-insensitive. Unknown codes select `s2t` and set a message in `get_last_error()`.
    - `preserve_ids`: Preserve characters inside Unicode IDS (Ideographic Description Sequence) structures such as `⿰`,
      `⿱`, and `⿲` during conversion. Default is `False`.
- `OpenCC.from_dicts(config="s2t", specs=None) -> OpenCC`
    - Creates a converter with programmatic, in-memory custom dictionary entries.
- `OpenCC.from_dict_files(config="s2t", specs=None) -> OpenCC`
    - Creates a converter with OpenCC-style custom dictionary files.
- `convert(input_text: str, punctuation: bool = False) -> str`
    - Converts text using the current configuration. Set `punctuation=True` to apply its punctuation mappings.
- `normalize_compat(text: str) -> str`
    - Maps CJK Compatibility Ideographs to their unified forms, such as `金` to `金`.
- `normalize_compat_extended(text: str) -> str`
    - Applies the CJK ideograph mappings and additional Unicode compatibility mappings.
      Use either normalization method before `convert()`.
- `detofu(text: str, level: str = "all") -> str`
    - Replaces rare CJK extension characters with display-safe fallback characters.
- `detofu_with_custom_file(text: str, level: str = "all", path: str | None = None) -> str`
    - Applies DeTofu with additional UTF-8 fallback mappings from a file.
- `detofu_with_custom_pairs(text: str, level: str = "all", pairs: list[tuple[str, str]] | None = None) -> str`
    - Applies DeTofu with additional in-memory fallback character pairs.
- `set_config(config: str | OpenccConfig) -> None`
    - Changes the active configuration. A valid code clears the configuration error. An unknown code selects `s2t`
      and records an error message. The `config` property and `apply_config()` provide the same behavior.
- `get_config() -> str`
    - Returns the current canonical config name.
- `get_last_error() -> str`
    - Returns the most recent config error message, or `""` if none.
- `zho_check(input_text: str) -> int`
    - Detects the text type.
    - `1` = Traditional Chinese, `2` = Simplified Chinese, `0` = other / undetermined
- `OpenCC.supported_configs() -> list[str]`
    - Returns all supported config names.
- `OpenCC.is_valid_config(config: str | OpenccConfig) -> bool`
    - Validates a config string.
- `OpenCC.available_slots() -> list[str]`
    - Returns all 25 active canonical custom dictionary slot names, including the four Seal slots.

Example:

```python
from opencc_pyo3 import OpenCC, OpenccConfig

cc = OpenCC(OpenccConfig.S2T)
print(cc.get_config())  # s2t
print(cc.convert("汉字", punctuation=True))
print(cc.zho_check("汉字"))  # 2

cc = OpenCC("t2s", preserve_ids=True)
print(cc.convert("漢字 ⿰氵漢 馬"))  # 汉字 ⿰氵漢 马

cc.set_config("t2jp")
print(cc.convert("圖書館"))  # 図書館

# Phrase-aware Traditional ↔ Hong Kong Traditional conversions
print(OpenCC(OpenccConfig.T2HKP).get_config())  # t2hkp
print(OpenCC(OpenccConfig.HK2TP).get_config())  # hk2tp

print(OpenCC.supported_configs())
print(OpenCC.available_slots())
print(OpenCC.is_valid_config("s2hk"))  # True
```

### DeTofu Display Fallbacks

DeTofu can replace rare CJK extension characters with more widely displayable fallback characters after normal OpenCC
conversion. The level selects the first extension block to process. `"all"` starts at ExtB. Use ExtB through ExtI
or their short forms B through I. Levels are case-insensitive and accept hyphens or underscores. An unsupported
level raises `ValueError`.

Supply `path` or `pairs` for the corresponding custom method, even when using the default level. Each pair contains
two single-character strings. Custom mappings take precedence over built-in mappings for the same character.

```python
from opencc_pyo3 import OpenCC

cc = OpenCC("s2t")

print(cc.detofu("𠉂𪠟𫝈𫬐", "all"))
print(cc.detofu_with_custom_file("𣭲毛", "all", "custom_detofu.txt"))
print(cc.detofu_with_custom_pairs("𣭲毛", "all", [("𣭲", "氄")]))
```

Custom DeTofu files use one mapping per line:

```text
𣭲	氄	B
```

---

### Custom Dictionaries

Both factories start with the embedded dictionaries and apply custom entries at construction. To change the
dictionaries, create a new converter. To switch the active configuration on a factory result, assign
`cc.config = "t2s"` or call `cc.apply_config("t2s")`. Both factories return the native converter, whose public
configuration methods are `apply_config()` and `get_config()`.

#### In-memory custom dictionaries

Use `OpenCC.from_dicts()` for programmatic terms:

```python
from typing import List

from opencc_pyo3 import OpenCC, CustomDictSpec

specs: List[CustomDictSpec] = [
    {
        "slot": "STPhrases",
        "pairs": [("帕兰蒂尔", "柏蘭蒂爾")],
        "mode": "append",
    }
]

cc = OpenCC.from_dicts("s2t", specs)

print(cc.convert("帕兰蒂尔是一家公司"))
# 柏蘭蒂爾是一家公司
```

#### File-based custom dictionaries

Use `OpenCC.from_dict_files()` for OpenCC-style dictionary files:

```python
from typing import List

from opencc_pyo3 import OpenCC, CustomDictFileSpec

specs: List[CustomDictFileSpec] = [
    {
        "slot": "STPhrases",
        "files": ["custom_st_phrases.txt"],
        "mode": "append",
    }
]

cc = OpenCC.from_dict_files("s2t", specs)
```

Custom dictionary files use one mapping per line:

```text
source<TAB>target
```

Example:

```text
帕兰蒂尔	柏蘭蒂爾
```

#### Merge modes

- `append` (default) adds custom entries to the existing dictionary slot. Duplicate keys follow the backend "last wins" behavior.
- `override` replaces the entire target dictionary slot with the custom entries or files.

#### Supported dictionary slots

Use canonical slot names without `.txt`, such as `STPhrases`, not `STPhrases.txt`. The Python wrapper may tolerate
`.txt`, but the documented API uses canonical names only.

| Slot                   | Purpose                                              | OpenCC dictionary file         |
|:-----------------------|:-----------------------------------------------------|:-------------------------------|
| `STCharacters`         | Simplified → Traditional character mappings          | `STCharacters.txt`             |
| `STPhrases`            | Simplified → Traditional phrase mappings             | `STPhrases.txt`                |
| `STPunctuations`       | Simplified → Traditional punctuation mappings        | `STPunctuations.txt`           |
| `TSCharacters`         | Traditional → Simplified character mappings          | `TSCharacters.txt`             |
| `TSPhrases`            | Traditional → Simplified phrase mappings             | `TSPhrases.txt`                |
| `TSPunctuations`       | Traditional → Simplified punctuation mappings        | `TSPunctuations.txt`           |
| `TWPhrases`            | Traditional → Taiwan phrase mappings                 | `TWPhrases.txt`                |
| `TWPhrasesRev`         | Taiwan → Traditional reverse phrase mappings         | `TWPhrasesRev.txt`             |
| `HKPhrases`            | Traditional → Hong Kong phrase mappings              | `HKPhrases.txt`                |
| `HKPhrasesRev`         | Hong Kong → Traditional reverse phrase mappings      | `HKPhrasesRev.txt`             |
| `TWVariants`           | Traditional → Taiwan regional character variants     | `TWVariants.txt`               |
| `TWVariantsPhrases`    | Traditional → Taiwan regional phrase variants        | `TWVariantsPhrases.txt`        |
| `TWVariantsRev`        | Taiwan → Traditional reverse character variants      | `TWVariantsRev.txt`            |
| `TWVariantsRevPhrases` | Taiwan → Traditional reverse phrase variants         | `TWVariantsRevPhrases.txt`     |
| `HKVariants`           | Traditional → Hong Kong regional character variants  | `HKVariants.txt`               |
| `HKVariantsPhrases`    | Traditional → Hong Kong regional phrase variants     | `HKVariantsPhrases.txt`        |
| `HKVariantsRev`        | Hong Kong → Traditional reverse character variants   | `HKVariantsRev.txt`            |
| `HKVariantsRevPhrases` | Hong Kong → Traditional reverse phrase variants      | `HKVariantsRevPhrases.txt`     |
| `JPSCharacters`        | Japanese Shinjitai character mappings                | `JPShinjitaiCharacters.txt`    |
| `JPSCharactersRev`     | Japanese Shinjitai reverse character mappings        | `JPShinjitaiCharactersRev.txt` |
| `JPSPhrases`           | Japanese Shinjitai phrase mappings                   | `JPShinjitaiPhrases.txt`       |
| `SealCharacters`       | Small Seal → regular-script transcriptions           | `SealCharacters.txt`           |
| `SealCharactersRev`    | Regular-script transcriptions → Small Seal           | `SealCharactersRev.txt`        |
| `SealVariants`         | Modern variants → regular-script transcriptions      | `SealVariants.txt`             |
| `SealVariantsRev`      | Regular-script transcriptions → standard Traditional | `SealVariantsRev.txt`          |

Custom dictionary behavior follows the same OpenCC dictionary-slot model. Choosing the wrong slot may have no effect or
may affect a different conversion path. For `s2t`, use `STCharacters` or `STPhrases`. For `t2s`, use `TSCharacters` or
`TSPhrases`. For Taiwan regional behavior, use `TWPhrases`, `TWPhrasesRev`, `TWVariantsPhrases`, `TWVariants`,
`TWVariantsRev`, or `TWVariantsRevPhrases`. For Hong Kong regional behavior, use `HKPhrases`, `HKPhrasesRev`,
`HKVariantsPhrases`, `HKVariants`, `HKVariantsRev`, or `HKVariantsRevPhrases`. For Japanese Shinjitai behavior, use
`JPSCharacters`, `JPSCharactersRev`, or `JPSPhrases`.

#### Custom Seal dictionaries

For `s2seal` and `t2seal`, use `SealVariants` for variant bridging and `SealCharactersRev` for mappings to Seal
characters. For `seal2s` and `seal2t`, use `SealCharacters` for mappings from Seal characters and `SealVariantsRev` for
bridging to standard Traditional forms. Each slot is customized separately; changing one does not automatically update
its reverse slot. Later conversion stages still apply to custom mapping results.

For example, override the mapping for one Seal character while keeping the rest of the embedded dictionary:

```python
from opencc_pyo3 import OpenCC

cc = OpenCC.from_dicts("seal2t", [{
    "slot": "SealCharacters",
    "mode": "append",
    "pairs": [("𿒛", "佳")],
}])
print(cc.convert("你𿒛"))  # 你佳
```

The equivalent UTF-8 `custom_seal.txt` file contains a tab-separated mapping:

```text
𿒛	佳
```

Use it with `OpenCC.from_dict_files("seal2t", [{"slot": "SealCharacters", "mode": "append",
"files": ["custom_seal.txt"]}])` or the CLI `-D SealCharacters:append:custom_seal.txt` example above.

#### Typing helpers

`CustomDictSpec` and `CustomDictFileSpec` are exported for typed Python code:

```python
from typing import List
from opencc_pyo3 import OpenCC, CustomDictSpec

specs: List[CustomDictSpec] = [
    {
        "slot": "STPhrases",
        "pairs": [("帕兰蒂尔", "柏蘭蒂爾")],
        "mode": "append",
    }
]

cc = OpenCC.from_dicts("s2t", specs)
```

Invalid slots, invalid modes, malformed dictionary lines, and unreadable dictionary files raise `ValueError`.

---

### Reflow helper

- `reflow_cjk_paragraphs(text: str, add_pdf_page_header: bool, compact: bool) -> str`
    - Reflows PDF-extracted CJK text by merging broken line wraps while preserving paragraph structure.
    - `add_pdf_page_header=True` retains page-like blank-line gaps that reflow would otherwise skip.
    - `compact=True` uses single newlines between paragraphs. Use `False` for blank lines.

Example:

```python
from opencc_pyo3.opencc_pyo3 import reflow_cjk_paragraphs

raw_text = "我来到\n无人海边。"
clean_text = reflow_cjk_paragraphs(raw_text, add_pdf_page_header=False, compact=False)  # 我来到无人海边。
```

### PDF extraction APIs

The package exposes PDFium-based PDF extraction helpers in `opencc_pyo3.pdfium_helper`.

Import from `opencc_pyo3.pdfium_helper`:

- `extract_pdf_pages_with_callback_pdfium(path: str, callback, /, add_page_header: bool = False) -> None`
- `extract_pdf_text_pdfium_progress(path: str) -> str`
- `extract_pdf_text_pdfium_silent(path: str) -> str`
- `extract_pdf_text_pages_pdfium(path: str, /) -> list[str]`
- `extract_pdf_text_pages_pdfium_progress(path: str, /) -> list[str]`
- `make_progress_collector() -> tuple[callback, list[str]]`
- `make_silent_collector() -> tuple[callback, list[str]]`

The callback receives `(page_number, total_pages, text)` with one-based page numbers. Page text is trimmed
and normalized to LF. Blank or unreadable pages contribute separators. Set `add_page_header=True` to include
page markers, or use one of the progress helpers to print extraction progress. PDFium raises a `RuntimeError`
through these helpers if it cannot open the document.

Example:

```python
from opencc_pyo3 import OpenCC
from opencc_pyo3.opencc_pyo3 import reflow_cjk_paragraphs
from opencc_pyo3.pdfium_helper import extract_pdf_text_pdfium_silent

raw = extract_pdf_text_pdfium_silent("input.pdf")
text = reflow_cjk_paragraphs(raw, add_pdf_page_header=False, compact=False)
converted = OpenCC("s2t").convert(text, punctuation=True)
```

### Office and EPUB conversion API

Import `convert_office_doc` from `opencc_pyo3.office_helper` to convert a document with a text callback.
Pass the format code, such as `"docx"` or `"epub"`, and a callable that receives a string and returns
its replacement. Passing `None` as the output path creates a sibling file with `_converted` appended
to its stem. The helper validates the resulting archive before replacing an existing output file.

```python
from opencc_pyo3 import OpenCC
from opencc_pyo3.office_helper import convert_office_doc

cc = OpenCC("s2t")
ok, message = convert_office_doc("input.docx", None, "docx", cc.convert, keep_font=True)
print(message)
```

## Development

- Rust source: [src/lib.rs](https://github.com/laisuk/opencc_pyo3/blob/master/src/lib.rs)
- Python bindings: [opencc_pyo3/
  __init__.py](https://github.com/laisuk/opencc_pyo3/blob/master/opencc_pyo3/__init__.py), [opencc_pyo3/opencc_pyo3.pyi](https://github.com/laisuk/opencc_pyo3/blob/master/opencc_pyo3/opencc_pyo3.pyi)
- CLI: [opencc_pyo3/ __main__.py](https://github.com/laisuk/opencc_pyo3/blob/master/opencc_pyo3/__main__.py)

## Benchmarks

Historical benchmark results for `opencc-pyo3` v0.10.2, collected by the
[Benchmark on macos-latest workflow](https://github.com/laisuk/opencc_pyo3/actions/runs/29220806465#summary-86725382175).

```
Package: opencc_pyo3
Version: 0.10.2
OS: macOS 26.4 (arm64)
CPU: Apple M1 (Virtual)
Cores: 3
Memory: 7.0 GiB
Python: 3.10.11
Rust: rustc 1.97.0 (2d8144b78 2026-07-07)
Configs: s2t, s2tw, s2twp
Text sizes: 100, 1,000, 10,000, 100,000 characters
```

### BENCHMARK RESULTS

---

| Method         | Config | Text Size | Mean (ms) | Median (ms) | StdDev (ms) | Min (ms) | Max (ms) | Ops/sec |  Chars/sec |
|:---------------|:-------|----------:|----------:|------------:|------------:|---------:|---------:|--------:|-----------:|
| Convert_Small  | s2t    |       100 |     0.008 |       0.005 |       0.011 |    0.004 |    0.071 | 128,239 | 12,823,911 |
| Convert_Medium | s2t    |     1,000 |     0.038 |       0.037 |       0.008 |    0.036 |    0.108 |  26,113 | 26,113,217 |
| Convert_Large  | s2t    |    10,000 |     0.236 |       0.237 |       0.030 |    0.190 |    0.337 |   4,229 | 42,287,025 |
| Convert_XLarge | s2t    |   100,000 |     2.144 |       2.088 |       0.285 |    1.751 |    3.034 |     466 | 46,638,386 |
| Convert_Small  | s2tw   |       100 |     0.005 |       0.005 |       0.001 |    0.005 |    0.011 | 193,679 | 19,367,928 |
| Convert_Medium | s2tw   |     1,000 |     0.046 |       0.043 |       0.010 |    0.043 |    0.133 |  21,755 | 21,754,749 |
| Convert_Large  | s2tw   |    10,000 |     0.311 |       0.309 |       0.037 |    0.237 |    0.424 |   3,219 | 32,190,775 |
| Convert_XLarge | s2tw   |   100,000 |     2.949 |       2.860 |       0.445 |    2.282 |    4.753 |     339 | 33,914,979 |
| Convert_Small  | s2twp  |       100 |     0.006 |       0.006 |       0.000 |    0.005 |    0.011 | 177,416 | 17,741,637 |
| Convert_Medium | s2twp  |     1,000 |     0.048 |       0.048 |       0.002 |    0.047 |    0.061 |  20,783 | 20,782,517 |
| Convert_Large  | s2twp  |    10,000 |     0.333 |       0.343 |       0.037 |    0.264 |    0.544 |   3,002 | 30,021,166 |
| Convert_XLarge | s2twp  |   100,000 |     3.034 |       3.024 |       0.273 |    2.497 |    3.844 |     330 | 32,964,999 |

### Summary

For representative Medium through XLarge inputs, `s2t` is consistently the fastest configuration, reaching about 46.6
million characters per second for the 100,000-character sample. The regional `s2tw` and phrase-aware `s2twp`
pipelines perform additional dictionary work and reach about 33.9 million and 33.0 million characters per second,
respectively, at the same size. Mean and median timings are now close on the larger samples, indicating that full-input
warmups and shuffled execution substantially reduced the earlier CI outlier distortion. Results for the 100-character
sample should still be treated as microbenchmark-scale measurements, where timer and scheduling noise have a larger
relative effect.

---

### Reproduce Benchmarks

```bash
python bench/opencc_benchmark_md.py --ci --configs s2t s2tw s2twp --sizes Small Medium Large XLarge --iterations 120 --warmup 15 --warmup-full-text --shuffle-order --export md json --output-dir bench/out
```

## Projects That Use `opencc-pyo3`

[OpenccPyo3Gui](https://github.com/laisuk/OpenccPyo3Gui)

---

## License

[MIT](https://github.com/laisuk/opencc_pyo3/blob/master/LICENSE)

---

Powered by **Rust**, **PyO3**, **OpenCC**, **Pdfium** and **opencc-fmmseg**.

