From 2d66328a72f7080e7cbf1175cf44fcdbd6ec91a6 Mon Sep 17 00:00:00 2001 From: kumattau Date: Fri, 28 Jan 2022 23:58:40 +0900 Subject: [PATCH 1/9] Use QRawFont and draw QPainterPath of glyph when animation --- qtawesome/iconic_font.py | 61 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/qtawesome/iconic_font.py b/qtawesome/iconic_font.py index e0724063..20c8d962 100644 --- a/qtawesome/iconic_font.py +++ b/qtawesome/iconic_font.py @@ -22,9 +22,10 @@ import warnings # Third party imports -from qtpy.QtCore import QByteArray, QObject, QPoint, QRect, Qt +from qtpy.QtCore import (QByteArray, QObject, QPoint, QRect, Qt, + QSizeF, QMarginsF, QRectF) from qtpy.QtGui import (QColor, QFont, QFontDatabase, QIcon, QIconEngine, - QPainter, QPixmap, QTransform, QPalette) + QPainter, QPixmap, QTransform, QPalette, QRawFont) from qtpy.QtWidgets import QApplication # Linux packagers, please set this to True if you want to make qtawesome @@ -184,6 +185,7 @@ def _paint_icon(self, iconic, painter, rect, mode, state, options): if animation is not None: font.setHintingPreference(QFont.PreferNoHinting) painter.setFont(font) + if 'offset' in options: rect = QRect(rect) rect.translate(round(options['offset'][0] * rect.width()), @@ -216,7 +218,47 @@ def _paint_icon(self, iconic, painter, rect, mode, state, options): painter.setOpacity(options.get('opacity', 1.0)) - painter.drawText(rect, int(Qt.AlignCenter | Qt.AlignVCenter), char) + draw = options.get('draw', 'auto') + if draw == 'text': + draw_path = False + elif draw == 'path': + draw_path = True + else: + # Use QPainterPath when animation + # to fix tremulous spinning icons #39 + draw_path = animation is not None + + if draw_path: + if char not in iconic.path_cache[prefix]: + rawfont = iconic.rawfont[prefix] + glyph = rawfont.glyphIndexesForString(char)[0] + flag = QRawFont.SeparateAdvances | QRawFont.UseDesignMetrics + advance = rawfont.advancesForGlyphIndexes((glyph,), flag)[0] + path = rawfont.pathForGlyph(glyph) + path.translate(0, rawfont.ascent()) + size = QSizeF(abs(advance.x()), rawfont.ascent() + rawfont.descent()) + iconic.path_cache[prefix][char] = (path, size) + + path, size = iconic.path_cache[prefix][char] + + margin = (rect.height() - draw_size) / 2 + rect = QRectF(rect).marginsRemoved(QMarginsF(*((margin,) * 4))) + + w, h = rect.width(), rect.height() + W, H = size.width(), size.height() + + scale = min(w / W, h / H) + painter.translate(rect.x() + (w - W * scale) / 2, rect.y() + (h - H * scale) / 2) + painter.scale(scale, scale) + + painter.setRenderHint(QPainter.Antialiasing, + painter.renderHints() & QPainter.TextAntialiasing) + painter.fillPath(path, painter.pen().color()) + + else: + painter.setFont(iconic.font(prefix, draw_size)) + painter.drawText(rect, int(Qt.AlignCenter | Qt.AlignVCenter), char) + painter.restore() @@ -269,6 +311,8 @@ def __init__(self, *args): self.fontname = {} self.fontids = {} self.charmap = {} + self.rawfont = {} + self.path_cache = {} self.icon_cache = {} for fargs in args: self.load_font(*fargs) @@ -312,7 +356,14 @@ def hook(obj): # Load font if QApplication.instance() is not None: with open(os.path.join(directory, ttf_filename), 'rb') as font_data: - id_ = QFontDatabase.addApplicationFontFromData(QByteArray(font_data.read())) + data = font_data.read() + id_ = QFontDatabase.addApplicationFontFromData(data) + # QRawFont() requires pixelSize, but it is unknown at this time. + # So, first create QRowfont with 2048, which is the size of + # almost TrueType fonts and reset actual size after that. + # Note: When TrueType fonts, pixelSize can be specified up to 16384. + rawfont = QRawFont(data, 2048) + rawfont.setPixelSize(rawfont.unitsPerEm()) font_data.close() loadedFontFamilies = QFontDatabase.applicationFontFamilies(id_) @@ -320,6 +371,8 @@ def hook(obj): if loadedFontFamilies: self.fontids[prefix] = id_ self.fontname[prefix] = loadedFontFamilies[0] + self.rawfont[prefix] = rawfont + self.path_cache[prefix] = {} else: raise FontError(u"Font at '{0}' appears to be empty. " "If you are on Windows 10, please read " From a198a998a2cd28ca3ba1bb8289bcd30dfd75f45e Mon Sep 17 00:00:00 2001 From: kumattau Date: Fri, 28 Jan 2022 23:58:53 +0900 Subject: [PATCH 2/9] Add an example for draw option --- example.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/example.py b/example.py index 5424d4ac..1512f058 100644 --- a/example.py +++ b/example.py @@ -114,6 +114,14 @@ def __init__(self): {'scale_factor': 0.8}]) saveall_button = QtWidgets.QPushButton(saveall, 'Stack, offset') + # Stack and offset icons, draw path + saveall_path = qta.icon('fa5.save', 'fa5.save', + options=[{'scale_factor': 0.8, + 'offset': (0.2, 0.2), + 'color': 'gray', 'draw': 'path'}, + {'scale_factor': 0.8, 'draw': 'path'}]) + saveall_button_path = QtWidgets.QPushButton(saveall_path, 'Stack, offset, draw path') + # Spin icons spin_button = QtWidgets.QPushButton(' Spinning icon') spin_icon = qta.icon('fa5s.spinner', color='red', @@ -167,6 +175,7 @@ def __init__(self): pulse_button, stack_button, saveall_button, + saveall_button_path, stack_spin_button, ] other_widgets = [ From 703646bfe708ee5352350e6ccf06e17b6bcc8ac8 Mon Sep 17 00:00:00 2001 From: kumattau Date: Mon, 31 Jan 2022 22:05:54 +0900 Subject: [PATCH 3/9] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré --- qtawesome/iconic_font.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/qtawesome/iconic_font.py b/qtawesome/iconic_font.py index 20c8d962..eb006888 100644 --- a/qtawesome/iconic_font.py +++ b/qtawesome/iconic_font.py @@ -224,8 +224,9 @@ def _paint_icon(self, iconic, painter, rect, mode, state, options): elif draw == 'path': draw_path = True else: - # Use QPainterPath when animation - # to fix tremulous spinning icons #39 + # Use QPainterPath when setting an animation + # to fix tremulous spinning icons. + # See #39 draw_path = animation is not None if draw_path: @@ -358,10 +359,10 @@ def hook(obj): with open(os.path.join(directory, ttf_filename), 'rb') as font_data: data = font_data.read() id_ = QFontDatabase.addApplicationFontFromData(data) - # QRawFont() requires pixelSize, but it is unknown at this time. - # So, first create QRowfont with 2048, which is the size of - # almost TrueType fonts and reset actual size after that. - # Note: When TrueType fonts, pixelSize can be specified up to 16384. + # `QRawFont()` requires a `pixelSize` value, but it is unknown at this point. + # To workaround that, first we create a `QRawFont` with 2048 as `pixelSize` + # (usual size for TrueType fonts) and reset to the actual size after that. + # Note: For TrueType fonts, `pixelSize` can be specified up to 16384. rawfont = QRawFont(data, 2048) rawfont.setPixelSize(rawfont.unitsPerEm()) font_data.close() From 7eb4ac5fc22e4a871bbf87e36e41990a36e288d6 Mon Sep 17 00:00:00 2001 From: kumattau Date: Tue, 1 Feb 2022 22:05:47 +0900 Subject: [PATCH 4/9] Use a drawPath based on draw_size to match the drawText behavior --- qtawesome/iconic_font.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/qtawesome/iconic_font.py b/qtawesome/iconic_font.py index eb006888..667391eb 100644 --- a/qtawesome/iconic_font.py +++ b/qtawesome/iconic_font.py @@ -23,7 +23,7 @@ # Third party imports from qtpy.QtCore import (QByteArray, QObject, QPoint, QRect, Qt, - QSizeF, QMarginsF, QRectF) + QSizeF, QRectF) from qtpy.QtGui import (QColor, QFont, QFontDatabase, QIcon, QIconEngine, QPainter, QPixmap, QTransform, QPalette, QRawFont) from qtpy.QtWidgets import QApplication @@ -230,27 +230,22 @@ def _paint_icon(self, iconic, painter, rect, mode, state, options): draw_path = animation is not None if draw_path: - if char not in iconic.path_cache[prefix]: - rawfont = iconic.rawfont[prefix] + if (draw_size, char) not in iconic.path_cache[prefix]: + rawfont = QRawFont(iconic.rawfont[prefix]) + rawfont.setPixelSize(draw_size) glyph = rawfont.glyphIndexesForString(char)[0] flag = QRawFont.SeparateAdvances | QRawFont.UseDesignMetrics advance = rawfont.advancesForGlyphIndexes((glyph,), flag)[0] path = rawfont.pathForGlyph(glyph) path.translate(0, rawfont.ascent()) + path.setFillRule(Qt.WindingFill) size = QSizeF(abs(advance.x()), rawfont.ascent() + rawfont.descent()) - iconic.path_cache[prefix][char] = (path, size) + iconic.path_cache[prefix][(draw_size, char)] = (path, size) - path, size = iconic.path_cache[prefix][char] + path, size = iconic.path_cache[prefix][(draw_size, char)] - margin = (rect.height() - draw_size) / 2 - rect = QRectF(rect).marginsRemoved(QMarginsF(*((margin,) * 4))) - - w, h = rect.width(), rect.height() - W, H = size.width(), size.height() - - scale = min(w / W, h / H) - painter.translate(rect.x() + (w - W * scale) / 2, rect.y() + (h - H * scale) / 2) - painter.scale(scale, scale) + painter.translate(QRectF(rect).center()) + painter.translate(-size.width() / 2, -size.height() / 2) painter.setRenderHint(QPainter.Antialiasing, painter.renderHints() & QPainter.TextAntialiasing) @@ -360,11 +355,10 @@ def hook(obj): data = font_data.read() id_ = QFontDatabase.addApplicationFontFromData(data) # `QRawFont()` requires a `pixelSize` value, but it is unknown at this point. - # To workaround that, first we create a `QRawFont` with 2048 as `pixelSize` + # To workaround that, first we create a `QRawFont` with 2048 as `pixelSize` # (usual size for TrueType fonts) and reset to the actual size after that. # Note: For TrueType fonts, `pixelSize` can be specified up to 16384. rawfont = QRawFont(data, 2048) - rawfont.setPixelSize(rawfont.unitsPerEm()) font_data.close() loadedFontFamilies = QFontDatabase.applicationFontFamilies(id_) From c15d07db936390553524380f4c5da08bc827cd84 Mon Sep 17 00:00:00 2001 From: kumattau Date: Wed, 2 Feb 2022 02:22:42 +0900 Subject: [PATCH 5/9] Test alphaMapForGlyph and drawImage --- example.py | 9 ++++++ qtawesome/iconic_font.py | 61 +++++++++++++++++++++------------------- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/example.py b/example.py index 1512f058..a4c699f3 100644 --- a/example.py +++ b/example.py @@ -114,6 +114,14 @@ def __init__(self): {'scale_factor': 0.8}]) saveall_button = QtWidgets.QPushButton(saveall, 'Stack, offset') + # Stack and offset icons, draw image + saveall_image = qta.icon('fa5.save', 'fa5.save', + options=[{'scale_factor': 0.8, + 'offset': (0.2, 0.2), + 'color': 'gray', 'draw': 'image'}, + {'scale_factor': 0.8, 'draw': 'image'}]) + saveall_button_image = QtWidgets.QPushButton(saveall_image, 'Stack, offset, draw image') + # Stack and offset icons, draw path saveall_path = qta.icon('fa5.save', 'fa5.save', options=[{'scale_factor': 0.8, @@ -175,6 +183,7 @@ def __init__(self): pulse_button, stack_button, saveall_button, + saveall_button_image, saveall_button_path, stack_spin_button, ] diff --git a/qtawesome/iconic_font.py b/qtawesome/iconic_font.py index 667391eb..2391dccd 100644 --- a/qtawesome/iconic_font.py +++ b/qtawesome/iconic_font.py @@ -25,7 +25,8 @@ from qtpy.QtCore import (QByteArray, QObject, QPoint, QRect, Qt, QSizeF, QRectF) from qtpy.QtGui import (QColor, QFont, QFontDatabase, QIcon, QIconEngine, - QPainter, QPixmap, QTransform, QPalette, QRawFont) + QPainter, QPixmap, QTransform, QPalette, QRawFont, + QImage) from qtpy.QtWidgets import QApplication # Linux packagers, please set this to True if you want to make qtawesome @@ -191,45 +192,30 @@ def _paint_icon(self, iconic, painter, rect, mode, state, options): rect.translate(round(options['offset'][0] * rect.width()), round(options['offset'][1] * rect.height())) + x_center = rect.width() * 0.5 + y_center = rect.height() * 0.5 + transform = QTransform() + transform.translate(+x_center, +y_center) if 'vflip' in options and options['vflip'] == True: - x_center = rect.width() * 0.5 - y_center = rect.height() * 0.5 - painter.translate(x_center, y_center) - transfrom = QTransform() - transfrom.scale(1,-1) - painter.setTransform(transfrom, True) - painter.translate(-x_center, -y_center) - + transform.scale(1,-1) if 'hflip' in options and options['hflip'] == True: - x_center = rect.width() * 0.5 - y_center = rect.height() * 0.5 - painter.translate(x_center, y_center) - transfrom = QTransform() - transfrom.scale(-1, 1) - painter.setTransform(transfrom, True) - painter.translate(-x_center, -y_center) - + transform.scale(-1, 1) if 'rotated' in options: - x_center = rect.width() * 0.5 - y_center = rect.height() * 0.5 - painter.translate(x_center, y_center) - painter.rotate(options['rotated']) - painter.translate(-x_center, -y_center) + transform.rotate(options['rotated']) + transform.translate(-x_center, -y_center) painter.setOpacity(options.get('opacity', 1.0)) draw = options.get('draw', 'auto') - if draw == 'text': - draw_path = False - elif draw == 'path': - draw_path = True - else: + if draw not in ('text', 'path', 'image', 'auto'): + draw = 'auto' + if draw == 'auto': # Use QPainterPath when setting an animation # to fix tremulous spinning icons. # See #39 - draw_path = animation is not None + draw = 'path' if animation is not None else 'text' - if draw_path: + if draw == 'path': if (draw_size, char) not in iconic.path_cache[prefix]: rawfont = QRawFont(iconic.rawfont[prefix]) rawfont.setPixelSize(draw_size) @@ -244,6 +230,7 @@ def _paint_icon(self, iconic, painter, rect, mode, state, options): path, size = iconic.path_cache[prefix][(draw_size, char)] + painter.setTransform(transform, True) painter.translate(QRectF(rect).center()) painter.translate(-size.width() / 2, -size.height() / 2) @@ -251,7 +238,23 @@ def _paint_icon(self, iconic, painter, rect, mode, state, options): painter.renderHints() & QPainter.TextAntialiasing) painter.fillPath(path, painter.pen().color()) + elif draw == 'image': + rawfont = QRawFont(iconic.rawfont[prefix]) + rawfont.setPixelSize(draw_size) + glyph = rawfont.glyphIndexesForString(char)[0] + alpha = rawfont.alphaMapForGlyph(glyph, transform=transform) + size = alpha.size() + image = QImage(size, QImage.Format_RGBA8888) + image.fill(painter.pen().color()) + image.setAlphaChannel(alpha) + + painter.translate(QRectF(rect).center()) + painter.translate(-size.width() / 2, -size.height() / 2) + + painter.drawImage(QPoint(0, 0), image) + else: + painter.setTransform(transform, True) painter.setFont(iconic.font(prefix, draw_size)) painter.drawText(rect, int(Qt.AlignCenter | Qt.AlignVCenter), char) From ca700f766586729026c8684868e279348b7e1da8 Mon Sep 17 00:00:00 2001 From: kumattau Date: Wed, 2 Feb 2022 03:16:59 +0900 Subject: [PATCH 6/9] Fix setAlphaChannel AttributeError on PyQt 5.9 --- qtawesome/iconic_font.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/qtawesome/iconic_font.py b/qtawesome/iconic_font.py index 2391dccd..6f6c18a5 100644 --- a/qtawesome/iconic_font.py +++ b/qtawesome/iconic_font.py @@ -246,7 +246,15 @@ def _paint_icon(self, iconic, painter, rect, mode, state, options): size = alpha.size() image = QImage(size, QImage.Format_RGBA8888) image.fill(painter.pen().color()) - image.setAlphaChannel(alpha) + try: + image.setAlphaChannel(alpha) + except AttributeError: + a = alpha.convertToFormat(QImage.Format_Grayscale8) + for i in range(size.width()): + for j in range(size.height()): + c = image.pixelColor(i, j) + c.setAlpha(a.pixelColor(i, j).red()) + image.setPixelColor(i, j, c) painter.translate(QRectF(rect).center()) painter.translate(-size.width() / 2, -size.height() / 2) From 81fc7f4460c80d23ffd08d0c1590fcde84c7396a Mon Sep 17 00:00:00 2001 From: kumattau Date: Fri, 14 Oct 2022 06:22:00 +0900 Subject: [PATCH 7/9] Passing arguments to global_defaults for test --- example.py | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/example.py b/example.py index a4c699f3..99ddc072 100644 --- a/example.py +++ b/example.py @@ -114,22 +114,6 @@ def __init__(self): {'scale_factor': 0.8}]) saveall_button = QtWidgets.QPushButton(saveall, 'Stack, offset') - # Stack and offset icons, draw image - saveall_image = qta.icon('fa5.save', 'fa5.save', - options=[{'scale_factor': 0.8, - 'offset': (0.2, 0.2), - 'color': 'gray', 'draw': 'image'}, - {'scale_factor': 0.8, 'draw': 'image'}]) - saveall_button_image = QtWidgets.QPushButton(saveall_image, 'Stack, offset, draw image') - - # Stack and offset icons, draw path - saveall_path = qta.icon('fa5.save', 'fa5.save', - options=[{'scale_factor': 0.8, - 'offset': (0.2, 0.2), - 'color': 'gray', 'draw': 'path'}, - {'scale_factor': 0.8, 'draw': 'path'}]) - saveall_button_path = QtWidgets.QPushButton(saveall_path, 'Stack, offset, draw path') - # Spin icons spin_button = QtWidgets.QPushButton(' Spinning icon') spin_icon = qta.icon('fa5s.spinner', color='red', @@ -183,8 +167,6 @@ def __init__(self): pulse_button, stack_button, saveall_button, - saveall_button_image, - saveall_button_path, stack_spin_button, ] other_widgets = [ @@ -198,20 +180,36 @@ def __init__(self): for idx, w in enumerate(styled_widgets): grid.addWidget(w, idx, 1) - + for idx, w in enumerate(animated_widgets): grid.addWidget(w, idx + len(styled_widgets), 1) - + for idx, w in enumerate(other_widgets): grid.addWidget(w, idx + len(styled_widgets) + len(animated_widgets), 1) + title = 'Awesome' + args = ' '.join(sys.argv[1:]).strip() + if args: + title += ' (' + args + ')' + self.setLayout(grid) - self.setWindowTitle('Awesome') + self.setWindowTitle(title) self.setMinimumWidth(520) self.show() def main(): + + global_defaults = {} + for arg in sys.argv[1:]: + try: + key, val = arg.split('=', maxsplit=1) + global_defaults[key] = val + except: + pass + if global_defaults: + qta.set_global_defaults(**global_defaults) + app = QtWidgets.QApplication(sys.argv) # Enable High DPI display with PyQt5 From 0dc4226a725c10c53eea18664ec36444192d5db2 Mon Sep 17 00:00:00 2001 From: kumattau Date: Fri, 14 Oct 2022 06:22:00 +0900 Subject: [PATCH 8/9] Some Updates * Fix QRawFont to be thread-local * Add public iconic.rawfont() same as iconic.font() * Use setCompositionMode instead of setAlphaChannel which is not supported on PyQt 5.9 * Add glyphrun to draw option * Add draw option in global_defaults * Add Fallback to text drawing if path/glyphrun/image drawing cannot be used * Refectoring CharIconPainter._paint_icon() --- qtawesome/iconic_font.py | 151 +++++++++++++++++++++------------------ 1 file changed, 82 insertions(+), 69 deletions(-) diff --git a/qtawesome/iconic_font.py b/qtawesome/iconic_font.py index 6f6c18a5..670b0cad 100644 --- a/qtawesome/iconic_font.py +++ b/qtawesome/iconic_font.py @@ -23,10 +23,10 @@ # Third party imports from qtpy.QtCore import (QByteArray, QObject, QPoint, QRect, Qt, - QSizeF, QRectF) + QSizeF, QRectF, QPointF, QThread) from qtpy.QtGui import (QColor, QFont, QFontDatabase, QIcon, QIconEngine, QPainter, QPixmap, QTransform, QPalette, QRawFont, - QImage) + QGlyphRun, QImage) from qtpy.QtWidgets import QApplication # Linux packagers, please set this to True if you want to make qtawesome @@ -105,7 +105,8 @@ def set_global_defaults(**kwargs): 'color_active', 'color_selected', 'color_disabled', 'color_on_selected', 'color_on_active', 'color_on_disabled', 'color_off_selected', 'color_off_active', 'color_off_disabled', - 'animation', 'offset', 'scale_factor', 'rotated', 'hflip', 'vflip' + 'animation', 'offset', 'scale_factor', 'rotated', 'hflip', 'vflip', + 'draw' ] for kw in kwargs: @@ -128,8 +129,6 @@ def paint(self, iconic, painter, rect, mode, state, options): def _paint_icon(self, iconic, painter, rect, mode, state, options): """Paint a single icon.""" painter.save() - color = options['color'] - char = options['char'] color_options = { QIcon.On: { @@ -180,13 +179,6 @@ def _paint_icon(self, iconic, painter, rect, mode, state, options): if animation is not None: animation.setup(self, painter, rect) - font = iconic.font(prefix, draw_size) - # Disable font hinting to mitigate tremulous spinning to some extent - # See spyder-ide/qtawesome#39 - if animation is not None: - font.setHintingPreference(QFont.PreferNoHinting) - painter.setFont(font) - if 'offset' in options: rect = QRect(rect) rect.translate(round(options['offset'][0] * rect.width()), @@ -196,74 +188,83 @@ def _paint_icon(self, iconic, painter, rect, mode, state, options): y_center = rect.height() * 0.5 transform = QTransform() transform.translate(+x_center, +y_center) - if 'vflip' in options and options['vflip'] == True: + if 'vflip' in options and options['vflip'] is True: transform.scale(1,-1) - if 'hflip' in options and options['hflip'] == True: + if 'hflip' in options and options['hflip'] is True: transform.scale(-1, 1) if 'rotated' in options: transform.rotate(options['rotated']) transform.translate(-x_center, -y_center) + painter.setTransform(transform, True) painter.setOpacity(options.get('opacity', 1.0)) - draw = options.get('draw', 'auto') - if draw not in ('text', 'path', 'image', 'auto'): - draw = 'auto' - if draw == 'auto': + draw = options.get('draw') + if draw not in ('text', 'path', 'glyphrun', 'image'): # Use QPainterPath when setting an animation # to fix tremulous spinning icons. # See #39 draw = 'path' if animation is not None else 'text' - if draw == 'path': - if (draw_size, char) not in iconic.path_cache[prefix]: - rawfont = QRawFont(iconic.rawfont[prefix]) - rawfont.setPixelSize(draw_size) - glyph = rawfont.glyphIndexesForString(char)[0] - flag = QRawFont.SeparateAdvances | QRawFont.UseDesignMetrics - advance = rawfont.advancesForGlyphIndexes((glyph,), flag)[0] - path = rawfont.pathForGlyph(glyph) - path.translate(0, rawfont.ascent()) - path.setFillRule(Qt.WindingFill) - size = QSizeF(abs(advance.x()), rawfont.ascent() + rawfont.descent()) - iconic.path_cache[prefix][(draw_size, char)] = (path, size) - - path, size = iconic.path_cache[prefix][(draw_size, char)] - - painter.setTransform(transform, True) - painter.translate(QRectF(rect).center()) - painter.translate(-size.width() / 2, -size.height() / 2) + def try_draw_rawfont(): + if draw == 'glyphrun' and animation is not None: + # Disable font hinting to mitigate tremulous spinning to some extent + # See spyder-ide/qtawesome#39 + rawfont = iconic.rawfont(prefix, draw_size, QFont.PreferNoHinting) + else: + rawfont = iconic.rawfont(prefix, draw_size) - painter.setRenderHint(QPainter.Antialiasing, - painter.renderHints() & QPainter.TextAntialiasing) - painter.fillPath(path, painter.pen().color()) + # Check glyf table and fallback to draw text if missing + # because font glyph is necessary to draw path/glyphrun/image. + if not rawfont.fontTable('glyf'): + return False - elif draw == 'image': - rawfont = QRawFont(iconic.rawfont[prefix]) - rawfont.setPixelSize(draw_size) glyph = rawfont.glyphIndexesForString(char)[0] - alpha = rawfont.alphaMapForGlyph(glyph, transform=transform) - size = alpha.size() - image = QImage(size, QImage.Format_RGBA8888) - image.fill(painter.pen().color()) - try: - image.setAlphaChannel(alpha) - except AttributeError: - a = alpha.convertToFormat(QImage.Format_Grayscale8) - for i in range(size.width()): - for j in range(size.height()): - c = image.pixelColor(i, j) - c.setAlpha(a.pixelColor(i, j).red()) - image.setPixelColor(i, j, c) - + advance = rawfont.advancesForGlyphIndexes((glyph,))[0] + ascent = rawfont.ascent() + size = QSizeF(abs(advance.x()), ascent + rawfont.descent()) painter.translate(QRectF(rect).center()) painter.translate(-size.width() / 2, -size.height() / 2) - painter.drawImage(QPoint(0, 0), image) + if draw == 'path': + path = rawfont.pathForGlyph(glyph) + path.translate(0, ascent) + path.setFillRule(Qt.WindingFill) + painter.setRenderHint(QPainter.Antialiasing, True) + painter.fillPath(path, painter.pen().color()) + + elif draw == 'glyphrun': + glyphrun = QGlyphRun() + glyphrun.setRawFont(rawfont) + glyphrun.setGlyphIndexes((glyph,)) + glyphrun.setPositions((QPointF(0, ascent),)) + painter.drawGlyphRun(QPointF(0, 0), glyphrun) + + elif draw == 'image': + image = rawfont.alphaMapForGlyph(glyph, QRawFont.PixelAntialiasing) \ + .convertToFormat(QImage.Format_ARGB32_Premultiplied) + painter2 = QPainter(image) + painter2.setCompositionMode(QPainter.CompositionMode_SourceIn) + painter2.fillRect(image.rect(), painter.pen().color()) + painter2.end() + brect = rawfont.boundingRect(glyph) + brect.translate(0, ascent) + painter.setRenderHint(QPainter.SmoothPixmapTransform, True) + painter.drawImage(brect.topLeft(), image) - else: - painter.setTransform(transform, True) - painter.setFont(iconic.font(prefix, draw_size)) + else: + # fallback to draw text if unknown value + return False + + return True + + if draw == 'text' or not try_draw_rawfont(): + font = iconic.font(prefix, draw_size) + # Disable font hinting to mitigate tremulous spinning to some extent + # See spyder-ide/qtawesome#39 + if animation is not None: + font.setHintingPreference(QFont.PreferNoHinting) + painter.setFont(font) painter.drawText(rect, int(Qt.AlignCenter | Qt.AlignVCenter), char) painter.restore() @@ -316,11 +317,11 @@ def __init__(self, *args): self.painter = CharIconPainter() self.painters = {} self.fontname = {} + self.fontdata = {} self.fontids = {} self.charmap = {} - self.rawfont = {} - self.path_cache = {} self.icon_cache = {} + self.rawfont_cache = {} for fargs in args: self.load_font(*fargs) @@ -365,11 +366,6 @@ def hook(obj): with open(os.path.join(directory, ttf_filename), 'rb') as font_data: data = font_data.read() id_ = QFontDatabase.addApplicationFontFromData(data) - # `QRawFont()` requires a `pixelSize` value, but it is unknown at this point. - # To workaround that, first we create a `QRawFont` with 2048 as `pixelSize` - # (usual size for TrueType fonts) and reset to the actual size after that. - # Note: For TrueType fonts, `pixelSize` can be specified up to 16384. - rawfont = QRawFont(data, 2048) font_data.close() loadedFontFamilies = QFontDatabase.applicationFontFamilies(id_) @@ -377,8 +373,7 @@ def hook(obj): if loadedFontFamilies: self.fontids[prefix] = id_ self.fontname[prefix] = loadedFontFamilies[0] - self.rawfont[prefix] = rawfont - self.path_cache[prefix] = {} + self.fontdata[prefix] = data else: raise FontError(u"Font at '{0}' appears to be empty. " "If you are on Windows 10, please read " @@ -517,6 +512,24 @@ def font(self, prefix, size): font.setStyleName('Solid') return font + def rawfont(self, prefix, size, hintingPreference=QFont.PreferDefaultHinting): + """Return a QRawFont corresponding to the given prefix and size.""" + cache = self.rawfont_cache + # https://doc.qt.io/qt-5/qrawfont.html + # QRawFont is considered local to the thread in which it is constructed + # (either using a constructor, or by calling loadFromData() or loadFromFile()). + # The QRawFont cannot be moved to a different thread, + # but will have to be recreated in the thread in question. + tid = int(QThread.currentThreadId()) + if tid not in cache: + cache[tid] = {} + def clear_cache(): cache.pop(tid) + QThread().currentThread().finished.connect(clear_cache) + key = prefix, size, hintingPreference + if key not in cache[tid]: + cache[tid][key] = QRawFont(self.fontdata[prefix], size, hintingPreference) + return cache[tid][key] + def set_custom_icon(self, name, painter): """Associate a user-provided CharIconPainter to an icon name. From e23c1326ee2953d4659ad8a610ee32aaa3746314 Mon Sep 17 00:00:00 2001 From: kumattau Date: Sat, 15 Oct 2022 10:18:40 +0900 Subject: [PATCH 9/9] Update qtawesome/iconic_font.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré --- qtawesome/iconic_font.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtawesome/iconic_font.py b/qtawesome/iconic_font.py index 670b0cad..8214d27e 100644 --- a/qtawesome/iconic_font.py +++ b/qtawesome/iconic_font.py @@ -203,7 +203,7 @@ def _paint_icon(self, iconic, painter, rect, mode, state, options): if draw not in ('text', 'path', 'glyphrun', 'image'): # Use QPainterPath when setting an animation # to fix tremulous spinning icons. - # See #39 + # See spyder-ide/qtawesome#39 draw = 'path' if animation is not None else 'text' def try_draw_rawfont():