在PySide6中使用TextObject绘制LaTeX图像

人工智能 2026-07-10

我想在一个pyside6应用中使用TextObjectInterface绘制渲染后的LaTeX,其中对象以TextObject存储,LaTeX在其上绘制。我希望避免TextImage,因为它可以工作,但在删除旧的LaTeX公式并添加新公式时,资源缓存中的bmp会累积。下面的代码不能在LaTeX图像前后只生成文本,图像替换处没有显示任何内容。我已经安装了TeX发行版。使用TextImage时一切都正常,也没有错误信息。我正在使用Python 3.13

import sys
from io import BytesIO
from PySide6.QtWidgets import QApplication, QTextEdit
from PySide6.QtGui import (
    QTextObjectInterface,
    QTextFormat,
    QTextCharFormat,
    QImage,
    QPainter,
    QColor
)
from PySide6.QtCore import QSizeF, QObject, Qt

from matplotlib.figure import Figure
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas

LATEX_FORMAT = QTextFormat.UserObject + 1

class LatexRenderer(QObject, QTextObjectInterface):
    def __init__(self):
        super().__init__()
        self._cache = {}

    def intrinsicSize(self, doc, posInDocument, format):
        latex = format.property(1)
        img = self.render_latex(latex)
        return QSizeF(img.width(), img.height())

    def drawObject(self, painter, rect, doc, posInDocument, format):
        latex = format.property(1)
        img = self.render_latex(latex)

        # DEBUG: Draw a light gray background so we can see the "box"
        painter.fillRect(rect, QColor("#f0f0f0"))

        painter.drawImage(rect.topLeft(), img)

    def render_latex(self, latex):
        if latex in self._cache:       
            return self._cache[latex]

        # Force a specific DPI and small figure
        fig = Figure(figsize=(1, 1), dpi=100)
        canvas = FigureCanvas(fig)
        fig.patch.set_alpha(0) 

        # Using 'mathtext' specifically
        fig.text(0.5, 0.5, f"${latex}$", 
                 fontsize=20, 
                 ha='center', 
                 va='center', 
                 color="black")

        buf = BytesIO()
        # 'tight' is essential, but let's add a small pad to be safe
        fig.savefig(buf, format='png', bbox_inches='tight', pad_inches=0.1, transparent=True)

        img = QImage()
        img.loadFromData(buf.getvalue(), "PNG")

        self._cache[latex] = img
        return img

class MainWindow(QTextEdit):
    def __init__(self):
        super().__init__()

        # 1. Register handler BEFORE adding text
        self.renderer = LatexRenderer()
        self.document().documentLayout().registerHandler(LATEX_FORMAT, self.renderer)

        # 2. Insert text and math
        cursor = self.textCursor()
        cursor.insertText("The Pythagorean theorem is: ")

        # Use a RAW string for LaTeX
        self.insert_latex(cursor, r"a^2 + b^2 = c^2")

        cursor.insertText("\n\nAnd the Quadratic formula: ")
        self.insert_latex(cursor, r"x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}")

    def insert_latex(self, cursor, latex):
        fmt = QTextCharFormat()
        fmt.setObjectType(LATEX_FORMAT)
        fmt.setProperty(1, latex)
        cursor.insertText("\uFFFC", fmt)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    # Removing the deprecated HighDPI line as it's now default
    w = MainWindow()
    w.resize(600, 400)
    w.show()
    sys.exit(app.exec())

我希望程序能在屏幕上显示渲染后的公式,例如:

a^2 + b^2 = c^2

但没有任何显示。我使用QTextObject将 LaTeX图像直接绘制在包含数学表达式的对象上。

新编辑:

我需要Python 3.13以用于Gemini API功能,因此我不能使用PyQt6,因为它无法安装。

请推荐最适合用于QTextObject与 Gemini API的 Python版本。

编辑: 我不知怎的安装了PyQt6,执行命令pip install PyQt6 --only-binary=:all,然后运行PyQt6的版本,工作了。猜测PySide的版本有bug。

编辑: 我正在使用Ubuntu 20.04 LTS(懒得升级Ubuntu)。

解决方案

Code works correctly with PyQt6 but it has some problem with PySide6
but it doesn't show any error messages.

不过正如 @musicamante所建议的,它在 PySide6QPyTextObject 下可以工作

class LatexRenderer(QPyTextObject):

文档:QPyTextObject

也使用 QPyTextObject 的示例:TextObject Example - Qt for Python

针对 QPyTextObject 的C++源代码 qpytextobject.h, qpytextobject.cpp
但没有什么特别之处。


在以下环境测试

Python: 3.13.12
PySide: 6.11.0
Qt: 6.11.0
LatexRenderer: ['QPyTextObject']

Linux Mint 22 (with Mate)

用于测试的完整代码:

还有针对 genai 的代码,因为我在测试它是否能在其他版本的Python上运行。

# --- info ---

import sys
from PySide6.QtCore import __version__, qVersion

print("Python:", sys.version)
print("PySide:", __version__)
print("Qt:", qVersion())

# ---

import sys
from io import BytesIO
from PySide6.QtWidgets import QApplication, QTextEdit
from PySide6.QtGui import (
    QTextObjectInterface,
    QTextFormat,
    QTextCharFormat,
    QImage,
    QPainter,
    QColor,
)
from PySide6.QtCore import QSizeF, QObject, Qt

# from PySide6.QtGui import QTextObject
from PySide6.QtGui import QPyTextObject


from matplotlib.figure import Figure
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas

LATEX_FORMAT = QTextFormat.ObjectTypes.UserObject + 1


# class LatexRenderer(QObject, QTextObjectInterface):  # run but not display
# class LatexRenderer(QTextObjectInterface):  # raise error during registration
# class LatexRenderer(QTextObject):  # raise error because QTextObject needs some parameter
class LatexRenderer(QPyTextObject):  # it works!
    def __init__(self):
        print("LatexRenderer:", [x.__name__ for x in self.__class__.__bases__])
        super().__init__()
        self._cache = {}

    def intrinsicSize(self, doc, posInDocument, format):
        # print("intrinsicSize")
        latex = format.property(1)
        img = self.render_latex(latex)
        # print("size:", img.width(), img.height())
        return QSizeF(img.width(), img.height())

    def drawObject(self, painter, rect, doc, posInDocument, format):
        # print("drawObject")
        latex = format.property(1)
        img = self.render_latex(latex)

        # DEBUG: Draw a light gray background so we can see the "box"
        painter.fillRect(rect, QColor("#f0f0f0"))

        painter.drawImage(rect.topLeft(), img)

    def render_latex(self, latex):
        # print("render_latex")
        if latex in self._cache:
            return self._cache[latex]

        # Force a specific DPI and small figure
        fig = Figure(figsize=(1, 1), dpi=100)
        canvas = FigureCanvas(fig)
        fig.patch.set_alpha(0)

        # Using 'mathtext' specifically
        fig.text(
            0.5, 0.5, f"${latex}$", fontsize=20, ha="center", va="center", color="black"
        )

        buf = BytesIO()
        # 'tight' is essential, but let's add a small pad to be safe
        fig.savefig(
            buf, format="png", bbox_inches="tight", pad_inches=0.1, transparent=True
        )

        img = QImage()
        img.loadFromData(buf.getvalue(), "PNG")

        self._cache[latex] = img
        return img


class MainWindow(QTextEdit):
    def __init__(self):
        super().__init__()

        # 1. Register handler BEFORE adding text
        self.renderer = LatexRenderer()
        self.document().documentLayout().registerHandler(LATEX_FORMAT, self.renderer)

        # 2. Insert text and math
        cursor = self.textCursor()
        cursor.insertText("The Pythagorean theorem is: ")

        # Use a RAW string for LaTeX
        self.insert_latex(cursor, r"a^2 + b^2 = c^2")

        cursor.insertText("\n\nAnd the Quadratic formula: ")
        self.insert_latex(cursor, r"x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}")

    def insert_latex(self, cursor, latex):
        # print("insert_latex:", cursor, latex)
        fmt = QTextCharFormat()
        fmt.setObjectType(LATEX_FORMAT)
        fmt.setProperty(1, latex)
        cursor.insertText("\ufffc", fmt)


def API():
    from google import genai

    try:
        print("=== GenAI ===")

        client = genai.Client()

        question = "Explain how AI works in a few words"
        print("Question:", question)

        response = client.models.generate_content(
            model="gemini-3-flash-preview",
            contents=question,
        )

        answer = response.text
        print("Answer:", answer)
    except Exception as ex:
        print("Ex:", ex)


if __name__ == "__main__":
    # API()

    app = QApplication(sys.argv)
    # Removing the deprecated HighDPI line as it's now default
    w = MainWindow()
    w.resize(600, 400)
    w.show()
    sys.exit(app.exec())
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章