from PIL import ImageEnhance, ImageFilter, ImageChops from PyQt5.QtGui import QColor, QFont from PyQt5 import QtGui, QtCore, QtWidgets import os import logging from ..component import Component from ..toolkit.frame import FramePainter, PaintColor log = logging.getLogger('AVP.Components.Text') class Component(Component): 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.lineEdit_title.setText(self.title) 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. # Must be something broken in the conversion to a ComponentAction 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) x = self.pixelValForAttr('xPosition') if self.alignment == 1: # Middle offset = int(fm.width(self.title)/2) x -= offset if self.alignment == 2: # Right offset = fm.width(self.title) 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.StyleNormal) font.setWeight(QFont.Normal) font.setCapitalization(QFont.MixedCase) if self.fontStyle == 1: font.setWeight(QFont.DemiBold) if self.fontStyle == 2: font.setWeight(QFont.Bold) elif self.fontStyle == 3: font.setStyle(QFont.StyleItalic) elif self.fontStyle == 4: font.setWeight(QFont.Bold) font.setStyle(QFont.StyleItalic) elif self.fontStyle == 5: font.setStyle(QFont.StyleOblique) elif self.fontStyle == 6: font.setCapitalization(QFont.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.width(self.title[0]) strokeFont = self.page.fontComboBox_titleFont.currentFont() strokeFont.setCapitalization(QFont.SmallCaps) strokeFont.setPixelSize(int((self.fontSize / 7) * 5)) strokeFont.setLetterSpacing(QFont.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.NoPen) image.setBrush(PaintColor(*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: shadImg = ImageEnhance.Contrast(frame).enhance(0.0) shadImg = shadImg.filter(ImageFilter.GaussianBlur(self.shadBlur)) shadImg = ImageChops.offset(shadImg, self.shadX, self.shadY) shadImg.paste(frame, box=(0, 0), mask=frame) frame = shadImg 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)