/*
* (C) 2003-2006 Gabest
* (C) 2006-2015 see Authors.txt
*
* This file is part of MPC-HC.
*
* MPC-HC is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* MPC-HC is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include "stdafx.h"
#include
#include
#include
#include "ColorConvTable.h"
#include "RTS.h"
#include "../DSUtil/PathUtils.h"
// WARNING: this isn't very thread safe, use only one RTS a time. We should use TLS in future.
static HDC g_hDC;
static int g_hDC_refcnt = 0;
static long revcolor(long c)
{
return ((c & 0xff0000) >> 16) + (c & 0xff00) + ((c & 0xff) << 16);
}
//////////////////////////////////////////////////////////////////////////////////////////////
// CMyFont
CMyFont::CMyFont(const STSStyle& style)
{
LOGFONT lf;
ZeroMemory(&lf, sizeof(lf));
lf <<= style;
lf.lfHeight = (LONG)(style.fontSize + 0.5);
lf.lfOutPrecision = OUT_TT_PRECIS;
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
lf.lfQuality = ANTIALIASED_QUALITY;
lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
if (!CreateFontIndirect(&lf)) {
_tcscpy_s(lf.lfFaceName, _T("Arial"));
VERIFY(CreateFontIndirect(&lf));
}
HFONT hOldFont = SelectFont(g_hDC, *this);
TEXTMETRIC tm;
GetTextMetrics(g_hDC, &tm);
m_ascent = ((tm.tmAscent + 4) >> 3);
m_descent = ((tm.tmDescent + 4) >> 3);
SelectFont(g_hDC, hOldFont);
}
// CWord
CWord::CWord(const STSStyle& style, CStringW str, int ktype, int kstart, int kend, double scalex, double scaley,
RenderingCaches& renderingCaches)
: m_fDrawn(false)
, m_p(INT_MAX, INT_MAX)
, m_renderingCaches(renderingCaches)
, m_scalex(scalex)
, m_scaley(scaley)
, m_str(str)
, m_fWhiteSpaceChar(false)
, m_fLineBreak(false)
, m_style(style)
, m_pOpaqueBox(nullptr)
, m_ktype(ktype)
, m_kstart(kstart)
, m_kend(kend)
, m_width(0)
, m_ascent(0)
, m_descent(0)
{
if (str.IsEmpty()) {
m_fWhiteSpaceChar = m_fLineBreak = true;
}
}
CWord::~CWord()
{
delete m_pOpaqueBox;
}
bool CWord::Append(CWord* w)
{
if (m_style != w->m_style
|| m_fLineBreak || w->m_fLineBreak
|| w->m_kstart != w->m_kend || m_ktype != w->m_ktype) {
return false;
}
m_fWhiteSpaceChar = m_fWhiteSpaceChar && w->m_fWhiteSpaceChar;
m_str += w->m_str;
m_width += w->m_width;
m_fDrawn = false;
m_p = CPoint(INT_MAX, INT_MAX);
return true;
}
void CWord::Paint(const CPoint& p, const CPoint& org)
{
if (!m_str) {
return;
}
COverlayKey overlayKey(this, p, org);
if (m_renderingCaches.overlayCache.Lookup(overlayKey, m_pOverlayData)) {
m_fDrawn = m_renderingCaches.outlineCache.Lookup(overlayKey, m_pOutlineData);
if (m_style.borderStyle == 1) {
VERIFY(CreateOpaqueBox());
}
} else {
if (!m_fDrawn) {
if (m_renderingCaches.outlineCache.Lookup(overlayKey, m_pOutlineData)) {
if (m_style.borderStyle == 1) {
VERIFY(CreateOpaqueBox());
}
} else {
if (!CreatePath()) {
return;
}
Transform(CPoint((org.x - p.x) * 8, (org.y - p.y) * 8));
if (!ScanConvert()) {
return;
}
if (m_style.borderStyle == 0 && (m_style.outlineWidthX + m_style.outlineWidthY > 0)) {
int rx = std::max(0, std::lround(m_style.outlineWidthX));
int ry = std::max(0, std::lround(m_style.outlineWidthY));
if (!m_pEllipse || m_pEllipse->GetXRadius() != rx || m_pEllipse->GetYRadius() != ry) {
CEllipseKey ellipseKey(rx, ry);
if (!m_renderingCaches.ellipseCache.Lookup(ellipseKey, m_pEllipse)) {
m_pEllipse = std::make_shared(rx, ry);
m_renderingCaches.ellipseCache.SetAt(ellipseKey, m_pEllipse);
}
}
if (!CreateWidenedRegion(rx, ry)) {
return;
}
} else if (m_style.borderStyle == 1) {
VERIFY(CreateOpaqueBox());
}
m_renderingCaches.outlineCache.SetAt(overlayKey, m_pOutlineData);
}
m_fDrawn = true;
if (!Rasterize(p.x & 7, p.y & 7, m_style.fBlur, m_style.fGaussianBlur)) {
return;
}
m_renderingCaches.overlayCache.SetAt(overlayKey, m_pOverlayData);
} else if ((m_p.x & 7) != (p.x & 7) || (m_p.y & 7) != (p.y & 7)) {
Rasterize(p.x & 7, p.y & 7, m_style.fBlur, m_style.fGaussianBlur);
m_renderingCaches.overlayCache.SetAt(overlayKey, m_pOverlayData);
}
}
m_p = p;
if (m_pOpaqueBox) {
m_pOpaqueBox->Paint(p, org);
}
}
void CWord::Transform(CPoint org)
{
#if defined(_M_IX86_FP) && _M_IX86_FP < 2
if (!m_bUseSSE2) {
Transform_C(org);
} else
#endif
{
Transform_SSE2(org);
}
}
bool CWord::CreateOpaqueBox()
{
if (m_pOpaqueBox) {
return true;
}
STSStyle style = m_style;
style.borderStyle = 0;
// We don't want to apply the outline and the scaling twice
style.outlineWidthX = style.outlineWidthY = 0.0;
style.fontScaleX = style.fontScaleY = 100.0;
style.colors[0] = m_style.colors[2];
style.alpha[0] = m_style.alpha[2];
int w = std::lround(m_style.outlineWidthX);
int h = std::lround(m_style.outlineWidthY);
// Convert to pixels rounding to nearest
CStringW str;
str.Format(L"m %d %d l %d %d %d %d %d %d",
-(w + 4) / 8, -(h + 4) / 8,
(m_width + w + 4) / 8, -(h + 4) / 8,
(m_width + w + 4) / 8, (m_ascent + m_descent + h + 4) / 8,
-(w + 4) / 8, (m_ascent + m_descent + h + 4) / 8);
try {
m_pOpaqueBox = DEBUG_NEW CPolygon(style, str, 0, 0, 0, 1.0, 1.0, 0, m_renderingCaches);
} catch (CMemoryException* e) {
e->Delete();
m_pOpaqueBox = nullptr;
}
return !!m_pOpaqueBox;
}
void CWord::Transform_C(const CPoint& org)
{
const double scalex = m_style.fontScaleX / 100.0;
const double scaley = m_style.fontScaleY / 100.0;
const double xzoomf = m_scalex * 20000.0;
const double yzoomf = m_scaley * 20000.0;
const double caz = cos((M_PI / 180.0) * m_style.fontAngleZ);
const double saz = sin((M_PI / 180.0) * m_style.fontAngleZ);
const double cax = cos((M_PI / 180.0) * m_style.fontAngleX);
const double sax = sin((M_PI / 180.0) * m_style.fontAngleX);
const double cay = cos((M_PI / 180.0) * m_style.fontAngleY);
const double say = sin((M_PI / 180.0) * m_style.fontAngleY);
double dOrgX = static_cast(org.x), dOrgY = static_cast(org.y);
for (ptrdiff_t i = 0; i < mPathPoints; i++) {
double x, y, z, xx, yy, zz;
x = mpPathPoints[i].x;
y = mpPathPoints[i].y;
z = 0;
const double dPPx = m_style.fontShiftX * y + x;
y = scaley * (m_style.fontShiftY * x + y) - dOrgY;
x = scalex * dPPx - dOrgX;
xx = x * caz + y * saz;
yy = -(x * saz - y * caz);
zz = z;
x = xx;
y = yy * cax + zz * sax;
z = yy * sax - zz * cax;
xx = x * cay + z * say;
yy = y;
zz = x * say - z * cay;
x = xx * xzoomf / std::max((zz + xzoomf), 1000.0);
y = yy * yzoomf / std::max((zz + yzoomf), 1000.0);
// round to integer
mpPathPoints[i].x = std::lround(x) + org.x;
mpPathPoints[i].y = std::lround(y) + org.y;
}
}
void CWord::Transform_SSE2(const CPoint& org)
{
// SSE code
// speed up ~1.5-1.7x
const __m128 __xshift = _mm_set_ps1((float)m_style.fontShiftX);
const __m128 __yshift = _mm_set_ps1((float)m_style.fontShiftY);
const __m128 __xorg = _mm_set_ps1((float)org.x);
const __m128 __yorg = _mm_set_ps1((float)org.y);
const __m128 __xscale = _mm_set_ps1((float)(m_style.fontScaleX / 100.0));
const __m128 __yscale = _mm_set_ps1((float)(m_style.fontScaleY / 100.0));
const __m128 __xzoomf = _mm_set_ps1((float)(m_scalex * 20000.0));
const __m128 __yzoomf = _mm_set_ps1((float)(m_scaley * 20000.0));
const __m128 __caz = _mm_set_ps1((float)cos((M_PI / 180.0) * m_style.fontAngleZ));
const __m128 __saz = _mm_set_ps1((float)sin((M_PI / 180.0) * m_style.fontAngleZ));
const __m128 __cax = _mm_set_ps1((float)cos((M_PI / 180.0) * m_style.fontAngleX));
const __m128 __sax = _mm_set_ps1((float)sin((M_PI / 180.0) * m_style.fontAngleX));
const __m128 __cay = _mm_set_ps1((float)cos((M_PI / 180.0) * m_style.fontAngleY));
const __m128 __say = _mm_set_ps1((float)sin((M_PI / 180.0) * m_style.fontAngleY));
const __m128 __1000 = _mm_set_ps1(1000.0f);
int mPathPointsD4 = mPathPoints / 4;
int mPathPointsM4 = mPathPoints % 4;
for (ptrdiff_t i = 0; i < mPathPointsD4 + 1; i++) {
__m128 __pointx, __pointy, __tmpx, __tmpy;
// we can't use load .-.
if (i != mPathPointsD4) {
__pointx = _mm_set_ps((float)mpPathPoints[4 * i + 0].x, (float)mpPathPoints[4 * i + 1].x, (float)mpPathPoints[4 * i + 2].x, (float)mpPathPoints[4 * i + 3].x);
__pointy = _mm_set_ps((float)mpPathPoints[4 * i + 0].y, (float)mpPathPoints[4 * i + 1].y, (float)mpPathPoints[4 * i + 2].y, (float)mpPathPoints[4 * i + 3].y);
} else { // last cycle
switch (mPathPointsM4) {
default:
case 0:
continue;
case 1:
__pointx = _mm_set_ps((float)mpPathPoints[4 * i + 0].x, 0, 0, 0);
__pointy = _mm_set_ps((float)mpPathPoints[4 * i + 0].y, 0, 0, 0);
break;
case 2:
__pointx = _mm_set_ps((float)mpPathPoints[4 * i + 0].x, (float)mpPathPoints[4 * i + 1].x, 0, 0);
__pointy = _mm_set_ps((float)mpPathPoints[4 * i + 0].y, (float)mpPathPoints[4 * i + 1].y, 0, 0);
break;
case 3:
__pointx = _mm_set_ps((float)mpPathPoints[4 * i + 0].x, (float)mpPathPoints[4 * i + 1].x, (float)mpPathPoints[4 * i + 2].x, 0);
__pointy = _mm_set_ps((float)mpPathPoints[4 * i + 0].y, (float)mpPathPoints[4 * i + 1].y, (float)mpPathPoints[4 * i + 2].y, 0);
break;
}
}
// scale and shift
__tmpy = __pointx; // save a copy for calculating __pointy later
if (m_style.fontShiftX != 0) {
__tmpx = _mm_mul_ps(__xshift, __pointy);
__pointx = _mm_add_ps(__pointx, __tmpx);
}
__pointx = _mm_mul_ps(__pointx, __xscale);
__pointx = _mm_sub_ps(__pointx, __xorg);
if (m_style.fontShiftY != 0) {
__tmpy = _mm_mul_ps(__yshift, __tmpy); // __tmpy is a copy of __pointx here, because it may otherwise be modified
__pointy = _mm_add_ps(__pointy, __tmpy);
}
__pointy = _mm_mul_ps(__pointy, __yscale);
__pointy = _mm_sub_ps(__pointy, __yorg);
// rotate
__m128 __xx, __yy;
__m128 __zz = _mm_setzero_ps();
// xx = x * caz + y * saz
__tmpx = _mm_mul_ps(__pointx, __caz); // x * caz
__tmpy = _mm_mul_ps(__pointy, __saz); // y * saz
__xx = _mm_add_ps(__tmpx, __tmpy); // xx = x * caz + y * saz;
// yy = -(x * saz - y * caz)
__tmpx = _mm_mul_ps(__pointx, __saz); // x * saz
__tmpy = _mm_mul_ps(__pointy, __caz); // y * caz
__yy = _mm_sub_ps(__tmpy, __tmpx); // yy = -(x * saz - y * caz) = y * caz - x * saz
__pointx = __xx; // x = xx
// y = yy * cax + zz * sax
__tmpx = _mm_mul_ps(__zz, __sax); // zz * sax
__tmpy = _mm_mul_ps(__yy, __cax); // yy * cax
__pointy = _mm_add_ps(__tmpy, __tmpx); // y = yy * cax + zz * sax
// z = yy * sax - zz * cax
__tmpx = _mm_mul_ps(__zz, __cax); // zz * cax
__tmpy = _mm_mul_ps(__yy, __sax); // yy * sax
__zz = _mm_sub_ps(__tmpy, __tmpx); // z = yy * sax - zz * cax
// xx = x * cay + z * say
__tmpx = _mm_mul_ps(__pointx, __cay); // x * cay
__tmpy = _mm_mul_ps(__zz, __say); // z * say
__xx = _mm_add_ps(__tmpx, __tmpy); // xx = x * cay + z * say
__yy = __pointy; // yy = y
// zz = x * say - z * cay
__tmpx = _mm_mul_ps(__pointx, __say); // x * say
__zz = _mm_mul_ps(__zz, __cay); // z * cay
__zz = _mm_sub_ps(__tmpx, __zz); // zz = x * say - z * cay
// x = xx * xzoomf / std::max((zz + xzoomf), 1000.0);
// y = yy * yzoomf / std::max((zz + yzoomf), 1000.0);
__m128 __tmpzz = _mm_add_ps(__zz, __xzoomf); // zz + xzoomf
__xx = _mm_mul_ps(__xx, __xzoomf); // xx * xzoomf
__pointx = _mm_div_ps(__xx, _mm_max_ps(__tmpzz, __1000)); // x = (xx * xzoomf) / std::max((zz + xzoomf), 1000.0)
__tmpzz = _mm_add_ps(__zz, __yzoomf); // zz + yzoomf
__yy = _mm_mul_ps(__yy, __yzoomf); // yy * yzoomf
__pointy = _mm_div_ps(__yy, _mm_max_ps(__tmpzz, __1000)); // y = yy * yzoomf / std::max((zz + yzoomf), 1000.0);
__pointx = _mm_add_ps(__pointx, __xorg); // x = x + org.x
__pointy = _mm_add_ps(__pointy, __yorg); // y = y + org.y
// round to integer
__m128i __pointxRounded = _mm_cvtps_epi32(__pointx);
__m128i __pointyRounded = _mm_cvtps_epi32(__pointy);
if (i == mPathPointsD4) { // last cycle
for (int k = 0; k < mPathPointsM4; k++) {
mpPathPoints[i * 4 + k].x = __pointxRounded.m128i_i32[3 - k];
mpPathPoints[i * 4 + k].y = __pointyRounded.m128i_i32[3 - k];
}
} else {
for (int k = 0; k < 4; k++) {
mpPathPoints[i * 4 + k].x = __pointxRounded.m128i_i32[3 - k];
mpPathPoints[i * 4 + k].y = __pointyRounded.m128i_i32[3 - k];
}
}
}
}
// CText
CText::CText(const STSStyle& style, CStringW str, int ktype, int kstart, int kend, double scalex, double scaley,
RenderingCaches& renderingCaches)
: CWord(style, str, ktype, kstart, kend, scalex, scaley, renderingCaches)
{
if (m_str == L" ") {
m_fWhiteSpaceChar = true;
}
CTextDimsKey textDimsKey(m_str, m_style);
CTextDims textDims;
if (!renderingCaches.textDimsCache.Lookup(textDimsKey, textDims)) {
CMyFont font(m_style);
m_ascent = font.m_ascent;
m_descent = font.m_descent;
HFONT hOldFont = SelectFont(g_hDC, font);
if (m_style.fontSpacing) {
for (LPCWSTR s = m_str; *s; s++) {
CSize extent;
if (!GetTextExtentPoint32W(g_hDC, s, 1, &extent)) {
SelectFont(g_hDC, hOldFont);
ASSERT(0);
return;
}
m_width += extent.cx + (int)m_style.fontSpacing;
}
// m_width -= (int)m_style.fontSpacing; // TODO: subtract only at the end of the line
} else {
CSize extent;
if (!GetTextExtentPoint32W(g_hDC, m_str, str.GetLength(), &extent)) {
SelectFont(g_hDC, hOldFont);
ASSERT(0);
return;
}
m_width += extent.cx;
}
SelectFont(g_hDC, hOldFont);
textDims.ascent = m_ascent;
textDims.descent = m_descent;
textDims.width = m_width;
renderingCaches.textDimsCache.SetAt(textDimsKey, textDims);
} else {
m_ascent = textDims.ascent;
m_descent = textDims.descent;
m_width = textDims.width;
}
m_ascent = (int)(m_style.fontScaleY / 100 * m_ascent);
m_descent = (int)(m_style.fontScaleY / 100 * m_descent);
m_width = (int)(m_style.fontScaleX / 100 * m_width + 4) >> 3;
}
CWord* CText::Copy()
{
return DEBUG_NEW CText(*this);
}
bool CText::Append(CWord* w)
{
return (dynamic_cast(w) && CWord::Append(w));
}
bool CText::CreatePath()
{
CMyFont font(m_style);
HFONT hOldFont = SelectFont(g_hDC, font);
if (m_style.fontSpacing) {
int width = 0;
bool bFirstPath = true;
for (LPCWSTR s = m_str; *s; s++) {
CSize extent;
if (!GetTextExtentPoint32W(g_hDC, s, 1, &extent)) {
SelectFont(g_hDC, hOldFont);
ASSERT(0);
return false;
}
PartialBeginPath(g_hDC, bFirstPath);
bFirstPath = false;
TextOutW(g_hDC, 0, 0, s, 1);
PartialEndPath(g_hDC, width, 0);
width += extent.cx + (int)m_style.fontSpacing;
}
} else {
CSize extent;
if (!GetTextExtentPoint32W(g_hDC, m_str, m_str.GetLength(), &extent)) {
SelectFont(g_hDC, hOldFont);
ASSERT(0);
return false;
}
BeginPath(g_hDC);
TextOutW(g_hDC, 0, 0, m_str, m_str.GetLength());
EndPath(g_hDC);
}
SelectFont(g_hDC, hOldFont);
return true;
}
// CPolygon
CPolygon::CPolygon(const STSStyle& style, CStringW str, int ktype, int kstart, int kend, double scalex, double scaley, int baseline,
RenderingCaches& renderingCaches)
: CWord(style, str, ktype, kstart, kend, scalex, scaley, renderingCaches)
, m_baseline(baseline)
{
ParseStr();
}
CPolygon::CPolygon(CPolygon& src)
: CWord(src.m_style, src.m_str, src.m_ktype, src.m_kstart, src.m_kend, src.m_scalex, src.m_scaley, src.m_renderingCaches)
, m_baseline(src.m_baseline)
, m_pPolygonPath(src.m_pPolygonPath)
{
m_width = src.m_width;
m_ascent = src.m_ascent;
m_descent = src.m_descent;
}
CPolygon::~CPolygon()
{
}
CWord* CPolygon::Copy()
{
return (DEBUG_NEW CPolygon(*this));
}
bool CPolygon::Append(CWord* w)
{
CPolygon* p = dynamic_cast(w);
if (!p) {
return false;
}
// TODO
return false;
//return true;
}
bool CPolygon::GetPOINT(LPCWSTR& str, POINT& point) const
{
LPWSTR xEnd = nullptr;
LPWSTR yEnd = nullptr;
point.x = std::lround(wcstod(str, &xEnd) * m_scalex) * 64;
if (xEnd <= str) {
return false;
}
point.y = std::lround(wcstod(xEnd, &yEnd) * m_scaley) * 64;
bool ret = yEnd > xEnd;
str = yEnd;
return ret;
}
bool CPolygon::ParseStr()
{
if (m_pPolygonPath && !m_pPolygonPath->typesOrg.IsEmpty()) {
return true;
}
CPolygonPathKey polygonPathKey(m_str, m_scalex, m_scaley);
if (!m_renderingCaches.polygonCache.Lookup(polygonPathKey, m_pPolygonPath)) {
m_pPolygonPath = std::make_shared();
CPoint p;
bool bFoundMove = false;
size_t i, j, lastSplineStart = SIZE_T_ERROR;
auto isValidAction = [](const WCHAR c) {
return c == L'm' || c == L'n' || c == L'l' || c == L'b'
|| c == L's' || c == L'p' || c == L'c';
};
for (LPCWSTR str = m_str; *str;) {
// Trim any leading invalid characters and whitespace
while (*str && !isValidAction(*str)) {
str++;
}
const WCHAR c = *str;
if (*str) {
do {
str++;
} while (isValidAction(*str));
}
switch (c) {
case L'm':
if (!bFoundMove) {
if (m_pPolygonPath->typesOrg.GetCount() > 0) {
// move command not first so we abort
m_pPolygonPath = nullptr;
return false;
}
bFoundMove = true;
}
while (GetPOINT(str, p)) {
m_pPolygonPath->typesOrg.Add(PT_MOVETO);
m_pPolygonPath->pointsOrg.Add(p);
}
break;
case L'n':
while (GetPOINT(str, p)) {
m_pPolygonPath->typesOrg.Add(PT_MOVETONC);
m_pPolygonPath->pointsOrg.Add(p);
}
break;
case L'l':
if (m_pPolygonPath->pointsOrg.GetCount() < 1) {
break;
}
while (GetPOINT(str, p)) {
m_pPolygonPath->typesOrg.Add(PT_LINETO);
m_pPolygonPath->pointsOrg.Add(p);
}
break;
case L'b':
j = m_pPolygonPath->typesOrg.GetCount();
if (j < 1) {
break;
}
while (GetPOINT(str, p)) {
m_pPolygonPath->typesOrg.Add(PT_BEZIERTO);
m_pPolygonPath->pointsOrg.Add(p);
++j;
}
j = m_pPolygonPath->typesOrg.GetCount() - ((m_pPolygonPath->typesOrg.GetCount() - j) % 3);
m_pPolygonPath->typesOrg.SetCount(j);
m_pPolygonPath->pointsOrg.SetCount(j);
break;
case L's':
if (m_pPolygonPath->pointsOrg.GetCount() < 1) {
break;
}
j = lastSplineStart = m_pPolygonPath->typesOrg.GetCount();
i = 3;
while (i-- && GetPOINT(str, p)) {
m_pPolygonPath->typesOrg.Add(PT_BSPLINETO);
m_pPolygonPath->pointsOrg.Add(p);
++j;
}
if (m_pPolygonPath->typesOrg.GetCount() - lastSplineStart < 3) {
m_pPolygonPath->typesOrg.SetCount(lastSplineStart);
m_pPolygonPath->pointsOrg.SetCount(lastSplineStart);
lastSplineStart = SIZE_T_ERROR;
}
// no break
case L'p':
if (m_pPolygonPath->pointsOrg.GetCount() < 3) {
break;
}
while (GetPOINT(str, p)) {
m_pPolygonPath->typesOrg.Add(PT_BSPLINEPATCHTO);
m_pPolygonPath->pointsOrg.Add(p);
}
break;
case L'c':
if (lastSplineStart != SIZE_T_ERROR && lastSplineStart > 0) {
m_pPolygonPath->typesOrg.Add(PT_BSPLINEPATCHTO);
m_pPolygonPath->typesOrg.Add(PT_BSPLINEPATCHTO);
m_pPolygonPath->typesOrg.Add(PT_BSPLINEPATCHTO);
p = m_pPolygonPath->pointsOrg[lastSplineStart - 1]; // we need p for temp storage, because operator [] will return a reference to CPoint and Add() may reallocate its internal buffer (this is true for MFC 7.0 but not for 6.0, hehe)
m_pPolygonPath->pointsOrg.Add(p);
p = m_pPolygonPath->pointsOrg[lastSplineStart];
m_pPolygonPath->pointsOrg.Add(p);
p = m_pPolygonPath->pointsOrg[lastSplineStart + 1];
m_pPolygonPath->pointsOrg.Add(p);
lastSplineStart = SIZE_T_ERROR;
}
break;
default:
break;
}
}
if (!bFoundMove) {
// move command not found so we abort
m_pPolygonPath = nullptr;
return false;
}
int minx = INT_MAX, miny = INT_MAX, maxx = INT_MIN, maxy = INT_MIN;
for (size_t m = 0; m < m_pPolygonPath->typesOrg.GetCount(); m++) {
if (minx > m_pPolygonPath->pointsOrg[m].x) {
minx = m_pPolygonPath->pointsOrg[m].x;
}
if (miny > m_pPolygonPath->pointsOrg[m].y) {
miny = m_pPolygonPath->pointsOrg[m].y;
}
if (maxx < m_pPolygonPath->pointsOrg[m].x) {
maxx = m_pPolygonPath->pointsOrg[m].x;
}
if (maxy < m_pPolygonPath->pointsOrg[m].y) {
maxy = m_pPolygonPath->pointsOrg[m].y;
}
}
m_pPolygonPath->size.SetSize(std::max(maxx - minx, 0), std::max(maxy - miny, 0));
m_renderingCaches.polygonCache.SetAt(polygonPathKey, m_pPolygonPath);
}
m_width = m_pPolygonPath->size.cx;
m_ascent = m_pPolygonPath->size.cy;
int baseline = std::lround(m_scaley * m_baseline) * 64;
m_descent = baseline;
m_ascent -= baseline;
m_width = ((int)(m_style.fontScaleX / 100.0 * m_width) + 4) >> 3;
m_ascent = ((int)(m_style.fontScaleY / 100.0 * m_ascent) + 4) >> 3;
m_descent = ((int)(m_style.fontScaleY / 100.0 * m_descent) + 4) >> 3;
return true;
}
bool CPolygon::CreatePath()
{
int len = m_pPolygonPath ? (int)m_pPolygonPath->typesOrg.GetCount() : 0;
if (len == 0) {
return false;
}
if (mPathPoints != len) {
BYTE* pNewPathTypes = (BYTE*)realloc(mpPathTypes, len * sizeof(BYTE));
if (!pNewPathTypes) {
return false;
}
mpPathTypes = pNewPathTypes;
POINT* pNewPathPoints = (POINT*)realloc(mpPathPoints, len * sizeof(POINT));
if (!pNewPathPoints) {
return false;
}
mpPathPoints = pNewPathPoints;
mPathPoints = len;
}
memcpy(mpPathTypes, m_pPolygonPath->typesOrg.GetData(), len * sizeof(BYTE));
memcpy(mpPathPoints, m_pPolygonPath->pointsOrg.GetData(), len * sizeof(POINT));
return true;
}
// CClipper
CClipper::CClipper(CStringW str, const CSize& size, double scalex, double scaley, bool inverse, const CPoint& cpOffset,
RenderingCaches& renderingCaches)
: CPolygon(STSStyle(), str, 0, 0, 0, scalex, scaley, 0, renderingCaches)
, m_size(size)
, m_inverse(inverse)
, m_cpOffset(cpOffset)
, m_pAlphaMask(nullptr)
{
if (m_size.cx <= 0 || m_size.cy <= 0) {
return;
}
const size_t alphaMaskSize = size_t(m_size.cx) * m_size.cy;
try {
m_pAlphaMask = DEBUG_NEW BYTE[alphaMaskSize];
} catch (CMemoryException* e) {
e->Delete();
return;
}
memset(m_pAlphaMask, (m_inverse ? 0x40 : 0), alphaMaskSize);
Paint(CPoint(0, 0), CPoint(0, 0));
if (!m_pOverlayData) {
return;
}
int w = m_pOverlayData->mOverlayWidth, h = m_pOverlayData->mOverlayHeight;
int x = (m_pOverlayData->mOffsetX + m_cpOffset.x + 4) >> 3, y = (m_pOverlayData->mOffsetY + m_cpOffset.y + 4) >> 3;
int xo = 0, yo = 0;
if (x < 0) {
xo = -x;
w -= -x;
x = 0;
}
if (y < 0) {
yo = -y;
h -= -y;
y = 0;
}
if (x + w > m_size.cx) {
w = m_size.cx - x;
}
if (y + h > m_size.cy) {
h = m_size.cy - y;
}
if (w <= 0 || h <= 0) {
return;
}
const BYTE* src = m_pOverlayData->mpOverlayBufferBody + m_pOverlayData->mOverlayPitch * yo + xo;
BYTE* dst = m_pAlphaMask + m_size.cx * y + x;
if (m_inverse) {
for (ptrdiff_t i = 0; i < h; ++i) {
for (ptrdiff_t wt = 0; wt < w; ++wt) {
dst[wt] = 0x40 - src[wt];
}
src += m_pOverlayData->mOverlayPitch;
dst += m_size.cx;
}
} else {
for (ptrdiff_t i = 0; i < h; ++i) {
memcpy(dst, src, w * sizeof(BYTE));
src += m_pOverlayData->mOverlayPitch;
dst += m_size.cx;
}
}
}
CClipper::~CClipper()
{
SAFE_DELETE_ARRAY(m_pAlphaMask);
}
CWord* CClipper::Copy()
{
return DEBUG_NEW CClipper(m_str, m_size, m_scalex, m_scaley, m_inverse, m_cpOffset, m_renderingCaches);
}
bool CClipper::Append(CWord* w)
{
return false;
}
// CLine
CLine::~CLine()
{
POSITION pos = GetHeadPosition();
while (pos) {
delete GetNext(pos);
}
}
void CLine::Compact()
{
POSITION pos = GetHeadPosition();
while (pos) {
CWord* w = GetNext(pos);
if (!w->m_fWhiteSpaceChar) {
break;
}
m_width -= w->m_width;
delete w;
RemoveHead();
}
pos = GetTailPosition();
while (pos) {
CWord* w = GetPrev(pos);
if (!w->m_fWhiteSpaceChar) {
break;
}
m_width -= w->m_width;
delete w;
RemoveTail();
}
if (IsEmpty()) {
return;
}
CLine l;
l.AddTailList(this);
RemoveAll();
CWord* last = nullptr;
pos = l.GetHeadPosition();
while (pos) {
CWord* w = l.GetNext(pos);
if (!last || !last->Append(w)) {
AddTail(last = w->Copy());
}
}
m_ascent = m_descent = m_borderX = m_borderY = 0;
pos = GetHeadPosition();
while (pos) {
CWord* w = GetNext(pos);
if (m_ascent < w->m_ascent) {
m_ascent = w->m_ascent;
}
if (m_descent < w->m_descent) {
m_descent = w->m_descent;
}
if (m_borderX < w->m_style.outlineWidthX) {
m_borderX = (int)(w->m_style.outlineWidthX + 0.5);
}
if (m_borderY < w->m_style.outlineWidthY) {
m_borderY = (int)(w->m_style.outlineWidthY + 0.5);
}
}
}
CRect CLine::PaintShadow(SubPicDesc& spd, CRect& clipRect, BYTE* pAlphaMask, CPoint p, CPoint org, int time, int alpha)
{
CRect bbox(0, 0, 0, 0);
POSITION pos = GetHeadPosition();
while (pos) {
CWord* w = GetNext(pos);
if (w->m_fLineBreak) {
return bbox; // should not happen since this class is just a line of text without any breaks
}
if (w->m_style.shadowDepthX != 0 || w->m_style.shadowDepthY != 0) {
int x = p.x + (int)(w->m_style.shadowDepthX + 0.5);
int y = p.y + m_ascent - w->m_ascent + (int)(w->m_style.shadowDepthY + 0.5);
DWORD a = 0xff - w->m_style.alpha[3];
if (alpha > 0) {
a = a * (0xff - static_cast(alpha)) / 0xff;
}
COLORREF shadow = revcolor(w->m_style.colors[3]) | (a << 24);
DWORD sw[6] = {shadow, DWORD_MAX};
sw[0] = ColorConvTable::ColorCorrection(sw[0]);
w->Paint(CPoint(x, y), org);
if (w->m_style.borderStyle == 0) {
bbox |= w->Draw(spd, clipRect, pAlphaMask, x, y, sw,
w->m_ktype > 0 || w->m_style.alpha[0] < 0xff,
(w->m_style.outlineWidthX + w->m_style.outlineWidthY > 0) && !(w->m_ktype == 2 && time < w->m_kstart));
} else if (w->m_style.borderStyle == 1 && w->m_pOpaqueBox) {
bbox |= w->m_pOpaqueBox->Draw(spd, clipRect, pAlphaMask, x, y, sw, true, false);
}
}
p.x += w->m_width;
}
return bbox;
}
CRect CLine::PaintOutline(SubPicDesc& spd, CRect& clipRect, BYTE* pAlphaMask, CPoint p, CPoint org, int time, int alpha)
{
CRect bbox(0, 0, 0, 0);
POSITION pos = GetHeadPosition();
while (pos) {
CWord* w = GetNext(pos);
if (w->m_fLineBreak) {
return bbox; // should not happen since this class is just a line of text without any breaks
}
if ((w->m_style.outlineWidthX + w->m_style.outlineWidthY > 0 || w->m_style.borderStyle == 1) && !(w->m_ktype == 2 && time < w->m_kstart)) {
int x = p.x;
int y = p.y + m_ascent - w->m_ascent;
DWORD aoutline = w->m_style.alpha[2];
if (alpha > 0) {
aoutline += alpha * (0xff - w->m_style.alpha[2]) / 0xff;
}
COLORREF outline = revcolor(w->m_style.colors[2]) | ((0xff - aoutline) << 24);
DWORD sw[6] = {outline, DWORD_MAX};
sw[0] = ColorConvTable::ColorCorrection(sw[0]);
w->Paint(CPoint(x, y), org);
if (w->m_style.borderStyle == 0) {
bbox |= w->Draw(spd, clipRect, pAlphaMask, x, y, sw, !w->m_style.alpha[0] && !w->m_style.alpha[1] && !alpha, true);
} else if (w->m_style.borderStyle == 1 && w->m_pOpaqueBox) {
bbox |= w->m_pOpaqueBox->Draw(spd, clipRect, pAlphaMask, x, y, sw, true, false);
}
}
p.x += w->m_width;
}
return bbox;
}
CRect CLine::PaintBody(SubPicDesc& spd, CRect& clipRect, BYTE* pAlphaMask, CPoint p, CPoint org, int time, int alpha)
{
CRect bbox(0, 0, 0, 0);
POSITION pos = GetHeadPosition();
while (pos) {
CWord* w = GetNext(pos);
if (w->m_fLineBreak) {
return bbox; // should not happen since this class is just a line of text without any breaks
}
int x = p.x;
int y = p.y + m_ascent - w->m_ascent;
// colors
DWORD aprimary = w->m_style.alpha[0];
DWORD asecondary = w->m_style.alpha[1];
if (alpha > 0) {
aprimary += alpha * (0xff - w->m_style.alpha[0]) / 0xff;
asecondary += alpha * (0xff - w->m_style.alpha[1]) / 0xff;
}
COLORREF primary = revcolor(w->m_style.colors[0]) | ((0xff - aprimary) << 24);
COLORREF secondary = revcolor(w->m_style.colors[1]) | ((0xff - asecondary) << 24);
DWORD sw[6] = {primary, 0, secondary};
// karaoke
double t = 0.0;
if (w->m_ktype == 0 || w->m_ktype == 2) {
t = time < w->m_kstart ? 0.0 : 1.0;
} else if (w->m_ktype == 1) {
if (time < w->m_kstart) {
t = 0.0;
} else if (time < w->m_kend) {
t = 1.0 * (time - w->m_kstart) / (w->m_kend - w->m_kstart);
double angle = fmod(w->m_style.fontAngleZ, 360.0);
if (angle > 90 && angle < 270) {
t = 1.0 - t;
COLORREF tmp = sw[0];
sw[0] = sw[2];
sw[2] = tmp;
}
} else {
t = 1.0;
}
}
if (t >= 1.0) {
sw[1] = DWORD_MAX;
}
// move dividerpoint
int bluradjust = 0;
if (w->m_style.fGaussianBlur > 0) {
bluradjust += (int)(w->m_style.fGaussianBlur * 3 * 8 + 0.5) | 1;
}
if (w->m_style.fBlur) {
bluradjust += 8;
}
w->Paint(CPoint(x, y), org);
sw[0] = ColorConvTable::ColorCorrection(sw[0]);
sw[2] = ColorConvTable::ColorCorrection(sw[2]);
sw[3] = (int)(w->m_style.outlineWidthX + t * w->getOverlayWidth() + t * bluradjust) >> 3;
sw[4] = sw[2];
sw[5] = 0x00ffffff;
bbox |= w->Draw(spd, clipRect, pAlphaMask, x, y, sw, true, false);
p.x += w->m_width;
}
return bbox;
}
// CSubtitle
CSubtitle::CSubtitle(RenderingCaches& renderingCaches)
: m_renderingCaches(renderingCaches)
, m_scrAlignment(0)
, m_wrapStyle(0)
, m_fAnimated(false)
, m_bIsAnimated(false)
, m_relativeTo(STSStyle::VIDEO)
, m_pClipper(nullptr)
, m_topborder(0)
, m_bottomborder(0)
, m_clipInverse(false)
, m_scalex(1.0)
, m_scaley(1.0)
{
ZeroMemory(m_effects, sizeof(Effect*)*EF_NUMBEROFEFFECTS);
}
CSubtitle::~CSubtitle()
{
Empty();
}
void CSubtitle::Empty()
{
POSITION pos = GetHeadPosition();
while (pos) {
delete GetNext(pos);
}
pos = m_words.GetHeadPosition();
while (pos) {
delete m_words.GetNext(pos);
}
EmptyEffects();
SAFE_DELETE(m_pClipper);
}
void CSubtitle::EmptyEffects()
{
for (ptrdiff_t i = 0; i < EF_NUMBEROFEFFECTS; i++) {
SAFE_DELETE(m_effects[i]);
}
}
int CSubtitle::GetFullWidth()
{
int width = 0;
POSITION pos = m_words.GetHeadPosition();
while (pos) {
width += m_words.GetNext(pos)->m_width;
}
return width;
}
int CSubtitle::GetFullLineWidth(POSITION pos)
{
int width = 0;
while (pos) {
CWord* w = m_words.GetNext(pos);
if (w->m_fLineBreak) {
break;
}
width += w->m_width;
}
return width;
}
int CSubtitle::GetWrapWidth(POSITION pos, int maxwidth)
{
if (m_wrapStyle == 0 || m_wrapStyle == 3) {
if (maxwidth > 0) {
int fullwidth = GetFullLineWidth(pos);
int minwidth = fullwidth / ((abs(fullwidth) / maxwidth) + 1);
int width = 0, wordwidth = 0;
while (pos && width < minwidth) {
CWord* w = m_words.GetNext(pos);
wordwidth = w->m_width;
if (abs(width + wordwidth) < abs(maxwidth)) {
width += wordwidth;
}
}
if (m_wrapStyle == 3 && width < fullwidth && fullwidth - width + wordwidth < maxwidth) {
width -= wordwidth;
}
maxwidth = width;
}
} else if (m_wrapStyle == 1) {
// maxwidth = maxwidth;
} else if (m_wrapStyle == 2) {
maxwidth = INT_MAX;
}
return maxwidth;
}
CLine* CSubtitle::GetNextLine(POSITION& pos, int maxwidth)
{
if (pos == nullptr) {
return nullptr;
}
CLine* ret;
try {
ret = DEBUG_NEW CLine();
} catch (CMemoryException* e) {
e->Delete();
return nullptr;
}
ret->m_width = ret->m_ascent = ret->m_descent = ret->m_borderX = ret->m_borderY = 0;
maxwidth = GetWrapWidth(pos, maxwidth);
bool fEmptyLine = true;
while (pos) {
CWord* w = m_words.GetNext(pos);
if (ret->m_ascent < w->m_ascent) {
ret->m_ascent = w->m_ascent;
}
if (ret->m_descent < w->m_descent) {
ret->m_descent = w->m_descent;
}
if (ret->m_borderX < w->m_style.outlineWidthX) {
ret->m_borderX = (int)(w->m_style.outlineWidthX + 0.5);
}
if (ret->m_borderY < w->m_style.outlineWidthY) {
ret->m_borderY = (int)(w->m_style.outlineWidthY + 0.5);
}
if (w->m_fLineBreak) {
if (fEmptyLine) {
ret->m_ascent /= 2;
ret->m_descent /= 2;
ret->m_borderX = ret->m_borderY = 0;
}
ret->Compact();
return ret;
}
fEmptyLine = false;
bool fWSC = w->m_fWhiteSpaceChar;
int width = w->m_width;
POSITION pos2 = pos;
while (pos2) {
if (m_words.GetAt(pos2)->m_fWhiteSpaceChar != fWSC
|| m_words.GetAt(pos2)->m_fLineBreak) {
break;
}
CWord* w2 = m_words.GetNext(pos2);
width += w2->m_width;
}
if ((ret->m_width += width) <= maxwidth || ret->IsEmpty()) {
ret->AddTail(w->Copy());
while (pos != pos2) {
ret->AddTail(m_words.GetNext(pos)->Copy());
}
pos = pos2;
} else {
if (pos) {
m_words.GetPrev(pos);
} else {
pos = m_words.GetTailPosition();
}
ret->m_width -= width;
break;
}
}
ret->Compact();
return ret;
}
void CSubtitle::CreateClippers(CSize size)
{
size.cx >>= 3;
size.cy >>= 3;
auto createClipper = [this](const CSize & size) {
ASSERT(!m_pClipper);
CStringW str;
str.Format(L"m %d %d l %d %d %d %d %d %d", 0, 0, size.cx, 0, size.cx, size.cy, 0, size.cy);
try {
m_pClipper = DEBUG_NEW CClipper(str, size, 1.0, 1.0, false, CPoint(0, 0), m_renderingCaches);
if (!m_pClipper->m_pAlphaMask) {
SAFE_DELETE(m_pClipper);
}
} catch (CMemoryException* e) {
e->Delete();
}
return !!m_pClipper;
};
if (m_effects[EF_BANNER] && m_effects[EF_BANNER]->param[2]) {
if (!m_pClipper && !createClipper(size)) {
return;
}
int width = m_effects[EF_BANNER]->param[2];
int w = size.cx, h = size.cy;
int da = (64 << 8) / width;
BYTE* am = m_pClipper->m_pAlphaMask;
for (ptrdiff_t j = 0; j < h; j++, am += w) {
int a = 0;
int k = std::min(width, w);
for (ptrdiff_t i = 0; i < k; i++, a += da) {
am[i] = BYTE((am[i] * a) >> 14);
}
a = 0x40 << 8;
k = w - width;
if (k < 0) {
a -= -k * da;
k = 0;
}
for (ptrdiff_t i = k; i < w; i++, a -= da) {
am[i] = BYTE((am[i] * a) >> 14);
}
}
} else if (m_effects[EF_SCROLL] && m_effects[EF_SCROLL]->param[4]) {
if (!m_pClipper && !createClipper(size)) {
return;
}
int height = m_effects[EF_SCROLL]->param[4];
int w = size.cx, h = size.cy;
int da = (64 << 8) / height;
int a = 0;
int k = m_effects[EF_SCROLL]->param[0] >> 3;
int l = k + height;
if (k < 0) {
a += -k * da;
k = 0;
}
if (l > h) {
l = h;
}
if (k < h) {
BYTE* am = &m_pClipper->m_pAlphaMask[k * w];
ZeroMemory(m_pClipper->m_pAlphaMask, am - m_pClipper->m_pAlphaMask);
for (ptrdiff_t j = k; j < l; j++, a += da) {
for (ptrdiff_t i = 0; i < w; i++, am++) {
*am = BYTE(((*am) * a) >> 14);
}
}
}
da = -(64 << 8) / height;
a = 0x40 << 8;
l = m_effects[EF_SCROLL]->param[1] >> 3;
k = l - height;
if (k < 0) {
a += -k * da;
k = 0;
}
if (l > h) {
l = h;
}
if (k < h) {
BYTE* am = &m_pClipper->m_pAlphaMask[k * w];
int j = k;
for (; j < l; j++, a += da) {
for (ptrdiff_t i = 0; i < w; i++, am++) {
*am = BYTE(((*am) * a) >> 14);
}
}
ZeroMemory(am, (h - j)*w);
}
}
}
void CSubtitle::MakeLines(CSize size, const CRect& marginRect)
{
CSize spaceNeeded(0, 0);
bool fFirstLine = true;
m_topborder = m_bottomborder = 0;
CLine* l = nullptr;
POSITION pos = m_words.GetHeadPosition();
while (pos) {
l = GetNextLine(pos, size.cx - marginRect.left - marginRect.right);
if (!l) {
break;
}
if (fFirstLine) {
m_topborder = l->m_borderY;
fFirstLine = false;
}
spaceNeeded.cx = std::max(l->m_width + l->m_borderX, spaceNeeded.cx);
spaceNeeded.cy += l->m_ascent + l->m_descent;
AddTail(l);
}
if (l) {
m_bottomborder = l->m_borderY;
}
m_rect = CRect(
CPoint((m_scrAlignment % 3) == 1 ? marginRect.left
: (m_scrAlignment % 3) == 2 ? (marginRect.left + (size.cx - marginRect.right) - spaceNeeded.cx + 1) / 2
: (size.cx - marginRect.right - spaceNeeded.cx),
m_scrAlignment <= 3 ? (size.cy - marginRect.bottom - spaceNeeded.cy)
: m_scrAlignment <= 6 ? (marginRect.top + (size.cy - marginRect.bottom) - spaceNeeded.cy + 1) / 2
: marginRect.top),
spaceNeeded);
}
// CScreenLayoutAllocator
void CScreenLayoutAllocator::Empty()
{
m_subrects.RemoveAll();
}
void CScreenLayoutAllocator::AdvanceToSegment(int segment, const CAtlArray& sa)
{
POSITION pos = m_subrects.GetHeadPosition();
while (pos) {
POSITION prev = pos;
SubRect& sr = m_subrects.GetNext(pos);
bool fFound = false;
if (abs(sr.segment - segment) <= 1) { // using abs() makes it possible to play the subs backwards, too :)
for (size_t i = 0; i < sa.GetCount() && !fFound; i++) {
if (sa[i] == sr.entry) {
sr.segment = segment;
fFound = true;
}
}
}
if (!fFound) {
m_subrects.RemoveAt(prev);
}
}
}
CRect CScreenLayoutAllocator::AllocRect(const CSubtitle* s, int segment, int entry, int layer, int collisions)
{
// TODO: handle collisions == 1 (reversed collisions)
POSITION pos = m_subrects.GetHeadPosition();
while (pos) {
SubRect& sr = m_subrects.GetNext(pos);
if (sr.segment == segment && sr.entry == entry) {
return (sr.r + CRect(0, -s->m_topborder, 0, -s->m_bottomborder));
}
}
CRect r = s->m_rect + CRect(0, s->m_topborder, 0, s->m_bottomborder);
bool fSearchDown = s->m_scrAlignment > 3;
bool fOK;
do {
fOK = true;
pos = m_subrects.GetHeadPosition();
while (pos) {
SubRect& sr = m_subrects.GetNext(pos);
if (layer == sr.layer && !(r & sr.r).IsRectEmpty()) {
if (fSearchDown) {
r.bottom = sr.r.bottom + r.Height();
r.top = sr.r.bottom;
} else {
r.top = sr.r.top - r.Height();
r.bottom = sr.r.top;
}
fOK = false;
}
}
} while (!fOK);
SubRect sr;
sr.r = r;
sr.segment = segment;
sr.entry = entry;
sr.layer = layer;
m_subrects.AddTail(sr);
return (sr.r + CRect(0, -s->m_topborder, 0, -s->m_bottomborder));
}
// CRenderedTextSubtitle
CAtlMap> CRenderedTextSubtitle::s_SSATagCmds;
CRenderedTextSubtitle::CRenderedTextSubtitle(CCritSec* pLock)
: CSubPicProviderImpl(pLock)
, m_time(0)
, m_delay(0)
, m_animStart(0)
, m_animEnd(0)
, m_animAccel(0.0)
, m_ktype(0)
, m_kstart(0)
, m_kend(0)
, m_nPolygon(0)
, m_polygonBaselineOffset(0)
, m_bOverrideStyle(false)
, m_bOverridePlacement(false)
, m_overridePlacement(50, 90)
{
m_size = CSize(0, 0);
if (g_hDC_refcnt == 0) {
g_hDC = CreateCompatibleDC(nullptr);
SetBkMode(g_hDC, TRANSPARENT);
SetTextColor(g_hDC, 0xffffff);
SetMapMode(g_hDC, MM_TEXT);
}
g_hDC_refcnt++;
if (s_SSATagCmds.IsEmpty()) {
s_SSATagCmds[L"1c"] = SSA_1c;
s_SSATagCmds[L"2c"] = SSA_2c;
s_SSATagCmds[L"3c"] = SSA_3c;
s_SSATagCmds[L"4c"] = SSA_4c;
s_SSATagCmds[L"1a"] = SSA_1a;
s_SSATagCmds[L"2a"] = SSA_2a;
s_SSATagCmds[L"3a"] = SSA_3a;
s_SSATagCmds[L"4a"] = SSA_4a;
s_SSATagCmds[L"alpha"] = SSA_alpha;
s_SSATagCmds[L"an"] = SSA_an;
s_SSATagCmds[L"a"] = SSA_a;
s_SSATagCmds[L"blur"] = SSA_blur;
s_SSATagCmds[L"bord"] = SSA_bord;
s_SSATagCmds[L"be"] = SSA_be;
s_SSATagCmds[L"b"] = SSA_b;
s_SSATagCmds[L"clip"] = SSA_clip;
s_SSATagCmds[L"iclip"] = SSA_iclip;
s_SSATagCmds[L"c"] = SSA_c;
s_SSATagCmds[L"fade"] = SSA_fade;
s_SSATagCmds[L"fad"] = SSA_fade;
s_SSATagCmds[L"fax"] = SSA_fax;
s_SSATagCmds[L"fay"] = SSA_fay;
s_SSATagCmds[L"fe"] = SSA_fe;
s_SSATagCmds[L"fn"] = SSA_fn;
s_SSATagCmds[L"frx"] = SSA_frx;
s_SSATagCmds[L"fry"] = SSA_fry;
s_SSATagCmds[L"frz"] = SSA_frz;
s_SSATagCmds[L"fr"] = SSA_fr;
s_SSATagCmds[L"fscx"] = SSA_fscx;
s_SSATagCmds[L"fscy"] = SSA_fscy;
s_SSATagCmds[L"fsc"] = SSA_fsc;
s_SSATagCmds[L"fsp"] = SSA_fsp;
s_SSATagCmds[L"fs"] = SSA_fs;
s_SSATagCmds[L"i"] = SSA_i;
s_SSATagCmds[L"kt"] = SSA_kt;
s_SSATagCmds[L"kf"] = SSA_kf;
s_SSATagCmds[L"K"] = SSA_K;
s_SSATagCmds[L"ko"] = SSA_ko;
s_SSATagCmds[L"k"] = SSA_k;
s_SSATagCmds[L"move"] = SSA_move;
s_SSATagCmds[L"org"] = SSA_org;
s_SSATagCmds[L"pbo"] = SSA_pbo;
s_SSATagCmds[L"pos"] = SSA_pos;
s_SSATagCmds[L"p"] = SSA_p;
s_SSATagCmds[L"q"] = SSA_q;
s_SSATagCmds[L"r"] = SSA_r;
s_SSATagCmds[L"shad"] = SSA_shad;
s_SSATagCmds[L"s"] = SSA_s;
s_SSATagCmds[L"t"] = SSA_t;
s_SSATagCmds[L"u"] = SSA_u;
s_SSATagCmds[L"xbord"] = SSA_xbord;
s_SSATagCmds[L"xshad"] = SSA_xshad;
s_SSATagCmds[L"ybord"] = SSA_ybord;
s_SSATagCmds[L"yshad"] = SSA_yshad;
}
}
CRenderedTextSubtitle::~CRenderedTextSubtitle()
{
Deinit();
g_hDC_refcnt--;
if (g_hDC_refcnt == 0) {
DeleteDC(g_hDC);
}
}
void CRenderedTextSubtitle::Copy(CSimpleTextSubtitle& sts)
{
__super::Copy(sts);
m_size = CSize(0, 0);
if (CRenderedTextSubtitle* pRTS = dynamic_cast(&sts)) {
m_size = pRTS->m_size;
}
}
void CRenderedTextSubtitle::Empty()
{
Deinit();
__super::Empty();
}
void CRenderedTextSubtitle::OnChanged()
{
__super::OnChanged();
POSITION pos = m_subtitleCache.GetStartPosition();
while (pos) {
int i;
CSubtitle* s;
m_subtitleCache.GetNextAssoc(pos, i, s);
delete s;
}
m_subtitleCache.RemoveAll();
m_sla.Empty();
}
bool CRenderedTextSubtitle::Init(CSize size, const CRect& vidrect)
{
Deinit();
m_size = CSize(size.cx * 8, size.cy * 8);
m_vidrect = CRect(vidrect.left * 8, vidrect.top * 8, vidrect.right * 8, vidrect.bottom * 8);
m_sla.Empty();
return true;
}
void CRenderedTextSubtitle::Deinit()
{
POSITION pos = m_subtitleCache.GetStartPosition();
while (pos) {
int i;
CSubtitle* s;
m_subtitleCache.GetNextAssoc(pos, i, s);
delete s;
}
m_subtitleCache.RemoveAll();
m_sla.Empty();
m_size = CSize(0, 0);
m_vidrect.SetRectEmpty();
}
void CRenderedTextSubtitle::ParseEffect(CSubtitle* sub, CString str)
{
str.Trim();
if (!sub || str.IsEmpty()) {
return;
}
const TCHAR* s = _tcschr(str, ';');
if (!s) {
s = (LPTSTR)(LPCTSTR)str;
s += str.GetLength() - 1;
}
s++;
CString effect = str.Left(int(s - str));
if (!effect.CompareNoCase(_T("Banner;"))) {
sub->m_bIsAnimated = true;
int delay, lefttoright = 0, fadeawaywidth = 0;
if (_stscanf_s(s, _T("%d;%d;%d"), &delay, &lefttoright, &fadeawaywidth) < 1) {
return;
}
Effect* e;
try {
e = DEBUG_NEW Effect;
} catch (CMemoryException* e) {
e->Delete();
return;
}
sub->m_effects[e->type = EF_BANNER] = e;
e->param[0] = std::lround(std::max(1.0 * delay / sub->m_scalex, 1.0));
e->param[1] = lefttoright;
e->param[2] = std::lround(sub->m_scalex * fadeawaywidth);
sub->m_wrapStyle = 2;
} else if (!effect.CompareNoCase(_T("Scroll up;")) || !effect.CompareNoCase(_T("Scroll down;"))) {
sub->m_bIsAnimated = true;
int top, bottom, delay, fadeawayheight = 0;
if (_stscanf_s(s, _T("%d;%d;%d;%d"), &top, &bottom, &delay, &fadeawayheight) < 3) {
return;
}
if (top > bottom) {
int tmp = top;
top = bottom;
bottom = tmp;
}
Effect* e;
try {
e = DEBUG_NEW Effect;
} catch (CMemoryException* e) {
e->Delete();
return;
}
sub->m_effects[e->type = EF_SCROLL] = e;
e->param[0] = std::lround(sub->m_scaley * top * 8.0);
e->param[1] = std::lround(sub->m_scaley * bottom * 8.0);
e->param[2] = std::lround(std::max(double(delay) / sub->m_scaley, 1.0));
e->param[3] = (effect.GetLength() == 12);
e->param[4] = std::lround(sub->m_scaley * fadeawayheight);
}
}
void CRenderedTextSubtitle::ParseString(CSubtitle* sub, CStringW str, STSStyle& style)
{
if (!sub) {
return;
}
str.Replace(L"\\N", L"\n");
str.Replace(L"\\n", (sub->m_wrapStyle < 2 || sub->m_wrapStyle == 3) ? L" " : L"\n");
str.Replace(L"\\h", L"\x00A0");
for (int i = 0, j = 0, len = str.GetLength(); j <= len; j++) {
WCHAR c = str[j];
if (c != L'\n' && c != L' ' && c != L'\x00A0' && c != 0) {
continue;
}
if (i < j) {
if (CWord* w = DEBUG_NEW CText(style, str.Mid(i, j - i), m_ktype, m_kstart, m_kend, sub->m_scalex, sub->m_scaley, m_renderingCaches)) {
sub->m_words.AddTail(w);
m_kstart = m_kend;
}
}
if (c == L'\n') {
if (CWord* w = DEBUG_NEW CText(style, CStringW(), m_ktype, m_kstart, m_kend, sub->m_scalex, sub->m_scaley, m_renderingCaches)) {
sub->m_words.AddTail(w);
m_kstart = m_kend;
}
} else if (c == L' ' || c == L'\x00A0') {
if (CWord* w = DEBUG_NEW CText(style, CStringW(c), m_ktype, m_kstart, m_kend, sub->m_scalex, sub->m_scaley, m_renderingCaches)) {
sub->m_words.AddTail(w);
m_kstart = m_kend;
}
}
i = j + 1;
}
return;
}
void CRenderedTextSubtitle::ParsePolygon(CSubtitle* sub, CStringW str, STSStyle& style)
{
if (!sub || !str.GetLength() || !m_nPolygon) {
return;
}
if (CWord* w = DEBUG_NEW CPolygon(style, str, m_ktype, m_kstart, m_kend,
sub->m_scalex / (1 << (m_nPolygon - 1)), sub->m_scaley / (1 << (m_nPolygon - 1)),
m_polygonBaselineOffset,
m_renderingCaches)) {
sub->m_words.AddTail(w);
m_kstart = m_kend;
}
}
bool CRenderedTextSubtitle::ParseSSATag(SSATagsList& tagsList, const CStringW& str)
{
if (m_renderingCaches.SSATagsCache.Lookup(str, tagsList)) {
return true;
}
int nTags = 0, nUnrecognizedTags = 0;
tagsList.reset(DEBUG_NEW CAtlList());
for (int i = 0, j; (j = str.Find(L'\\', i)) >= 0; i = j) {
int jOld;
// find the end of the current tag or the start of its parameters
for (jOld = ++j; str[j] && str[j] != L'(' && str[j] != L'\\'; ++j) {
;
}
CStringW cmd = str.Mid(jOld, j - jOld);
cmd.Trim();
if (cmd.IsEmpty()) {
continue;
}
nTags++;
SSATag tag;
tag.cmd = SSA_unknown;
for (int cmdLength = std::min(SSA_CMD_MAX_LENGTH, cmd.GetLength()), cmdLengthMin = SSA_CMD_MIN_LENGTH; cmdLength >= cmdLengthMin; cmdLength--) {
if (s_SSATagCmds.Lookup(cmd.Left(cmdLength), tag.cmd)) {
break;
}
}
if (tag.cmd == SSA_unknown) {
nUnrecognizedTags++;
continue;
}
if (str[j] == L'(') {
// complex tags search
int br = 1; // 1 bracket
// find the end of the parameters
for (jOld = ++j; str[j] && br > 0; ++j) {
if (str[j] == L'(') {
br++;
} else if (str[j] == L')') {
br--;
}
if (br == 0) {
break;
}
}
CStringW param = str.Mid(jOld, j - jOld);
param.Trim();
while (!param.IsEmpty()) {
int k = param.Find(L','), l = param.Find(L'\\');
if (k >= 0 && (l < 0 || k < l)) {
CStringW s = param.Left(k).Trim();
if (!s.IsEmpty()) {
tag.params.Add(s);
}
param = k + 1 < param.GetLength() ? param.Mid(k + 1) : L"";
} else {
param.Trim();
if (!param.IsEmpty()) {
tag.params.Add(param);
}
param.Empty();
}
}
}
switch (tag.cmd) {
case SSA_1c:
case SSA_2c:
case SSA_3c:
case SSA_4c:
case SSA_1a:
case SSA_2a:
case SSA_3a:
case SSA_4a:
if (cmd.GetLength() > 2) {
tag.paramsInt.Add(wcstol(cmd.Mid(2).Trim(L"&H"), nullptr, 16));
}
break;
case SSA_alpha:
if (cmd.GetLength() > 5) {
tag.paramsInt.Add(wcstol(cmd.Mid(5).Trim(L"&H"), nullptr, 16));
}
break;
case SSA_an:
case SSA_be:
case SSA_fe:
case SSA_kt:
case SSA_kf:
case SSA_ko:
if (cmd.GetLength() > 2) {
tag.paramsInt.Add(wcstol(cmd.Mid(2), nullptr, 10));
}
break;
case SSA_fn:
tag.params.Add(cmd.Mid(2));
break;
case SSA_fr:
if (cmd.GetLength() > 2) {
tag.paramsReal.Add(wcstod(cmd.Mid(2), nullptr));
}
break;
case SSA_fs:
if (cmd.GetLength() > 2) {
int s = 2;
if (cmd[s] == L'+' || cmd[s] == L'-') {
tag.params.Add(cmd.Mid(s, 1));
}
tag.paramsInt.Add(wcstol(cmd.Mid(s), nullptr, 10));
}
break;
case SSA_a:
case SSA_b:
case SSA_i:
case SSA_k:
case SSA_K:
case SSA_p:
case SSA_q:
case SSA_s:
case SSA_u:
if (cmd.GetLength() > 1) {
tag.paramsInt.Add(wcstol(cmd.Mid(1), nullptr, 10));
}
break;
case SSA_r:
tag.params.Add(cmd.Mid(1));
break;
case SSA_blur:
case SSA_bord:
case SSA_fscx:
case SSA_fscy:
case SSA_shad:
if (cmd.GetLength() > 4) {
tag.paramsReal.Add(wcstod(cmd.Mid(4), nullptr));
}
break;
case SSA_clip:
case SSA_iclip: {
size_t nParams = tag.params.GetCount();
if (nParams == 2) {
tag.paramsInt.Add(wcstol(tag.params[0], nullptr, 10));
tag.params.RemoveAt(0);
} else if (nParams == 4) {
for (size_t i = 0; i < nParams; i++) {
tag.paramsInt.Add(wcstol(tag.params[i], nullptr, 10));
}
tag.params.RemoveAll();
}
}
break;
case SSA_fade: {
size_t nParams = tag.params.GetCount();
if (nParams == 7 || nParams == 2) {
for (size_t i = 0; i < nParams; i++) {
tag.paramsInt.Add(wcstol(tag.params[i], nullptr, 10));
}
tag.params.RemoveAll();
}
}
break;
case SSA_move: {
size_t nParams = tag.params.GetCount();
if (nParams == 4 || nParams == 6) {
for (size_t i = 0; i < 4; i++) {
tag.paramsReal.Add(wcstod(tag.params[i], nullptr));
}
for (size_t i = 4; i < nParams; i++) {
tag.paramsInt.Add(wcstol(tag.params[i], nullptr, 10));
}
tag.params.RemoveAll();
}
}
break;
case SSA_org:
case SSA_pos: {
size_t nParams = tag.params.GetCount();
if (nParams == 2) {
for (size_t i = 0; i < nParams; i++) {
tag.paramsReal.Add(wcstod(tag.params[i], nullptr));
}
tag.params.RemoveAll();
}
}
break;
case SSA_c:
if (cmd.GetLength() > 1) {
tag.paramsInt.Add(wcstol(cmd.Mid(1).Trim(L"&H"), nullptr, 16));
}
break;
case SSA_frx:
case SSA_fry:
case SSA_frz:
case SSA_fax:
case SSA_fay:
case SSA_fsc:
case SSA_fsp:
if (cmd.GetLength() > 3) {
tag.paramsReal.Add(wcstod(cmd.Mid(3), nullptr));
}
break;
case SSA_pbo:
if (cmd.GetLength() > 3) {
tag.paramsInt.Add(wcstol(cmd.Mid(3), nullptr, 10));
}
break;
case SSA_t: {
size_t nParams = tag.params.GetCount();
if (nParams >= 1 && nParams <= 4) {
if (nParams == 2) {
tag.paramsReal.Add(wcstod(tag.params[0], nullptr));
} else if (nParams == 3) {
tag.paramsReal.Add(wcstod(tag.params[0], nullptr));
tag.paramsReal.Add(wcstod(tag.params[1], nullptr));
} else if (nParams == 4) {
tag.paramsInt.Add(wcstol(tag.params[0], nullptr, 10));
tag.paramsInt.Add(wcstol(tag.params[1], nullptr, 10));
tag.paramsReal.Add(wcstod(tag.params[2], nullptr));
}
ParseSSATag(tag.subTagsList, tag.params[nParams - 1]);
}
tag.params.RemoveAll();
}
break;
case SSA_xbord:
case SSA_xshad:
case SSA_ybord:
case SSA_yshad:
if (cmd.GetLength() > 5) {
tag.paramsReal.Add(wcstod(cmd.Mid(5), nullptr));
}
break;
}
tagsList->AddTail(tag);
}
m_renderingCaches.SSATagsCache.SetAt(str, tagsList);
//return (nUnrecognizedTags < nTags);
return true; // there are people keeping comments inside {}, lets make them happy now
}
bool CRenderedTextSubtitle::CreateSubFromSSATag(CSubtitle* sub, const SSATagsList& tagsList,
STSStyle& style, STSStyle& org, bool fAnimate /*= false*/)
{
if (!sub || !tagsList) {
return false;
}
POSITION pos = tagsList->GetHeadPosition();
while (pos) {
const SSATag& tag = tagsList->GetNext(pos);
// TODO: call ParseStyleModifier(cmd, params, ..) and move the rest there
switch (tag.cmd) {
case SSA_1c:
case SSA_2c:
case SSA_3c:
case SSA_4c: {
int k = tag.cmd - SSA_1c;
if (!tag.paramsInt.IsEmpty()) {
DWORD c = tag.paramsInt[0];
style.colors[k] = (((int)CalcAnimation(c & 0xff, style.colors[k] & 0xff, fAnimate)) & 0xff
| ((int)CalcAnimation(c & 0xff00, style.colors[k] & 0xff00, fAnimate)) & 0xff00
| ((int)CalcAnimation(c & 0xff0000, style.colors[k] & 0xff0000, fAnimate)) & 0xff0000);
} else {
style.colors[k] = org.colors[k];
}
}
break;
case SSA_1a:
case SSA_2a:
case SSA_3a:
case SSA_4a: {
int k = tag.cmd - SSA_1a;
style.alpha[k] = !tag.paramsInt.IsEmpty()
? (BYTE)CalcAnimation(tag.paramsInt[0] & 0xff, style.alpha[k], fAnimate)
: org.alpha[k];
}
break;
case SSA_alpha:
for (ptrdiff_t k = 0; k < 4; k++) {
style.alpha[k] = !tag.paramsInt.IsEmpty()
? (BYTE)CalcAnimation(tag.paramsInt[0] & 0xff, style.alpha[k], fAnimate)
: org.alpha[k];
}
break;
case SSA_an: {
int n = !tag.paramsInt.IsEmpty() ? tag.paramsInt[0] : 0;
if (sub->m_scrAlignment < 0) {
sub->m_scrAlignment = (n > 0 && n < 10) ? n : org.scrAlignment;
}
}
break;
case SSA_a: {
int n = !tag.paramsInt.IsEmpty() ? tag.paramsInt[0] : 0;
if (sub->m_scrAlignment < 0) {
sub->m_scrAlignment = (n > 0 && n < 12) ? ((((n - 1) & 3) + 1) + ((n & 4) ? 6 : 0) + ((n & 8) ? 3 : 0)) : org.scrAlignment;
}
}
break;
case SSA_blur:
if (!tag.paramsReal.IsEmpty()) {
double n = CalcAnimation(tag.paramsReal[0], style.fGaussianBlur, fAnimate);
style.fGaussianBlur = (n < 0 ? 0 : n);
} else {
style.fGaussianBlur = org.fGaussianBlur;
}
break;
case SSA_bord:
if (!tag.paramsReal.IsEmpty()) {
double nx = CalcAnimation(tag.paramsReal[0], style.outlineWidthX, fAnimate);
style.outlineWidthX = (nx < 0 ? 0 : nx);
double ny = CalcAnimation(tag.paramsReal[0], style.outlineWidthY, fAnimate);
style.outlineWidthY = (ny < 0 ? 0 : ny);
} else {
style.outlineWidthX = org.outlineWidthX;
style.outlineWidthY = org.outlineWidthY;
}
break;
case SSA_be:
style.fBlur = !tag.paramsInt.IsEmpty()
? (int)(CalcAnimation(tag.paramsInt[0], style.fBlur, fAnimate) + 0.5)
: org.fBlur;
break;
case SSA_b: {
int n = !tag.paramsInt.IsEmpty() ? tag.paramsInt[0] : -1;
style.fontWeight = (n == 0 ? FW_NORMAL : n == 1 ? FW_BOLD : n >= 100 ? n : org.fontWeight);
}
break;
case SSA_clip:
case SSA_iclip: {
bool invert = (tag.cmd == SSA_iclip);
size_t nParams = tag.params.GetCount();
size_t nParamsInt = tag.paramsInt.GetCount();
if (nParams == 1 && nParamsInt == 0 && !sub->m_pClipper) {
sub->m_pClipper = DEBUG_NEW CClipper(tag.params[0], CSize(m_size.cx >> 3, m_size.cy >> 3), sub->m_scalex, sub->m_scaley,
invert, (sub->m_relativeTo == STSStyle::VIDEO) ? CPoint(m_vidrect.left, m_vidrect.top) : CPoint(0, 0),
m_renderingCaches);
} else if (nParams == 1 && nParamsInt == 1 && !sub->m_pClipper) {
long scale = tag.paramsInt[0];
if (scale < 1) {
scale = 1;
}
sub->m_pClipper = DEBUG_NEW CClipper(tag.params[0], CSize(m_size.cx >> 3, m_size.cy >> 3),
sub->m_scalex / (1 << (scale - 1)), sub->m_scaley / (1 << (scale - 1)), invert,
(sub->m_relativeTo == STSStyle::VIDEO) ? CPoint(m_vidrect.left, m_vidrect.top) : CPoint(0, 0),
m_renderingCaches);
} else if (nParamsInt == 4) {
sub->m_clipInverse = invert;
double dLeft = sub->m_scalex * tag.paramsInt[0];
double dTop = sub->m_scaley * tag.paramsInt[1];
double dRight = sub->m_scalex * tag.paramsInt[2];
double dBottom = sub->m_scaley * tag.paramsInt[3];
if (sub->m_relativeTo == STSStyle::VIDEO) {
double dOffsetX = m_vidrect.left / 8.0;
double dOffsetY = m_vidrect.top / 8.0;
dLeft += dOffsetX;
dTop += dOffsetY;
dRight += dOffsetX;
dBottom += dOffsetY;
}
sub->m_clip.SetRect(
static_cast(CalcAnimation(dLeft, sub->m_clip.left, fAnimate)),
static_cast(CalcAnimation(dTop, sub->m_clip.top, fAnimate)),
static_cast(CalcAnimation(dRight, sub->m_clip.right, fAnimate)),
static_cast(CalcAnimation(dBottom, sub->m_clip.bottom, fAnimate)));
}
}
break;
case SSA_c:
if (!tag.paramsInt.IsEmpty()) {
DWORD c = tag.paramsInt[0];
style.colors[0] = (((int)CalcAnimation(c & 0xff, style.colors[0] & 0xff, fAnimate)) & 0xff
| ((int)CalcAnimation(c & 0xff00, style.colors[0] & 0xff00, fAnimate)) & 0xff00
| ((int)CalcAnimation(c & 0xff0000, style.colors[0] & 0xff0000, fAnimate)) & 0xff0000);
} else {
style.colors[0] = org.colors[0];
}
break;
case SSA_fade: {
sub->m_bIsAnimated = true;
if (tag.paramsInt.GetCount() == 7 && !sub->m_effects[EF_FADE]) { // {\fade(a1=param[0], a2=param[1], a3=param[2], t1=t[0], t2=t[1], t3=t[2], t4=t[3])
if (Effect* e = DEBUG_NEW Effect) {
for (size_t k = 0; k < 3; k++) {
e->param[k] = tag.paramsInt[k];
}
for (size_t k = 0; k < 4; k++) {
e->t[k] = tag.paramsInt[3 + k];
}
sub->m_effects[EF_FADE] = e;
}
} else if (tag.paramsInt.GetCount() == 2 && !sub->m_effects[EF_FADE]) { // {\fad(t1=t[1], t2=t[2])
if (Effect* e = DEBUG_NEW Effect) {
e->param[0] = e->param[2] = 0xff;
e->param[1] = 0x00;
for (size_t k = 1; k < 3; k++) {
e->t[k] = tag.paramsInt[k - 1];
}
e->t[0] = e->t[3] = -1; // will be substituted with "start" and "end"
sub->m_effects[EF_FADE] = e;
}
}
}
break;
case SSA_fax:
style.fontShiftX = !tag.paramsReal.IsEmpty()
? CalcAnimation(tag.paramsReal[0], style.fontShiftX, fAnimate)
: org.fontShiftX;
break;
case SSA_fay:
style.fontShiftY = !tag.paramsReal.IsEmpty()
? CalcAnimation(tag.paramsReal[0], style.fontShiftY, fAnimate)
: org.fontShiftY;
break;
case SSA_fe:
style.charSet = !tag.paramsInt.IsEmpty()
? tag.paramsInt[0]
: org.charSet;
break;
case SSA_fn:
style.fontName = (!tag.params.IsEmpty() && !tag.params[0].IsEmpty() && tag.params[0] != L"0")
? CString(tag.params[0]).Trim()
: org.fontName;
break;
case SSA_frx:
style.fontAngleX = !tag.paramsReal.IsEmpty()
? CalcAnimation(tag.paramsReal[0], style.fontAngleX, fAnimate)
: org.fontAngleX;
break;
case SSA_fry:
style.fontAngleY = !tag.paramsReal.IsEmpty()
? CalcAnimation(tag.paramsReal[0], style.fontAngleY, fAnimate)
: org.fontAngleY;
break;
case SSA_frz:
case SSA_fr:
style.fontAngleZ = !tag.paramsReal.IsEmpty()
? CalcAnimation(tag.paramsReal[0], style.fontAngleZ, fAnimate)
: org.fontAngleZ;
break;
case SSA_fscx:
if (!tag.paramsReal.IsEmpty()) {
double n = CalcAnimation(tag.paramsReal[0], style.fontScaleX, fAnimate);
style.fontScaleX = (n < 0 ? 0 : n);
} else {
style.fontScaleX = org.fontScaleX;
}
break;
case SSA_fscy:
if (!tag.paramsReal.IsEmpty()) {
double n = CalcAnimation(tag.paramsReal[0], style.fontScaleY, fAnimate);
style.fontScaleY = (n < 0 ? 0 : n);
} else {
style.fontScaleY = org.fontScaleY;
}
break;
case SSA_fsc:
style.fontScaleX = org.fontScaleX;
style.fontScaleY = org.fontScaleY;
break;
case SSA_fsp:
style.fontSpacing = !tag.paramsReal.IsEmpty()
? CalcAnimation(tag.paramsReal[0], style.fontSpacing, fAnimate)
: org.fontSpacing;
break;
case SSA_fs:
if (!tag.paramsInt.IsEmpty()) {
if (!tag.params.IsEmpty() && (tag.params[0][0] == L'-' || tag.params[0][0] == L'+')) {
double n = CalcAnimation(style.fontSize + style.fontSize * tag.paramsInt[0] / 10, style.fontSize, fAnimate);
style.fontSize = (n > 0) ? n : org.fontSize;
} else {
double n = CalcAnimation(tag.paramsInt[0], style.fontSize, fAnimate);
style.fontSize = (n > 0) ? n : org.fontSize;
}
} else {
style.fontSize = org.fontSize;
}
break;
case SSA_i: {
int n = !tag.paramsInt.IsEmpty() ? tag.paramsInt[0] : -1;
style.fItalic = (n == 0 ? false : n == 1 ? true : org.fItalic);
}
break;
case SSA_kt:
sub->m_bIsAnimated = true;
m_kstart = !tag.paramsInt.IsEmpty()
? tag.paramsInt[0] * 10
: 0;
m_kend = m_kstart;
break;
case SSA_kf:
case SSA_K:
sub->m_bIsAnimated = true;
m_ktype = 1;
m_kstart = m_kend;
m_kend += !tag.paramsInt.IsEmpty()
? tag.paramsInt[0] * 10
: 1000;
break;
case SSA_ko:
sub->m_bIsAnimated = true;
m_ktype = 2;
m_kstart = m_kend;
m_kend += !tag.paramsInt.IsEmpty()
? tag.paramsInt[0] * 10
: 1000;
break;
case SSA_k:
sub->m_bIsAnimated = true;
m_ktype = 0;
m_kstart = m_kend;
m_kend += !tag.paramsInt.IsEmpty()
? tag.paramsInt[0] * 10
: 1000;
break;
case SSA_move: // {\move(x1=param[0], y1=param[1], x2=param[2], y2=param[3][, t1=t[0], t2=t[1]])}
sub->m_bIsAnimated = true;
if (tag.paramsReal.GetCount() == 4 && !sub->m_effects[EF_MOVE]) {
if (Effect* e = DEBUG_NEW Effect) {
e->param[0] = std::lround(sub->m_scalex * tag.paramsReal[0] * 8.0);
e->param[1] = std::lround(sub->m_scaley * tag.paramsReal[1] * 8.0);
e->param[2] = std::lround(sub->m_scalex * tag.paramsReal[2] * 8.0);
e->param[3] = std::lround(sub->m_scaley * tag.paramsReal[3] * 8.0);
e->t[0] = e->t[1] = -1;
if (tag.paramsInt.GetCount() == 2) {
for (size_t k = 0; k < 2; k++) {
e->t[k] = tag.paramsInt[k];
}
}
sub->m_effects[EF_MOVE] = e;
}
}
break;
case SSA_org: // {\org(x=param[0], y=param[1])}
if (tag.paramsReal.GetCount() == 2 && !sub->m_effects[EF_ORG]) {
if (Effect* e = DEBUG_NEW Effect) {
e->param[0] = std::lround(sub->m_scalex * tag.paramsReal[0] * 8.0);
e->param[1] = std::lround(sub->m_scaley * tag.paramsReal[1] * 8.0);
if (sub->m_relativeTo == STSStyle::VIDEO) {
e->param[0] += m_vidrect.left;
e->param[1] += m_vidrect.top;
}
sub->m_effects[EF_ORG] = e;
}
}
break;
case SSA_pbo:
m_polygonBaselineOffset = !tag.paramsInt.IsEmpty() ? tag.paramsInt[0] : 0;
break;
case SSA_pos:
if (tag.paramsReal.GetCount() == 2 && !sub->m_effects[EF_MOVE]) {
if (Effect* e = DEBUG_NEW Effect) {
e->param[0] = e->param[2] = std::lround(sub->m_scalex * tag.paramsReal[0] * 8.0);
e->param[1] = e->param[3] = std::lround(sub->m_scaley * tag.paramsReal[1] * 8.0);
e->t[0] = e->t[1] = 0;
sub->m_effects[EF_MOVE] = e;
}
}
break;
case SSA_p: {
int n = !tag.paramsInt.IsEmpty() ? tag.paramsInt[0] : 0;
m_nPolygon = (n <= 0 ? 0 : n);
}
break;
case SSA_q: {
int n = !tag.paramsInt.IsEmpty() ? tag.paramsInt[0] : -1;
sub->m_wrapStyle = (0 <= n && n <= 3)
? n
: m_defaultWrapStyle;
}
break;
case SSA_r:
if (tag.params[0].IsEmpty() || !GetStyle(tag.params[0], style)) {
style = org;
}
break;
case SSA_shad:
if (!tag.paramsReal.IsEmpty()) {
double nx = CalcAnimation(tag.paramsReal[0], style.shadowDepthX, fAnimate);
style.shadowDepthX = (nx < 0 ? 0 : nx);
double ny = CalcAnimation(tag.paramsReal[0], style.shadowDepthY, fAnimate);
style.shadowDepthY = (ny < 0 ? 0 : ny);
} else {
style.shadowDepthX = org.shadowDepthX;
style.shadowDepthY = org.shadowDepthY;
}
break;
case SSA_s: {
int n = !tag.paramsInt.IsEmpty() ? tag.paramsInt[0] : -1;
style.fStrikeOut = (n == 0 ? false : n == 1 ? true : org.fStrikeOut);
}
break;
case SSA_t: // \t([,,][,]