All formats

easyofd

LanguagePython 3
LicenseApache-2.0 (depends on PyMuPDF: AGPL-3.0 or commercial license)
Repositorygithub.com/renoyuan/easyofd
DocumentationREADME and test/demo.py (Chinese)
Depends onReportLab, xmltodict, fontTools, PyMuPDF, loguru, Pillow
Read / writeyes / yes (OFD from PDF or images)

easyofd is a pure-Python OFD parser. OFD.read() turns a file into nested dicts and lists: for each page, text objects with their positions, images, lines and seals. From that data it draws a PDF with ReportLab, and renders the PDF to images with PyMuPDF. It also converts PDF files and images to OFD.

It handles typical Chinese e-invoices and simple documents well. Complex vector graphics, gradients and unusual fonts may render differently from a dedicated OFD reader. Versions are numbered by date (e.g. 20260427). Because of the PyMuPDF dependency, distributing a program that uses easyofd brings in AGPL obligations unless you have a PyMuPDF commercial license.

Installation#

Shell
pip install easyofd

On Linux, install Chinese fonts (for example fonts-noto-cjk or SimSun-compatible fonts); otherwise text drawn into PDF may show as boxes.

Basic usage: convert OFD to PDF and images#

read() accepts a file path, bytes, a BytesIO or a base64 string, selected with fmt:

Python
from easyofd.ofd import OFD

ofd = OFD()
ofd.read("invoice.ofd", fmt="path")   # also fmt="binary", "io", "b64" (default)

pdf_bytes = ofd.to_pdf()
with open("invoice.pdf", "wb") as f:
    f.write(pdf_bytes)

for i, img in enumerate(ofd.to_jpg()):  # list of PIL images, one per page
    img.save(f"invoice_{i}.jpg")

ofd.del_data()

How to extract text from an OFD invoice#

After read(), ofd.data is a list with one dict per document. page_info is a dict of page contents; each text item has the text and its box (x, y, width, height in millimeters):

Python
from easyofd.ofd import OFD

ofd = OFD()
ofd.read("invoice.ofd", fmt="path")

for doc in ofd.data:
    for page in doc["page_info"].values():
        items = sorted(page.get("text_list", []), key=lambda t: (t["pos"][1], t["pos"][0]))
        for item in items:
            x, y, w, h = item["pos"]
            print(f"{x:7.1f} {y:7.1f}  {item['text']}")

Sorting by position (top to bottom, then left to right) makes it practical to pick invoice fields by their location.

How to convert PDF to OFD#

Python
from easyofd.ofd import OFD

with open("in.pdf", "rb") as f:
    pdf_bytes = f.read()

ofd = OFD()
ofd_bytes = ofd.pdf2ofd(pdf_bytes, optional_text=True)  # True keeps selectable text from a text PDF
with open("out.ofd", "wb") as f:
    f.write(ofd_bytes)

How to create OFD from images#

Python
from PIL import Image
from easyofd.ofd import OFD

images = [Image.open("scan1.jpg"), Image.open("scan2.jpg")]
ofd_bytes = OFD().jpg2ofd(images)     # one page per image
with open("scans.ofd", "wb") as f:
    f.write(ofd_bytes)

OFD().jpg2pfd(images) (the method name has a typo) creates a PDF from the same images.

Tips#