Skip to content

Commit

Permalink
Merge pull request #4047 from t20100/update-font
Browse files Browse the repository at this point in the history
silx.gui.plot and plot3d: Updated font management
  • Loading branch information
vallsv authored Jan 17, 2024
2 parents 52ecb4d + 2b3438b commit 3bf7f76
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 287 deletions.
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ fabio >= 0.9
pyopencl; platform_machine in "i386, x86_64, AMD64" # For silx.opencl
Mako # For pyopencl reduction
qtconsole # For silx.gui.console
matplotlib >= 1.2.0 # For silx.gui.plot
matplotlib >= 3.1.0 # For silx.gui.plot
PyOpenGL # For silx.gui.plot3d
python-dateutil # For silx.gui.plot
scipy # For silx.math.fit demo, silx.image.sift demo, silx.image.sift.test
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ def get_project_configuration():
"Mako",
# gui
"qtconsole",
"matplotlib>=1.2.0",
"matplotlib>=3.1.0",
"PyOpenGL",
"python-dateutil",
"PyQt5",
Expand Down
165 changes: 2 additions & 163 deletions src/silx/gui/_glutils/font.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,178 +23,17 @@
# ###########################################################################*/
"""Text rasterisation feature leveraging Qt font and text layout support."""

from __future__ import annotations

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


import logging
import numpy

from .. import qt
from ..utils.image import convertQImageToArray

try:
from ..utils.matplotlib import rasterMathText
except ImportError:
rasterMathText = None

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


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


# Font weights
ULTRA_LIGHT = 0
"""Lightest characters: Minimum font weight"""

LIGHT = 25
"""Light characters"""

NORMAL = 50
"""Normal characters"""

SEMI_BOLD = 63
"""Between normal and bold characters"""

BOLD = 74
"""Thicker characters"""

BLACK = 87
"""Really thick characters"""

ULTRA_BLACK = 99
"""Thickest characters: Maximum font weight"""


def rasterTextQt(
text: str,
font: str | qt.QFont,
size: int = -1,
weight: int = -1,
italic: bool = False,
devicePixelRatio: float = 1.0,
) -> tuple[numpy.ndarray, int]:
"""Raster text using Qt.
It supports multiple lines.
:param text: The text to raster
:param font: Font name or QFont to use
:param size:
Font size in points
Used only if font is given as name.
:param weight:
Font weight in [0, 99], see QFont.Weight.
Used only if font is given as name.
:param italic:
True for italic font (default: False).
Used only if font is given as name.
:param devicePixelRatio:
The current ratio between device and device-independent pixel
(default: 1.0)
:return: Corresponding image in gray scale and baseline offset from top
:rtype: (HxW numpy.ndarray of uint8, int)
"""
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

if not isinstance(font, qt.QFont):
font = qt.QFont(font, size, weight, italic)

# get text size
image = qt.QImage(1, 1, qt.QImage.Format_Grayscale8)
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)

# 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 and handle devicePixelRatio
width = bounds.width() * devicePixelRatio + 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() * devicePixelRatio + 2),
qt.QImage.Format_Grayscale8,
)
image.setDevicePixelRatio(devicePixelRatio)
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, metrics.ascent()

min_row = max(0, filled_rows[0] - 1)
array = array[
min_row : filled_rows[-1] + 2,
max(0, filled_columns[0] - 1) : filled_columns[-1] + 2,
]

return array, metrics.ascent() - min_row


def rasterText(
text: str,
font: str | qt.QFont,
size: int = -1,
weight=-1,
italic: bool = False,
devicePixelRatio=1.0,
) -> tuple[numpy.ndarray, int]:
"""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 size:
Font size in points
Used only if font is given as name.
:param weight:
Font weight in [0, 99], see QFont.Weight.
Used only if font is given as name.
:param italic:
True for italic font (default: False).
Used only if font is given as name.
:param devicePixelRatio:
The current ratio between device and device-independent pixel
(default: 1.0)
:return: Corresponding image in gray scale and baseline offset from top
:rtype: (HxW numpy.ndarray of uint8, int)
"""
if rasterMathText is not None and text.count("$") >= 2:
return rasterMathText(text, font, size, weight, italic, devicePixelRatio)
else:
return rasterTextQt(text, font, size, weight, italic, devicePixelRatio)
2 changes: 1 addition & 1 deletion src/silx/gui/plot/backends/BackendOpenGL.py
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,7 @@ def _renderItems(self, overlay=False):
# Render marker labels
gl.glViewport(0, 0, self._plotFrame.size[0], self._plotFrame.size[1])
for label in labels:
label.render(self.matScreenProj)
label.render(self.matScreenProj, self._plotFrame.dotsPerInch)

def _renderOverlayGL(self):
"""Render overlay layer: overlay items and crosshair."""
Expand Down
42 changes: 24 additions & 18 deletions src/silx/gui/plot/backends/glutils/GLPlotFrame.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
This modules provides the rendering of plot titles, axes and grid.
"""

from __future__ import annotations

__authors__ = ["T. Vincent"]
__license__ = "MIT"
__date__ = "03/04/2017"
Expand Down Expand Up @@ -83,6 +85,7 @@ def __init__(
orderOffsetVAlign=CENTER,
titleRotate=0,
titleOffset=(0.0, 0.0),
font: qt.QFont | None = None,
):
self._tickFormatter = DefaultTickFormatter()
self._ticks = None
Expand All @@ -108,13 +111,20 @@ def __init__(
self._titleVAlign = titleVAlign
self._titleRotate = titleRotate
self._titleOffset = titleOffset
self._font = font

@property
def dataRange(self):
"""The range of the data represented on the axis as a tuple
of 2 floats: (min, max)."""
return self._dataRange

@property
def font(self) -> qt.QFont:
if self._font is None:
return qt.QApplication.instance().font()
return self._font

@dataRange.setter
def dataRange(self, dataRange):
assert len(dataRange) == 2
Expand Down Expand Up @@ -252,9 +262,6 @@ def getVerticesAndLabels(self):
"""
vertices = list(self.displayCoords) # Add start and end points
labels = []
tickLabelsSize = [0.0, 0.0]

font = qt.QApplication.instance().font()

xTickLength, yTickLength = self._tickLength
xTickLength *= self.devicePixelRatio
Expand All @@ -267,21 +274,14 @@ def getVerticesAndLabels(self):

label = Text2D(
text=text,
font=font,
font=self.font,
color=self._foregroundColor,
x=xPixel - xTickLength,
y=yPixel - yTickLength,
align=self._labelAlign,
valign=self._labelVAlign,
devicePixelRatio=self.devicePixelRatio,
)

width, height = label.size
if width > tickLabelsSize[0]:
tickLabelsSize[0] = width
if height > tickLabelsSize[1]:
tickLabelsSize[1] = height

labels.append(label)

vertices.append((xPixel, yPixel))
Expand All @@ -304,7 +304,7 @@ def getVerticesAndLabels(self):

axisTitle = Text2D(
text=self.title,
font=font,
font=self.font,
color=self._foregroundColor,
x=xAxisCenter + xOffset,
y=yAxisCenter + yOffset,
Expand All @@ -320,7 +320,7 @@ def getVerticesAndLabels(self):
labels.append(
Text2D(
text=self._orderAndOffsetText,
font=font,
font=self.font,
color=self._foregroundColor,
x=xOrderOffset,
y=yOrderOffet,
Expand Down Expand Up @@ -781,7 +781,7 @@ def render(self):
gl.glDrawArrays(gl.GL_LINES, 0, len(vertices))

for label in labels:
label.render(matProj)
label.render(matProj, self.dotsPerInch)

def renderGrid(self):
if self._grid == self.GRID_NONE:
Expand Down Expand Up @@ -829,7 +829,11 @@ def __init__(self, marginRatios, foregroundColor, gridColor, font: qt.QFont):
:type gridColor: tuple RGBA with RGBA values ranging from 0.0 to 1.0
:param font: Font used by the axes label
"""
super(GLPlotFrame2D, self).__init__(marginRatios, foregroundColor, gridColor, font)
super(GLPlotFrame2D, self).__init__(
marginRatios, foregroundColor, gridColor, font
)
self._font = font

self.axes.append(
PlotAxis(
self,
Expand All @@ -842,6 +846,7 @@ def __init__(self, marginRatios, foregroundColor, gridColor, font: qt.QFont):
titleAlign=CENTER,
titleVAlign=TOP,
titleRotate=0,
font=self._font,
)
)

Expand All @@ -859,6 +864,7 @@ def __init__(self, marginRatios, foregroundColor, gridColor, font: qt.QFont):
titleAlign=CENTER,
titleVAlign=BOTTOM,
titleRotate=ROTATE_270,
font=self._font,
)
)

Expand All @@ -873,6 +879,7 @@ def __init__(self, marginRatios, foregroundColor, gridColor, font: qt.QFont):
titleAlign=CENTER,
titleVAlign=TOP,
titleRotate=ROTATE_270,
font=self._font,
)

self._isYAxisInverted = False
Expand Down Expand Up @@ -1330,10 +1337,9 @@ def _buildVerticesAndLabels(self):
self._x2AxisCoords = ((xCoords[0], yCoords[1]), (xCoords[1], yCoords[1]))

# Set order&offset anchor **before** handling Y axis inversion
font = qt.QApplication.instance().font()
fontPixelSize = font.pixelSize()
fontPixelSize = self._font.pixelSize()
if fontPixelSize == -1:
fontPixelSize = font.pointSizeF() / 72.0 * self.dotsPerInch
fontPixelSize = self._font.pointSizeF() / 72.0 * self.dotsPerInch

self.axes[0].orderOffetAnchor = (
xCoords[1],
Expand Down
Loading

0 comments on commit 3bf7f76

Please sign in to comment.