Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

silx.gui: Updated OpenGL text rendering to use Qt when possible #4186

Merged
merged 2 commits into from
Oct 23, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 113 additions & 3 deletions src/silx/gui/_glutils/font.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# /*##########################################################################
#
# Copyright (c) 2016-2023 European Synchrotron Radiation Facility
# Copyright (c) 2016-2024 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
Expand All @@ -21,19 +21,129 @@
# THE SOFTWARE.
#
# ###########################################################################*/
from __future__ import annotations
"""Text rasterisation feature leveraging Qt font and text layout support."""

__authors__ = ["T. Vincent"]
__license__ = "MIT"
__date__ = "13/10/2016"


import logging
import numpy

from .. import qt
from ..utils.image import convertQImageToArray
from ..utils.matplotlib import rasterMathText


# Expose rasterMathText as part of this module
from ..utils.matplotlib import rasterMathText as rasterText # noqa
_logger = logging.getLogger(__name__)


def getDefaultFontFamily() -> str:
"""Returns the default font family of the application"""
return qt.QApplication.instance().font().family()


def rasterTextQt(
text: str,
font: qt.QFont,
dotsPerInch: float = 96.0,
) -> tuple[numpy.ndarray, float]:
"""Raster text using Qt.

It supports multiple lines.

:param text: The text to raster
:param font: Font to use
:param dotsPerInch: The DPI resolution of the created image
:return: Corresponding image in gray scale and baseline offset from top
"""
if not text:
_logger.info("Trying to raster empty text, replaced by white space")
text = " " # Replace empty text by white space to produce an image

dotsPerMeter = int(dotsPerInch * 100 / 2.54)

# get text size
image = qt.QImage(1, 1, qt.QImage.Format_Grayscale8)
image.setDotsPerMeterX(dotsPerMeter)
image.setDotsPerMeterY(dotsPerMeter)

painter = qt.QPainter()
painter.begin(image)
painter.setPen(qt.Qt.white)
painter.setFont(font)
bounds = painter.boundingRect(
qt.QRect(0, 0, 4096, 4096), qt.Qt.TextExpandTabs, text
)
painter.end()

metrics = qt.QFontMetrics(font)
offset = metrics.ascent() / 72.0 * dotsPerInch

# This does not provide the correct text bbox on macOS
# size = metrics.size(qt.Qt.TextExpandTabs, text)
# bounds = metrics.boundingRect(
# qt.QRect(0, 0, size.width(), size.height()),
# qt.Qt.TextExpandTabs,
# text)

# Add extra border
width = bounds.width() + 2
# align line size to 32 bits to ease conversion to numpy array
width = 4 * ((width + 3) // 4)
image = qt.QImage(
int(width),
int(bounds.height() + 2),
qt.QImage.Format_Grayscale8,
)
image.setDotsPerMeterX(dotsPerMeter)
image.setDotsPerMeterY(dotsPerMeter)
image.fill(0)

# Raster text
painter = qt.QPainter()
painter.begin(image)
painter.setPen(qt.Qt.white)
painter.setFont(font)
painter.drawText(bounds, qt.Qt.TextExpandTabs, text)
painter.end()

array = convertQImageToArray(image)

# Remove leading and trailing empty columns/rows but one on each side
filled_rows = numpy.nonzero(numpy.sum(array, axis=1))[0]
filled_columns = numpy.nonzero(numpy.sum(array, axis=0))[0]

if len(filled_rows) == 0 or len(filled_columns) == 0:
return array, offset
return (
numpy.ascontiguousarray(
array[
0 : filled_rows[-1] + 2,
max(0, filled_columns[0] - 1) : filled_columns[-1] + 2,
]
),
offset,
)


def rasterText(
text: str,
font: qt.QFont,
dotsPerInch: float = 96.0,
) -> tuple[numpy.ndarray, float]:
"""Raster text using Qt or matplotlib if there may be math syntax.

It supports multiple lines.

:param text: The text to raster
:param font: Font name or QFont to use
:param dotsPerInch: Created image resolution
:return: Corresponding image in gray scale and baseline offset from top
"""

if text.count("$") >= 2:
return rasterMathText(text, font, dotsPerInch)
return rasterTextQt(text, font, dotsPerInch)