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 }