File indexing completed on 2024-12-22 04:40:20

0001 /*
0002     SPDX-FileCopyrightText: 2010-2022 Mladen Milinkovic <max@smoothware.net>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "subtitletextoverlay.h"
0008 
0009 #include "core/subtitleline.h"
0010 
0011 #include <QAbstractTextDocumentLayout>
0012 #include <QPainter>
0013 #include <QTextCharFormat>
0014 #include <QTextLayout>
0015 
0016 #include "scconfig.h"
0017 
0018 using namespace SubtitleComposer;
0019 
0020 SubtitleTextOverlay::SubtitleTextOverlay()
0021     : m_invertPixels(false)
0022 {
0023     m_font.setStyleStrategy(QFont::PreferAntialias);
0024     m_font.setPixelSize(SCConfig::fontSize());
0025 }
0026 
0027 QTextLayout **
0028 SubtitleTextOverlay::drawDocPrepare(QPainter *painter)
0029 {
0030     QTextLayout **layoutData = new QTextLayout*[2 * m_doc->blockCount() + 1];
0031     QTextLayout **res = layoutData;
0032 
0033     const QFontMetrics &fontMetrics = painter->fontMetrics();
0034 
0035     QTextOption layoutTextOption;
0036     const int imgWidth = m_renderScale > 1.f ? float(m_image.width()) / m_renderScale : m_image.width();
0037     int lineWidth;
0038     if(m_pos) {
0039         layoutTextOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
0040         if(m_pos->hAlign == SubtitleRect::START)
0041             layoutTextOption.setAlignment(Qt::AlignLeft);
0042         else if(m_pos->hAlign == SubtitleRect::END)
0043             layoutTextOption.setAlignment(Qt::AlignRight);
0044         else
0045             layoutTextOption.setAlignment(Qt::AlignHCenter);
0046         lineWidth = (m_pos->right - m_pos->left) * imgWidth / 100;
0047     } else {
0048         layoutTextOption.setWrapMode(QTextOption::NoWrap);
0049         layoutTextOption.setAlignment(Qt::AlignHCenter);
0050         lineWidth = imgWidth;
0051     }
0052 
0053     qreal height = 0., heightOutline = 0.;
0054     qreal maxLineWidth = 0;
0055 
0056     RichDocumentLayout *docLayout = m_doc->documentLayout();
0057     for(QTextBlock bi = m_doc->begin(); bi != m_doc->end(); bi = bi.next()) {
0058         const QString &text = bi.text();
0059         QVector<QTextLayout::FormatRange> fmtRanges = docLayout->applyCSS(bi.textFormats());
0060 
0061         QTextLayout *tlNormal = new QTextLayout(text, m_font, painter->device());
0062         *layoutData++ = tlNormal;
0063         tlNormal->setCacheEnabled(true);
0064         tlNormal->setTextOption(layoutTextOption);
0065         tlNormal->setFormats(fmtRanges);
0066 
0067         tlNormal->beginLayout();
0068         for(;;) {
0069             QTextLine line = tlNormal->createLine();
0070             if(!line.isValid())
0071                 break;
0072             line.setLineWidth(lineWidth);
0073             height += fontMetrics.leading();
0074             line.setPosition(QPointF(0., height));
0075             height += line.height();
0076             maxLineWidth = qMax(maxLineWidth, line.naturalTextWidth());
0077         }
0078         tlNormal->endLayout();
0079 
0080         if(m_textOutline.width()) {
0081             QTextLayout *tlOutline = new QTextLayout(text, m_font, painter->device());
0082             *layoutData++ = tlOutline;
0083             tlOutline->setCacheEnabled(true);
0084             tlOutline->setTextOption(layoutTextOption);
0085             for(QTextLayout::FormatRange &r: fmtRanges)
0086                 r.format.setTextOutline(m_textOutline);
0087             tlOutline->setFormats(fmtRanges);
0088 
0089             tlOutline->beginLayout();
0090             for(;;) {
0091                 QTextLine line = tlOutline->createLine();
0092                 if(!line.isValid())
0093                     break;
0094                 line.setLineWidth(lineWidth);
0095                 heightOutline += fontMetrics.leading();
0096                 line.setPosition(QPointF(0., heightOutline));
0097                 heightOutline += line.height();
0098                 maxLineWidth = qMax(maxLineWidth, line.naturalTextWidth());
0099             }
0100             tlOutline->endLayout();
0101         } else {
0102             *layoutData++ = nullptr;
0103         }
0104     }
0105 
0106     *layoutData = nullptr;
0107 
0108     m_textSize = QSize(maxLineWidth, qMax(height, heightOutline));
0109 
0110     return res;
0111 }
0112 
0113 void
0114 SubtitleTextOverlay::drawDoc()
0115 {
0116     QPainter painter(&m_image);
0117     painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, true);
0118     painter.setFont(m_font);
0119     painter.setPen(m_textColor);
0120 
0121     QTextLayout **d = drawDocPrepare(&painter);
0122 
0123     const float imgWidth = m_renderScale > 1.f ? float(m_image.width()) / m_renderScale : m_image.width();
0124     const float imgHeight = (m_renderScale > 1.f ? float(m_image.height()) / m_renderScale : m_image.height()) - m_bottomPadding;
0125     QPointF drawPos;
0126     if(m_pos) {
0127         drawPos.setX(m_pos->left * imgWidth / 100.);
0128         if(m_pos->vAlign == SubtitleRect::TOP)
0129             drawPos.setY(m_pos->top * imgHeight / 100.);
0130         else
0131             drawPos.setY(m_pos->bottom * imgHeight / 100. - m_textSize.height());
0132     } else {
0133         drawPos.setY(imgHeight - m_textSize.height());
0134     }
0135 
0136     for(int i = 0; d[i]; i += 2) {
0137         if(d[i + 1]) {
0138             d[i + 1]->draw(&painter, drawPos);
0139             delete d[i + 1];
0140         }
0141         d[i]->draw(&painter, drawPos);
0142         delete d[i];
0143     }
0144     delete[] d;
0145 
0146     painter.end();
0147 }
0148 
0149 void
0150 SubtitleTextOverlay::drawImage()
0151 {
0152     m_image.fill(Qt::transparent);
0153     if(m_doc)
0154         drawDoc();
0155     m_dirty = false;
0156 }
0157 
0158 const QImage &
0159 SubtitleTextOverlay::image()
0160 {
0161     if(m_dirty)
0162         drawImage();
0163 
0164     return m_image;
0165 }
0166 
0167 void
0168 SubtitleTextOverlay::invertPixels(bool invert)
0169 {
0170     if(m_invertPixels == invert)
0171         return;
0172     m_invertPixels = invert;
0173     setTextColor(m_textColor);
0174     setOutlineColor(m_textOutline.color());
0175 }
0176 
0177 const QSize &
0178 SubtitleTextOverlay::textSize()
0179 {
0180     if(m_dirty)
0181         drawImage();
0182 
0183     return m_textSize;
0184 }
0185 
0186 void
0187 SubtitleTextOverlay::setImageSize(int width, int height)
0188 {
0189     if(m_image.width() == width && m_image.height() == height)
0190         return;
0191 
0192     m_image = QImage(width, height, QImage::Format_ARGB32);
0193     setDirty();
0194 }
0195 
0196 void
0197 SubtitleTextOverlay::setDirty()
0198 {
0199     m_dirty = true;
0200     emit repaintNeeded();
0201 }
0202 
0203 void
0204 SubtitleTextOverlay::setText(const QString &text)
0205 {
0206     if(!m_text) {
0207         m_text = new RichDocument(this);
0208     } else if(m_text->toHtml() == text) {
0209         return;
0210     }
0211     m_text->setHtml(text, true);
0212     setDoc(m_text);
0213     setDirty();
0214 }
0215 
0216 void
0217 SubtitleTextOverlay::setDoc(const RichDocument *doc)
0218 {
0219     if(m_doc == doc)
0220         return;
0221     if(m_doc) {
0222         disconnect(m_doc, nullptr, this, nullptr);
0223         disconnect(m_doc->stylesheet(), nullptr, this, nullptr);
0224     }
0225     m_doc = doc;
0226     if(m_doc) {
0227         connect(m_doc, &RichDocument::contentsChanged, this, &SubtitleTextOverlay::setDirty);
0228         connect(m_doc->stylesheet(), &RichCSS::changed, this, &SubtitleTextOverlay::setDirty);
0229     }
0230     setDirty();
0231 }
0232 
0233 void
0234 SubtitleTextOverlay::setDocRect(const SubtitleRect *pos)
0235 {
0236     if(m_pos == pos)
0237         return;
0238     m_pos = pos;
0239     setDirty();
0240 }
0241 
0242 void
0243 SubtitleTextOverlay::setRenderScale(double scale)
0244 {
0245     if(m_renderScale == scale)
0246         return;
0247     m_renderScale = scale;
0248     setDirty();
0249 }
0250 
0251 void
0252 SubtitleTextOverlay::setBottomPadding(int padding)
0253 {
0254     if(m_bottomPadding == padding)
0255         return;
0256     m_bottomPadding = padding;
0257     setDirty();
0258 }
0259 
0260 void
0261 SubtitleTextOverlay::setFontFamily(const QString &family)
0262 {
0263     if(m_font.family() == family)
0264         return;
0265     m_font.setFamily(family);
0266     setDirty();
0267 }
0268 
0269 void
0270 SubtitleTextOverlay::setFontSize(int fontSize)
0271 {
0272     if(fontSize == m_font.pixelSize())
0273         return;
0274     m_font.setPixelSize(fontSize);
0275     setDirty();
0276 }
0277 
0278 void
0279 SubtitleTextOverlay::setTextColor(QColor color)
0280 {
0281     if(m_invertPixels)
0282         color = QColor(color.blue(), color.green(), color.red(), color.alpha());
0283     if(m_textColor == color)
0284         return;
0285     m_textColor = color;
0286     setDirty();
0287 }
0288 
0289 void
0290 SubtitleTextOverlay::setOutlineColor(QColor color)
0291 {
0292     if(m_invertPixels)
0293         color = QColor(color.blue(), color.green(), color.red(), color.alpha());
0294     if(m_textOutline.color() == color)
0295         return;
0296     m_textOutline.setColor(color);
0297     setDirty();
0298 }
0299 
0300 void
0301 SubtitleTextOverlay::setOutlineWidth(int width)
0302 {
0303     if(m_textOutline.width() == width)
0304         return;
0305     m_textOutline.setWidth(width);
0306     setDirty();
0307 }
0308