File indexing completed on 2025-02-23 04:35:42
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 "wavesubtitle.h" 0008 0009 #include "core/richtext/richdocument.h" 0010 #include "core/subtitleline.h" 0011 #include "gui/waveform/waverenderer.h" 0012 0013 #include <QPainter> 0014 #include <QScopedPointer> 0015 #include <QTextBlock> 0016 #include <QTextLayout> 0017 #include <QTextLine> 0018 0019 using namespace SubtitleComposer; 0020 0021 WaveSubtitle::WaveSubtitle(SubtitleLine *line, WaveRenderer *parent) 0022 : QObject(parent), 0023 m_line(line), 0024 m_rend(parent), 0025 m_image(1, 1, QImage::Format_ARGB32_Premultiplied) 0026 { 0027 const RichDocument *doc = m_rend->showTranslation() ? m_line->secondaryDoc() : m_line->primaryDoc(); 0028 connect(doc, &RichDocument::contentsChanged, this, [&](){ m_imageDirty = true; }); 0029 } 0030 0031 WaveSubtitle::~WaveSubtitle() 0032 { 0033 } 0034 0035 DragPosition 0036 WaveSubtitle::draggableAt(double time, double msTolerance) const 0037 { 0038 if(!m_line->intersectsTimespan(time - msTolerance, time + msTolerance)) 0039 return DRAG_NONE; 0040 0041 const bool hasAnchors = m_line->subtitle() && m_line->subtitle()->hasAnchors(); 0042 if(hasAnchors && !m_line->subtitle()->isLineAnchored(m_line)) 0043 return DRAG_FORBIDDEN; 0044 0045 const double showDistance = qAbs(m_line->showTime().toMillis() - time); 0046 if(msTolerance > showDistance) 0047 return hasAnchors ? DRAG_LINE : DRAG_SHOW; 0048 0049 const double hideDistance = qAbs(m_line->hideTime().toMillis() - time); 0050 if(msTolerance > hideDistance) 0051 return hasAnchors ? DRAG_LINE : DRAG_HIDE; 0052 0053 return DRAG_LINE; 0054 } 0055 0056 void 0057 WaveSubtitle::dragStart(DragPosition dragMode, double dragTime) 0058 { 0059 if(dragMode == DRAG_FORBIDDEN) { 0060 m_dragTime = 0.; 0061 m_dragMode = DRAG_NONE; 0062 return; 0063 } 0064 0065 m_dragTime = dragTime; 0066 m_dragMode = dragMode; 0067 0068 if(dragMode == DRAG_LINE || dragMode == DRAG_SHOW) 0069 m_dragTimeOffset = dragTime - m_line->showTime().toMillis(); 0070 else if(dragMode == DRAG_HIDE) 0071 m_dragTimeOffset = dragTime - m_line->hideTime().toMillis(); 0072 } 0073 0074 DragPosition 0075 WaveSubtitle::dragEnd(double dragTime) 0076 { 0077 DragPosition mode = m_dragMode; 0078 0079 const Time newTime(dragTime - m_dragTimeOffset); 0080 if(m_dragMode == DRAG_LINE) 0081 m_line->setTimes(newTime, newTime.shifted(m_line->duration())); 0082 else if(m_dragMode == DRAG_SHOW) 0083 m_line->setShowTime(newTime); 0084 else if(m_dragMode == DRAG_HIDE) 0085 m_line->setHideTime(newTime); 0086 0087 m_dragMode = DRAG_NONE; 0088 m_dragTime = 0.; 0089 0090 return mode; 0091 } 0092 0093 Time 0094 WaveSubtitle::showTime() const 0095 { 0096 Time newTime(m_dragTime - m_dragTimeOffset); 0097 0098 switch(m_dragMode) { 0099 case DRAG_LINE: 0100 return newTime; 0101 0102 case DRAG_SHOW: 0103 return qMin(newTime, m_line->hideTime()); 0104 0105 case DRAG_HIDE: 0106 return qMin(newTime, m_line->showTime()); 0107 0108 default: 0109 return m_line->showTime(); 0110 } 0111 } 0112 0113 Time 0114 WaveSubtitle::hideTime() const 0115 { 0116 Time newTime(m_dragTime - m_dragTimeOffset); 0117 0118 switch(m_dragMode) { 0119 case DRAG_LINE: 0120 return newTime.shifted(m_line->duration()); 0121 0122 case DRAG_SHOW: 0123 return qMax(newTime, m_line->hideTime()); 0124 0125 case DRAG_HIDE: 0126 return qMax(newTime, m_line->showTime()); 0127 0128 default: 0129 return m_line->hideTime(); 0130 } 0131 } 0132 0133 const QImage & 0134 WaveSubtitle::image() const 0135 { 0136 if(!m_imageDirty) 0137 return m_image; 0138 0139 const RichDocument *doc = m_rend->showTranslation() ? m_line->secondaryDoc() : m_line->primaryDoc(); 0140 const RichDocumentLayout *layout = doc->documentLayout(); 0141 0142 qreal width = 0., height = 0.; 0143 QScopedPointer<QTextLayout, QScopedPointerArrayDeleter<QTextLayout>> layouts(new QTextLayout[doc->blockCount()]); 0144 0145 QTextLayout *bl = layouts.data(); 0146 for(QTextBlock bi = doc->begin(); bi != doc->end(); bi = bi.next()) { 0147 bl->setCacheEnabled(true); 0148 bl->setFont(m_rend->fontText()); 0149 bl->setText(bi.text()); 0150 bl->setFormats(layout->applyCSS(bi.textFormats())); 0151 bl->beginLayout(); 0152 QTextOption option = bi.layout()->textOption(); 0153 option.setAlignment(Qt::AlignTop | Qt::AlignLeft | Qt::AlignAbsolute); 0154 bl->setTextOption(option); 0155 for(;;) { 0156 QTextLine line = bl->createLine(); 0157 if(!line.isValid()) 0158 break; 0159 line.setLeadingIncluded(true); 0160 line.setLineWidth(10000); 0161 line.setPosition(QPointF(0., height)); 0162 height += line.height(); 0163 width = qMax(width, line.naturalTextWidth()); 0164 } 0165 bl->endLayout(); 0166 bl++; 0167 } 0168 0169 m_image = QImage(QSize(width, height), QImage::Format_ARGB32_Premultiplied); 0170 if(m_image.isNull()) 0171 return m_image; 0172 m_image.fill(Qt::transparent); 0173 QScopedPointer<QPainter> painter(new QPainter(&m_image)); 0174 if(!painter->isActive()) 0175 return m_image; 0176 painter->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); 0177 painter->setFont(m_rend->fontText()); 0178 painter->setPen(m_rend->subTextColor()); 0179 0180 while(bl-- != layouts.data()) { 0181 const int n = bl->lineCount(); 0182 for(int i = 0; i < n; i++) { 0183 const QTextLine &tl = bl->lineAt(i); 0184 const QPointF pos((width - tl.naturalTextWidth()) / 2., 0.); 0185 tl.draw(painter.data(), pos); 0186 } 0187 } 0188 0189 painter->end(); 0190 0191 m_imageDirty = false; 0192 return m_image; 0193 }