from PyQt6.QtGui import QFont from PyQt6 import QtGui, QtCore import logging from ..libcomponent import BaseComponent from ..toolkit.frame import FramePainter, addShadow log = logging.getLogger("AVP.Components.Text") class Component(BaseComponent): name = "Title Text" version = "1.0.1" def widget(self, *args): super().widget(*args) self.title = "Text" self.alignment = 1 self.titleFont = QFont() self.fontSize = self.height / 13.5 self.page.comboBox_textAlign.addItem("Left") self.page.comboBox_textAlign.addItem("Middle") self.page.comboBox_textAlign.addItem("Right") self.page.comboBox_textAlign.setCurrentIndex(int(self.alignment)) self.page.spinBox_fontSize.setValue(int(self.fontSize)) self.page.pushButton_center.clicked.connect(self.centerXY) self.page.fontComboBox_titleFont.currentFontChanged.connect( self._sendUpdateSignal ) # The QFontComboBox must be connected directly to the Qt Signal # which triggers the preview to update. # This unfortunately makes changing the font into a non-undoable action. # Fix requires updating ComponentAction to handle fonts self.trackWidgets( { "textColor": self.page.lineEdit_textColor, "title": self.page.lineEdit_title, "alignment": self.page.comboBox_textAlign, "fontSize": self.page.spinBox_fontSize, "xPosition": self.page.spinBox_xTextAlign, "yPosition": self.page.spinBox_yTextAlign, "fontStyle": self.page.comboBox_fontStyle, "stroke": self.page.spinBox_stroke, "strokeColor": self.page.lineEdit_strokeColor, "shadow": self.page.checkBox_shadow, "shadX": self.page.spinBox_shadX, "shadY": self.page.spinBox_shadY, "shadBlur": self.page.spinBox_shadBlur, }, colorWidgets={ "textColor": self.page.pushButton_textColor, "strokeColor": self.page.pushButton_strokeColor, }, relativeWidgets=[ "xPosition", "yPosition", "fontSize", "stroke", "shadX", "shadY", "shadBlur", ], ) self.centerXY() def update(self): self.titleFont = self.page.fontComboBox_titleFont.currentFont() if self.page.checkBox_shadow.isChecked(): self.page.label_shadX.setHidden(False) self.page.spinBox_shadX.setHidden(False) self.page.spinBox_shadY.setHidden(False) self.page.label_shadBlur.setHidden(False) self.page.spinBox_shadBlur.setHidden(False) else: self.page.label_shadX.setHidden(True) self.page.spinBox_shadX.setHidden(True) self.page.spinBox_shadY.setHidden(True) self.page.label_shadBlur.setHidden(True) self.page.spinBox_shadBlur.setHidden(True) def centerXY(self): self.setRelativeWidget("xPosition", 0.5) self.setRelativeWidget("yPosition", 0.521) def getXY(self): """Returns true x, y after considering alignment settings""" fm = QtGui.QFontMetrics(self.titleFont) text_width = fm.boundingRect(self.title).width() x = self.pixelValForAttr("xPosition") if self.alignment == 1: # Middle offset = int(text_width / 2) elif self.alignment == 2: # Right offset = text_width else: raise ValueError(f"Alignment value {self.alignment} unknown") x -= offset return x, self.yPosition def loadPreset(self, pr, *args): super().loadPreset(pr, *args) font = QFont() font.fromString(pr["titleFont"]) self.page.fontComboBox_titleFont.setCurrentFont(font) def savePreset(self): saveValueStore = super().savePreset() saveValueStore["titleFont"] = self.titleFont.toString() return saveValueStore def previewRender(self): return self.addText(self.width, self.height) def properties(self): props = ["static"] if not self.title: props.append("error") return props def error(self): return "No text provided." def frameRender(self, frameNo): return self.addText(self.width, self.height) def addText(self, width, height): font = self.titleFont font.setPixelSize(self.fontSize) font.setStyle(QFont.Style.StyleNormal) font.setWeight(QFont.Weight.Normal) font.setCapitalization(QFont.Capitalization.MixedCase) if self.fontStyle == 1: font.setWeight(QFont.Weight.DemiBold) if self.fontStyle == 2: font.setWeight(QFont.Weight.Bold) elif self.fontStyle == 3: font.setStyle(QFont.Style.StyleItalic) elif self.fontStyle == 4: font.setWeight(QFont.Weight.Bold) font.setStyle(QFont.Style.StyleItalic) elif self.fontStyle == 5: font.setStyle(QFont.Style.StyleOblique) elif self.fontStyle == 6: font.setCapitalization(QFont.Capitalization.SmallCaps) image = FramePainter(width, height) x, y = self.getXY() log.debug("Text position translates to %s, %s", x, y) if self.stroke > 0: outliner = QtGui.QPainterPathStroker() outliner.setWidth(self.stroke) path = QtGui.QPainterPath() if self.fontStyle == 6: # PathStroker ignores smallcaps so we need this weird hack path.addText(x, y, font, self.title[0]) fm = QtGui.QFontMetrics(font) newX = x + fm.boundingRect(self.title[0]).width() strokeFont = self.page.fontComboBox_titleFont.currentFont() strokeFont.setCapitalization(QFont.Capitalization.SmallCaps) strokeFont.setPixelSize(int((self.fontSize / 7) * 5)) strokeFont.setLetterSpacing(QFont.SpacingType.PercentageSpacing, 139) path.addText(newX, y, strokeFont, self.title[1:]) else: path.addText(x, y, font, self.title) path = outliner.createStroke(path) image.setPen(QtCore.Qt.PenStyle.NoPen) image.setBrush(QtGui.QColor(*self.strokeColor)) image.drawPath(path) image.setFont(font) image.setPen(self.textColor) image.drawText(x, y, self.title) # turn QImage into Pillow frame frame = image.finalize() if self.shadow: frame = addShadow(frame, self.shadBlur / 10, self.shadX, self.shadY) return frame def commandHelp(self): print("Enter a string to use as centred white text:") print(' "title=User Error"') print("Specify a text color:\n color=255,255,255") print("Set custom x, y position:\n x=500 y=500") def command(self, arg): if "=" in arg: key, arg = arg.split("=", 1) if key == "color": self.page.lineEdit_textColor.setText(arg) return elif key == "size": self.page.spinBox_fontSize.setValue(int(arg)) return elif key == "x": self.page.spinBox_xTextAlign.setValue(int(arg)) return elif key == "y": self.page.spinBox_yTextAlign.setValue(int(arg)) return elif key == "title": self.page.lineEdit_title.setText(arg) return super().command(arg)