/* * Copyright (C) 2003-2006 Gabest * http://www.gabest.org * * This Program 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 2, or (at your option) * any later version. * * This Program 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 GNU Make; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #include "stdafx.h" #include #include #include "RTS.h" // WARNING: this isn't very thread safe, use only one RTS a time. 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(STSStyle& style) { LOGFONT lf; memset(&lf, 0, 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(lf.lfFaceName, _T("Arial")); 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(STSStyle& style, CStringW str, int ktype, int kstart, int kend) : m_style(style), m_str(str) , m_width(0), m_ascent(0), m_descent(0) , m_ktype(ktype), m_kstart(kstart), m_kend(kend) , m_fDrawn(false), m_p(INT_MAX, INT_MAX) , m_fLineBreak(false), m_fWhiteSpaceChar(false) , m_pOpaqueBox(NULL) { if(str.IsEmpty()) { m_fWhiteSpaceChar = m_fLineBreak = true; } CMyFont font(m_style); m_ascent = (int)(m_style.fontScaleY/100*font.m_ascent); m_descent = (int)(m_style.fontScaleY/100*font.m_descent); m_width = 0; } CWord::~CWord() { if(m_pOpaqueBox) 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(CPoint p, CPoint org) { if(!m_str) return; if(!m_fDrawn) { 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)) { if(!CreateWidenedRegion((int)(m_style.outlineWidthX+0.5), (int)(m_style.outlineWidthY+0.5))) return; } else if(m_style.borderStyle == 1) { if(!CreateOpaqueBox()) return; } m_fDrawn = true; if(!Rasterize(p.x&7, p.y&7, m_style.fBlur, m_style.fGaussianBlur)) return; } 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_p = p; if(m_pOpaqueBox) m_pOpaqueBox->Paint(p, org); } void CWord::Transform(CPoint org) { double scalex = m_style.fontScaleX/100; double scaley = m_style.fontScaleY/100; double caz = cos((3.1415/180)*m_style.fontAngleZ); double saz = sin((3.1415/180)*m_style.fontAngleZ); double cax = cos((3.1415/180)*m_style.fontAngleX); double sax = sin((3.1415/180)*m_style.fontAngleX); double cay = cos((3.1415/180)*m_style.fontAngleY); double say = sin((3.1415/180)*m_style.fontAngleY); for(int i = 0; i < mPathPoints; i++) { double x, y, z, xx, yy, zz; x = scalex * (mpPathPoints[i].x + m_style.fontShiftX * mpPathPoints[i].y) - org.x; y = scaley * (mpPathPoints[i].y + m_style.fontShiftY * mpPathPoints[i].x) - org.y; z = 0; 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; zz = max(zz, -19000); x = (xx * 20000) / (zz + 20000); y = (yy * 20000) / (zz + 20000); mpPathPoints[i].x = (LONG)(x + org.x + 0.5); mpPathPoints[i].y = (LONG)(y + org.y + 0.5); } } bool CWord::CreateOpaqueBox() { if(m_pOpaqueBox) return(true); STSStyle style = m_style; style.borderStyle = 0; style.outlineWidthX = style.outlineWidthY = 0; style.colors[0] = m_style.colors[2]; style.alpha[0] = m_style.alpha[2]; int w = (int)(m_style.outlineWidthX + 0.5); int h = (int)(m_style.outlineWidthY + 0.5); CStringW str; str.Format(L"m %d %d l %d %d %d %d %d %d", -w, -h, m_width+w, -h, m_width+w, m_ascent+m_descent+h, -w, m_ascent+m_descent+h); m_pOpaqueBox = new CPolygon(style, str, 0, 0, 0, 1.0/8, 1.0/8, 0); return(!!m_pOpaqueBox); } // CText CText::CText(STSStyle& style, CStringW str, int ktype, int kstart, int kend) : CWord(style, str, ktype, kstart, kend) { if(m_str == L" ") { m_fWhiteSpaceChar = true; } CMyFont font(m_style); HFONT hOldFont = SelectFont(g_hDC, font); if(m_style.fontSpacing || (long)GetVersion() < 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;} 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, wcslen(str), &extent)) {SelectFont(g_hDC, hOldFont); ASSERT(0); return;} m_width += extent.cx; } m_width = (int)(m_style.fontScaleX/100*m_width + 4) >> 3; SelectFont(g_hDC, hOldFont); } CWord* CText::Copy() { return(new CText(m_style, m_str, m_ktype, m_kstart, m_kend)); } 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); int width = 0; if(m_style.fontSpacing || (long)GetVersion() < 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(STSStyle& style, CStringW str, int ktype, int kstart, int kend, double scalex, double scaley, int baseline) : CWord(style, str, ktype, kstart, kend) , m_scalex(scalex), m_scaley(scaley), m_baseline(baseline) { ParseStr(); } CPolygon::~CPolygon() { } CWord* CPolygon::Copy() { return(new CPolygon(m_style, m_str, m_ktype, m_kstart, m_kend, m_scalex, m_scaley, m_baseline)); } bool CPolygon::Append(CWord* w) { int width = m_width; CPolygon* p = dynamic_cast(w); if(!p) return(false); // TODO return(false); return(true); } bool CPolygon::GetLONG(CStringW& str, LONG& ret) { LPWSTR s = (LPWSTR)(LPCWSTR)str, e = s; ret = wcstol(str, &e, 10); str = str.Mid(e - s); return(e > s); } bool CPolygon::GetPOINT(CStringW& str, POINT& ret) { return(GetLONG(str, ret.x) && GetLONG(str, ret.y)); } bool CPolygon::ParseStr() { if(m_pathTypesOrg.GetCount() > 0) return(true); CPoint p; int i, j, lastsplinestart = -1, firstmoveto = -1, lastmoveto = -1; CStringW str = m_str; str.SpanIncluding(L"mnlbspc 0123456789"); str.Replace(L"m", L"*m"); str.Replace(L"n", L"*n"); str.Replace(L"l", L"*l"); str.Replace(L"b", L"*b"); str.Replace(L"s", L"*s"); str.Replace(L"p", L"*p"); str.Replace(L"c", L"*c"); int k = 0; for(CStringW s = str.Tokenize(L"*", k); !s.IsEmpty(); s = str.Tokenize(L"*", k)) { WCHAR c = s[0]; s.TrimLeft(L"mnlbspc "); switch(c) { case 'm': lastmoveto = m_pathTypesOrg.GetCount(); if(firstmoveto == -1) firstmoveto = lastmoveto; while(GetPOINT(s, p)) {m_pathTypesOrg.Add(PT_MOVETO); m_pathPointsOrg.Add(p);} break; case 'n': while(GetPOINT(s, p)) {m_pathTypesOrg.Add(PT_MOVETONC); m_pathPointsOrg.Add(p);} break; case 'l': while(GetPOINT(s, p)) {m_pathTypesOrg.Add(PT_LINETO); m_pathPointsOrg.Add(p);} break; case 'b': j = m_pathTypesOrg.GetCount(); while(GetPOINT(s, p)) {m_pathTypesOrg.Add(PT_BEZIERTO); m_pathPointsOrg.Add(p); j++;} j = m_pathTypesOrg.GetCount() - ((m_pathTypesOrg.GetCount()-j)%3); m_pathTypesOrg.SetCount(j); m_pathPointsOrg.SetCount(j); break; case 's': j = lastsplinestart = m_pathTypesOrg.GetCount(); i = 3; while(i-- && GetPOINT(s, p)) {m_pathTypesOrg.Add(PT_BSPLINETO); m_pathPointsOrg.Add(p); j++;} if(m_pathTypesOrg.GetCount()-lastsplinestart < 3) {m_pathTypesOrg.SetCount(lastsplinestart); m_pathPointsOrg.SetCount(lastsplinestart); lastsplinestart = -1;} // no break here case 'p': while(GetPOINT(s, p)) {m_pathTypesOrg.Add(PT_BSPLINEPATCHTO); m_pathPointsOrg.Add(p); j++;} break; case 'c': if(lastsplinestart > 0) { m_pathTypesOrg.Add(PT_BSPLINEPATCHTO); m_pathTypesOrg.Add(PT_BSPLINEPATCHTO); m_pathTypesOrg.Add(PT_BSPLINEPATCHTO); p = m_pathPointsOrg[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_pathPointsOrg.Add(p); p = m_pathPointsOrg[lastsplinestart]; m_pathPointsOrg.Add(p); p = m_pathPointsOrg[lastsplinestart+1]; m_pathPointsOrg.Add(p); lastsplinestart = -1; } break; default: break; } } /* LPCWSTR str = m_str; while(*str) { while(*str && *str != 'm' && *str != 'n' && *str != 'l' && *str != 'b' && *str != 's' && *str != 'p' && *str != 'c') str++; if(!*str) break; switch(*str++) { case 'm': lastmoveto = m_pathTypesOrg.GetCount(); if(firstmoveto == -1) firstmoveto = lastmoveto; while(GetPOINT(str, p)) {m_pathTypesOrg.Add(PT_MOVETO); m_pathPointsOrg.Add(p);} break; case 'n': while(GetPOINT(str, p)) {m_pathTypesOrg.Add(PT_MOVETONC); m_pathPointsOrg.Add(p);} break; case 'l': while(GetPOINT(str, p)) {m_pathTypesOrg.Add(PT_LINETO); m_pathPointsOrg.Add(p);} break; case 'b': j = m_pathTypesOrg.GetCount(); while(GetPOINT(str, p)) {m_pathTypesOrg.Add(PT_BEZIERTO); m_pathPointsOrg.Add(p); j++;} j = m_pathTypesOrg.GetCount() - ((m_pathTypesOrg.GetCount()-j)%3); m_pathTypesOrg.SetCount(j); m_pathPointsOrg.SetCount(j); break; case 's': j = lastsplinestart = m_pathTypesOrg.GetCount(); i = 3; while(i-- && GetPOINT(str, p)) {m_pathTypesOrg.Add(PT_BSPLINETO); m_pathPointsOrg.Add(p); j++;} if(m_pathTypesOrg.GetCount()-lastsplinestart < 3) {m_pathTypesOrg.SetCount(lastsplinestart); m_pathPointsOrg.SetCount(lastsplinestart); lastsplinestart = -1;} // no break here case 'p': while(GetPOINT(str, p)) {m_pathTypesOrg.Add(PT_BSPLINEPATCHTO); m_pathPointsOrg.Add(p); j++;} break; case 'c': if(lastsplinestart > 0) { m_pathTypesOrg.Add(PT_BSPLINEPATCHTO); m_pathTypesOrg.Add(PT_BSPLINEPATCHTO); m_pathTypesOrg.Add(PT_BSPLINEPATCHTO); p = m_pathPointsOrg[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_pathPointsOrg.Add(p); p = m_pathPointsOrg[lastsplinestart]; m_pathPointsOrg.Add(p); p = m_pathPointsOrg[lastsplinestart+1]; m_pathPointsOrg.Add(p); lastsplinestart = -1; } break; default: break; } if(firstmoveto > 0) break; } */ if(lastmoveto == -1 || firstmoveto > 0) { m_pathTypesOrg.RemoveAll(); m_pathPointsOrg.RemoveAll(); return(false); } int minx = INT_MAX, miny = INT_MAX, maxx = -INT_MAX, maxy = -INT_MAX; for(i = 0; i < m_pathTypesOrg.GetCount(); i++) { m_pathPointsOrg[i].x = (int)(64 * m_scalex * m_pathPointsOrg[i].x); m_pathPointsOrg[i].y = (int)(64 * m_scaley * m_pathPointsOrg[i].y); if(minx > m_pathPointsOrg[i].x) minx = m_pathPointsOrg[i].x; if(miny > m_pathPointsOrg[i].y) miny = m_pathPointsOrg[i].y; if(maxx < m_pathPointsOrg[i].x) maxx = m_pathPointsOrg[i].x; if(maxy < m_pathPointsOrg[i].y) maxy = m_pathPointsOrg[i].y; } m_width = max(maxx - minx, 0); m_ascent = max(maxy - miny, 0); int baseline = (int)(64 * m_scaley * m_baseline); m_descent = baseline; m_ascent -= baseline; m_width = ((int)(m_style.fontScaleX/100 * m_width) + 4) >> 3; m_ascent = ((int)(m_style.fontScaleY/100 * m_ascent) + 4) >> 3; m_descent = ((int)(m_style.fontScaleY/100 * m_descent) + 4) >> 3; return(true); } bool CPolygon::CreatePath() { int len = m_pathTypesOrg.GetCount(); if(len == 0) return(false); if(mPathPoints != len) { mpPathTypes = (BYTE*)realloc(mpPathTypes, len*sizeof(BYTE)); mpPathPoints = (POINT*)realloc(mpPathPoints, len*sizeof(POINT)); if(!mpPathTypes || !mpPathPoints) return(false); mPathPoints = len; } memcpy(mpPathTypes, m_pathTypesOrg.GetData(), len*sizeof(BYTE)); memcpy(mpPathPoints, m_pathPointsOrg.GetData(), len*sizeof(POINT)); return(true); } // CClipper CClipper::CClipper(CStringW str, CSize size, double scalex, double scaley) : CPolygon(STSStyle(), str, 0, 0, 0, scalex, scaley, 0) { m_size.cx = m_size.cy = 0; m_pAlphaMask = NULL; if(size.cx < 0 || size.cy < 0 || !(m_pAlphaMask = new BYTE[size.cx*size.cy])) return; m_size = size; memset(m_pAlphaMask, 0, size.cx*size.cy); Paint(CPoint(0, 0), CPoint(0, 0)); int w = mOverlayWidth, h = mOverlayHeight; int x = (mOffsetX+4)>>3, y = (mOffsetY+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 = mpOverlayBuffer + 2*(mOverlayWidth * yo + xo); BYTE* dst = m_pAlphaMask + m_size.cx * y + x; while(h--) { for(int wt=0; wtm_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 = NULL; 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 = MulDiv(a, 0xff - alpha, 0xff); COLORREF shadow = revcolor(w->m_style.colors[3]) | (a<<24); long sw[6] = {shadow, -1}; 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_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 += MulDiv(alpha, 0xff - w->m_style.alpha[2], 0xff); COLORREF outline = revcolor(w->m_style.colors[2]) | ((0xff-aoutline)<<24); long sw[6] = {outline, -1}; w->Paint(CPoint(x, y), org); if(w->m_style.borderStyle == 0) { // Always draw border part of widened region // Draw fill part of widened region only if there isn't a chance the real fill is transculent 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]; if(alpha > 0) aprimary += MulDiv(alpha, 0xff - w->m_style.alpha[0], 0xff); COLORREF primary = revcolor(w->m_style.colors[0]) | ((0xff-aprimary)<<24); DWORD asecondary = w->m_style.alpha[1]; if(alpha > 0) asecondary += MulDiv(alpha, 0xff - w->m_style.alpha[1], 0xff); COLORREF secondary = revcolor(w->m_style.colors[1]) | ((0xff-asecondary)<<24); long sw[6] = {primary, 0, secondary}; // karaoke double t; if(w->m_ktype == 0 || w->m_ktype == 2) { t = time < w->m_kstart ? 0 : 1; } else if(w->m_ktype == 1) { if(time < w->m_kstart) t = 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-t; COLORREF tmp = sw[0]; sw[0] = sw[2]; sw[2] = tmp; } } else t = 1.0; } if(t >= 1) { sw[1] = 0xffffffff; } sw[3] = (int)(w->m_style.outlineWidthX + t*w->m_width) >> 3; sw[4] = sw[2]; sw[5] = 0x00ffffff; w->Paint(CPoint(x, y), org); bbox |= w->Draw(spd, clipRect, pAlphaMask, x, y, sw, true, false); p.x += w->m_width; } return(bbox); } // CSubtitle CSubtitle::CSubtitle() { memset(m_effects, 0, sizeof(Effect*)*EF_NUMBEROFEFFECTS); m_pClipper = NULL; m_clipInverse = false; m_scalex = m_scaley = 1; } 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); for(int i = 0; i < EF_NUMBEROFEFFECTS; i++) {if(m_effects[i]) delete m_effects[i];} memset(m_effects, 0, sizeof(Effect*)*EF_NUMBEROFEFFECTS); if(m_pClipper) delete m_pClipper; m_pClipper = NULL; } 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 = GetFullWidth(); 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; } maxwidth = width; if(m_wrapStyle == 3 && pos) maxwidth -= wordwidth; } } 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 == NULL) return(NULL); CLine* ret = new CLine(); if(!ret) return(NULL); 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; if(m_effects[EF_BANNER] && m_effects[EF_BANNER]->param[2]) { int width = m_effects[EF_BANNER]->param[2]; int w = size.cx, h = size.cy; if(!m_pClipper) { CStringW str; str.Format(L"m %d %d l %d %d %d %d %d %d", 0, 0, w, 0, w, h, 0, h); m_pClipper = new CClipper(str, size, 1, 1); if(!m_pClipper) return; } int da = (64<<8)/width; BYTE* am = m_pClipper->m_pAlphaMask; for(int j = 0; j < h; j++, am += w) { int a = 0; int k = min(width, w); for(int i = 0; i < k; i++, a += da) am[i] = (am[i]*a)>>14; a = 0x40<<8; k = w-width; if(k < 0) {a -= -k*da; k = 0;} for(int i = k; i < w; i++, a -= da) am[i] = (am[i]*a)>>14; } } else if(m_effects[EF_SCROLL] && m_effects[EF_SCROLL]->param[4]) { int height = m_effects[EF_SCROLL]->param[4]; int w = size.cx, h = size.cy; if(!m_pClipper) { CStringW str; str.Format(L"m %d %d l %d %d %d %d %d %d", 0, 0, w, 0, w, h, 0, h); m_pClipper = new CClipper(str, size, 1, 1); if(!m_pClipper) return; } 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]; memset(m_pClipper->m_pAlphaMask, 0, am - m_pClipper->m_pAlphaMask); for(int j = k; j < l; j++, a += da) { for(int i = 0; i < w; i++, am++) *am = ((*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(int i = 0; i < w; i++, am++) *am = ((*am)*a)>>14; } memset(am, 0, (h-j)*w); } } } void CSubtitle::MakeLines(CSize size, CRect marginRect) { CSize spaceNeeded(0, 0); bool fFirstLine = true; m_topborder = m_bottomborder = 0; CLine* l = NULL; 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 = 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(int 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(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 CRenderedTextSubtitle::CRenderedTextSubtitle(CCritSec* pLock) : ISubPicProviderImpl(pLock) { m_size = CSize(0, 0); if(g_hDC_refcnt == 0) { g_hDC = CreateCompatibleDC(NULL); SetBkMode(g_hDC, TRANSPARENT); SetTextColor(g_hDC, 0xffffff); SetMapMode(g_hDC, MM_TEXT); } g_hDC_refcnt++; } 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, 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(s - str); if(!effect.CompareNoCase(_T("Banner;"))) { int delay, lefttoright = 0, fadeawaywidth = 0; if(_stscanf(s, _T("%d;%d;%d"), &delay, &lefttoright, &fadeawaywidth) < 1) return; Effect* e = new Effect; if(!e) return; sub->m_effects[e->type = EF_BANNER] = e; e->param[0] = (int)(max(1.0*delay/sub->m_scalex, 1)); e->param[1] = lefttoright; e->param[2] = (int)(sub->m_scalex*fadeawaywidth); sub->m_wrapStyle = 2; } else if(!effect.CompareNoCase(_T("Scroll up;")) || !effect.CompareNoCase(_T("Scroll down;"))) { int top, bottom, delay, fadeawayheight = 0; if(_stscanf(s, _T("%d;%d;%d;%d"), &top, &bottom, &delay, &fadeawayheight) < 3) return; if(top > bottom) {int tmp = top; top = bottom; bottom = tmp;} Effect* e = new Effect; if(!e) return; sub->m_effects[e->type = EF_SCROLL] = e; e->param[0] = (int)(sub->m_scaley*top*8); e->param[1] = (int)(sub->m_scaley*bottom*8); e->param[2] = (int)(max(1.0*delay/sub->m_scaley, 1)); e->param[3] = (effect.GetLength() == 12); e->param[4] = (int)(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 != '\n' && c != ' ' && c != '\x00A0' && c != 0) continue; if(i < j) { if(CWord* w = new CText(style, str.Mid(i, j-i), m_ktype, m_kstart, m_kend)) { sub->m_words.AddTail(w); m_kstart = m_kend; } } if(c == '\n') { if(CWord* w = new CText(style, CStringW(), m_ktype, m_kstart, m_kend)) { sub->m_words.AddTail(w); m_kstart = m_kend; } } else if(c == ' ' || c == '\x00A0') { if(CWord* w = new CText(style, CStringW(c), m_ktype, m_kstart, m_kend)) { 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 = 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)) { sub->m_words.AddTail(w); m_kstart = m_kend; } } bool CRenderedTextSubtitle::ParseSSATag(CSubtitle* sub, CStringW str, STSStyle& style, STSStyle& org, bool fAnimate) { if(!sub) return(false); int nTags = 0, nUnrecognizedTags = 0; for(int i = 0, j; (j = str.Find('\\', i)) >= 0; i = j) { CStringW cmd; for(WCHAR c = str[++j]; c && c != '(' && c != '\\'; cmd += c, c = str[++j]); cmd.Trim(); if(cmd.IsEmpty()) continue; CAtlArray params; if(str[j] == '(') { CStringW param; for(WCHAR c = str[++j]; c && c != ')'; param += c, c = str[++j]); param.Trim(); while(!param.IsEmpty()) { int i = param.Find(','), j = param.Find('\\'); if(i >= 0 && (j < 0 || i < j)) { CStringW s = param.Left(i).Trim(); if(!s.IsEmpty()) params.Add(s); param = i+1 < param.GetLength() ? param.Mid(i+1) : L""; } else { param.Trim(); if(!param.IsEmpty()) params.Add(param); param.Empty(); } } } if(!cmd.Find(L"1c") || !cmd.Find(L"2c") || !cmd.Find(L"3c") || !cmd.Find(L"4c")) params.Add(cmd.Mid(2).Trim(L"&H")), cmd = cmd.Left(2); else if(!cmd.Find(L"1a") || !cmd.Find(L"2a") || !cmd.Find(L"3a") || !cmd.Find(L"4a")) params.Add(cmd.Mid(2).Trim(L"&H")), cmd = cmd.Left(2); else if(!cmd.Find(L"alpha")) params.Add(cmd.Mid(5).Trim(L"&H")), cmd = cmd.Left(5); else if(!cmd.Find(L"an")) params.Add(cmd.Mid(2)), cmd = cmd.Left(2); else if(!cmd.Find(L"a")) params.Add(cmd.Mid(1)), cmd = cmd.Left(1); else if(!cmd.Find(L"blur")) params.Add(cmd.Mid(4)), cmd = cmd.Left(4); else if(!cmd.Find(L"bord")) params.Add(cmd.Mid(4)), cmd = cmd.Left(4); else if(!cmd.Find(L"be")) params.Add(cmd.Mid(2)), cmd = cmd.Left(2); else if(!cmd.Find(L"b")) params.Add(cmd.Mid(1)), cmd = cmd.Left(1); else if(!cmd.Find(L"clip")) ; else if(!cmd.Find(L"c")) params.Add(cmd.Mid(1).Trim(L"&H")), cmd = cmd.Left(1); else if(!cmd.Find(L"fade")) ; else if(!cmd.Find(L"fe")) params.Add(cmd.Mid(2)), cmd = cmd.Left(2); else if(!cmd.Find(L"fn")) params.Add(cmd.Mid(2)), cmd = cmd.Left(2); else if(!cmd.Find(L"frx") || !cmd.Find(L"fry") || !cmd.Find(L"frz")) params.Add(cmd.Mid(3)), cmd = cmd.Left(3); else if(!cmd.Find(L"fax") || !cmd.Find(L"fay")) params.Add(cmd.Mid(3)), cmd = cmd.Left(3); else if(!cmd.Find(L"fr")) params.Add(cmd.Mid(2)), cmd = cmd.Left(2); else if(!cmd.Find(L"fscx") || !cmd.Find(L"fscy")) params.Add(cmd.Mid(4)), cmd = cmd.Left(4); else if(!cmd.Find(L"fsc")) params.Add(cmd.Mid(3)), cmd = cmd.Left(3); else if(!cmd.Find(L"fsp")) params.Add(cmd.Mid(3)), cmd = cmd.Left(3); else if(!cmd.Find(L"fs")) params.Add(cmd.Mid(2)), cmd = cmd.Left(2); else if(!cmd.Find(L"iclip")) ; else if(!cmd.Find(L"i")) params.Add(cmd.Mid(1)), cmd = cmd.Left(1); else if(!cmd.Find(L"kt") || !cmd.Find(L"kf") || !cmd.Find(L"ko")) params.Add(cmd.Mid(2)), cmd = cmd.Left(2); else if(!cmd.Find(L"k") || !cmd.Find(L"K")) params.Add(cmd.Mid(1)), cmd = cmd.Left(1); else if(!cmd.Find(L"move")) ; else if(!cmd.Find(L"org")) ; else if(!cmd.Find(L"pbo")) params.Add(cmd.Mid(3)), cmd = cmd.Left(3); else if(!cmd.Find(L"pos")) ; else if(!cmd.Find(L"p")) params.Add(cmd.Mid(1)), cmd = cmd.Left(1); else if(!cmd.Find(L"q")) params.Add(cmd.Mid(1)), cmd = cmd.Left(1); else if(!cmd.Find(L"r")) params.Add(cmd.Mid(1)), cmd = cmd.Left(1); else if(!cmd.Find(L"shad")) params.Add(cmd.Mid(4)), cmd = cmd.Left(4); else if(!cmd.Find(L"s")) params.Add(cmd.Mid(1)), cmd = cmd.Left(1); else if(!cmd.Find(L"t")) ; else if(!cmd.Find(L"u")) params.Add(cmd.Mid(1)), cmd = cmd.Left(1); else if(!cmd.Find(L"xbord")) params.Add(cmd.Mid(5)), cmd = cmd.Left(5); else if(!cmd.Find(L"xshad")) params.Add(cmd.Mid(5)), cmd = cmd.Left(5); else if(!cmd.Find(L"ybord")) params.Add(cmd.Mid(5)), cmd = cmd.Left(5); else if(!cmd.Find(L"yshad")) params.Add(cmd.Mid(5)), cmd = cmd.Left(5); else nUnrecognizedTags++; nTags++; // TODO: call ParseStyleModifier(cmd, params, ..) and move the rest there CStringW p = params.GetCount() > 0 ? params[0] : L""; if(cmd == "1c" || cmd == L"2c" || cmd == L"3c" || cmd == L"4c") { int i = cmd[0] - '1'; DWORD c = wcstol(p, NULL, 16); style.colors[i] = !p.IsEmpty() ? (((int)CalcAnimation(c&0xff, style.colors[i]&0xff, fAnimate))&0xff |((int)CalcAnimation(c&0xff00, style.colors[i]&0xff00, fAnimate))&0xff00 |((int)CalcAnimation(c&0xff0000, style.colors[i]&0xff0000, fAnimate))&0xff0000) : org.colors[i]; } else if(cmd == L"1a" || cmd == L"2a" || cmd == L"3a" || cmd == L"4a") { int i = cmd[0] - '1'; style.alpha[i] = !p.IsEmpty() ? (BYTE)CalcAnimation(wcstol(p, NULL, 16), style.alpha[i], fAnimate) : org.alpha[i]; } else if(cmd == L"alpha") { for(int i = 0; i < 4; i++) { style.alpha[i] = !p.IsEmpty() ? (BYTE)CalcAnimation(wcstol(p, NULL, 16), style.alpha[i], fAnimate) : org.alpha[i]; } } else if(cmd == L"an") { int n = wcstol(p, NULL, 10); if(sub->m_scrAlignment < 0) sub->m_scrAlignment = (n > 0 && n < 10) ? n : org.scrAlignment; } else if(cmd == L"a") { int n = wcstol(p, NULL, 10); 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; } else if(cmd == L"blur") { double n = CalcAnimation(wcstod(p, NULL), style.fGaussianBlur, fAnimate); style.fGaussianBlur = !p.IsEmpty() ? (n < 0 ? 0 : n) : org.fGaussianBlur; } else if(cmd == L"bord") { double dst = wcstod(p, NULL); double nx = CalcAnimation(dst, style.outlineWidthX, fAnimate); style.outlineWidthX = !p.IsEmpty() ? (nx < 0 ? 0 : nx) : org.outlineWidthX; double ny = CalcAnimation(dst, style.outlineWidthY, fAnimate); style.outlineWidthY = !p.IsEmpty() ? (ny < 0 ? 0 : ny) : org.outlineWidthY; } else if(cmd == L"be") { int n = (int)(CalcAnimation(wcstol(p, NULL, 10), style.fBlur, fAnimate)+0.5); style.fBlur = !p.IsEmpty() ? n : org.fBlur; } else if(cmd == L"b") { int n = wcstol(p, NULL, 10); style.fontWeight = !p.IsEmpty() ? (n == 0 ? FW_NORMAL : n == 1 ? FW_BOLD : n >= 100 ? n : org.fontWeight) : org.fontWeight; } else if(cmd == L"clip" || cmd == L"iclip") { if(params.GetCount() == 1 && !sub->m_pClipper) { sub->m_pClipper = new CClipper(params[0], CSize(m_size.cx>>3, m_size.cy>>3), sub->m_scalex, sub->m_scaley); } else if(params.GetCount() == 2 && !sub->m_pClipper) { int scale = max(wcstol(p, NULL, 10), 1); sub->m_pClipper = new CClipper(params[1], CSize(m_size.cx>>3, m_size.cy>>3), sub->m_scalex/(1<<(scale-1)), sub->m_scaley/(1<<(scale-1))); } else if(params.GetCount() == 4) { CRect r; if(cmd == L"iclip") // TODO: Also support inverse vector clips? sub->m_clipInverse = true; r.SetRect( wcstol(params[0], NULL, 10), wcstol(params[1], NULL, 10), wcstol(params[2], NULL, 10), wcstol(params[3], NULL, 10)); CPoint o(0, 0); if(sub->m_relativeTo == 1) // TODO: this should also apply to the other two clippings above { o.x = m_vidrect.left>>3; o.y = m_vidrect.top>>3; } sub->m_clip.SetRect( (int)CalcAnimation(sub->m_scalex*r.left + o.x, sub->m_clip.left, fAnimate), (int)CalcAnimation(sub->m_scaley*r.top + o.y, sub->m_clip.top, fAnimate), (int)CalcAnimation(sub->m_scalex*r.right + o.x, sub->m_clip.right, fAnimate), (int)CalcAnimation(sub->m_scaley*r.bottom + o.y, sub->m_clip.bottom, fAnimate)); } } else if(cmd == L"c") { DWORD c = wcstol(p, NULL, 16); style.colors[0] = !p.IsEmpty() ? (((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) : org.colors[0]; } else if(cmd == L"fade" || cmd == L"fad") { if(params.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 = new Effect) { for(int i = 0; i < 3; i++) e->param[i] = wcstol(params[i], NULL, 10); for(int i = 0; i < 4; i++) e->t[i] = wcstol(params[3+i], NULL, 10); sub->m_effects[EF_FADE] = e; } } else if(params.GetCount() == 2 && !sub->m_effects[EF_FADE]) // {\fad(t1=t[1], t2=t[2]) { if(Effect* e = new Effect) { e->param[0] = e->param[2] = 0xff; e->param[1] = 0x00; for(int i = 1; i < 3; i++) e->t[i] = wcstol(params[i-1], NULL, 10); e->t[0] = e->t[3] = -1; // will be substituted with "start" and "end" sub->m_effects[EF_FADE] = e; } } } else if(cmd == L"fax") { style.fontShiftX = !p.IsEmpty() ? CalcAnimation(wcstod(p, NULL), style.fontShiftX, fAnimate) : org.fontShiftX; } else if(cmd == L"fay") { style.fontShiftY = !p.IsEmpty() ? CalcAnimation(wcstod(p, NULL), style.fontShiftY, fAnimate) : org.fontShiftY; } else if(cmd == L"fe") { int n = wcstol(p, NULL, 10); style.charSet = !p.IsEmpty() ? n : org.charSet; } else if(cmd == L"fn") { style.fontName = (!p.IsEmpty() && p != '0') ? CString(p).Trim() : org.fontName; } else if(cmd == L"frx") { style.fontAngleX = !p.IsEmpty() ? CalcAnimation(wcstod(p, NULL), style.fontAngleX, fAnimate) : org.fontAngleX; } else if(cmd == L"fry") { style.fontAngleY = !p.IsEmpty() ? CalcAnimation(wcstod(p, NULL), style.fontAngleY, fAnimate) : org.fontAngleY; } else if(cmd == L"frz" || cmd == L"fr") { style.fontAngleZ = !p.IsEmpty() ? CalcAnimation(wcstod(p, NULL), style.fontAngleZ, fAnimate) : org.fontAngleZ; } else if(cmd == L"fscx") { double n = CalcAnimation(wcstol(p, NULL, 10), style.fontScaleX, fAnimate); style.fontScaleX = !p.IsEmpty() ? ((n < 0) ? 0 : n) : org.fontScaleX; } else if(cmd == L"fscy") { double n = CalcAnimation(wcstol(p, NULL, 10), style.fontScaleY, fAnimate); style.fontScaleY = !p.IsEmpty() ? ((n < 0) ? 0 : n) : org.fontScaleY; } else if(cmd == L"fsc") { style.fontScaleX = org.fontScaleX; style.fontScaleY = org.fontScaleY; } else if(cmd == L"fsp") { style.fontSpacing = !p.IsEmpty() ? CalcAnimation(wcstod(p, NULL), style.fontSpacing, fAnimate) : org.fontSpacing; } else if(cmd == L"fs") { if(!p.IsEmpty()) { if(p[0] == '-' || p[0] == '+') { double n = CalcAnimation(style.fontSize + style.fontSize*wcstol(p, NULL, 10)/10, style.fontSize, fAnimate); style.fontSize = (n > 0) ? n : org.fontSize; } else { double n = CalcAnimation(wcstol(p, NULL, 10), style.fontSize, fAnimate); style.fontSize = (n > 0) ? n : org.fontSize; } } else { style.fontSize = org.fontSize; } } else if(cmd == L"i") { int n = wcstol(p, NULL, 10); style.fItalic = !p.IsEmpty() ? (n == 0 ? false : n == 1 ? true : org.fItalic) : org.fItalic; } else if(cmd == L"kt") { m_kstart = !p.IsEmpty() ? wcstol(p, NULL, 10)*10 : 0; m_kend = m_kstart; } else if(cmd == L"kf" || cmd == L"K") { m_ktype = 1; m_kstart = m_kend; m_kend += !p.IsEmpty() ? wcstol(p, NULL, 10)*10 : 1000; } else if(cmd == L"ko") { m_ktype = 2; m_kstart = m_kend; m_kend += !p.IsEmpty() ? wcstol(p, NULL, 10)*10 : 1000; } else if(cmd == L"k") { m_ktype = 0; m_kstart = m_kend; m_kend += !p.IsEmpty() ? wcstol(p, NULL, 10)*10 : 1000; } else if(cmd == L"move") // {\move(x1=param[0], y1=param[1], x2=param[2], y2=param[3][, t1=t[0], t2=t[1]])} { if((params.GetCount() == 4 || params.GetCount() == 6) && !sub->m_effects[EF_MOVE]) { if(Effect* e = new Effect) { e->param[0] = (int)(sub->m_scalex*wcstod(params[0], NULL)*8); e->param[1] = (int)(sub->m_scaley*wcstod(params[1], NULL)*8); e->param[2] = (int)(sub->m_scalex*wcstod(params[2], NULL)*8); e->param[3] = (int)(sub->m_scaley*wcstod(params[3], NULL)*8); e->t[0] = e->t[1] = -1; if(params.GetCount() == 6) { for(int i = 0; i < 2; i++) e->t[i] = wcstol(params[4+i], NULL, 10); } sub->m_effects[EF_MOVE] = e; } } } else if(cmd == L"org") // {\org(x=param[0], y=param[1])} { if(params.GetCount() == 2 && !sub->m_effects[EF_ORG]) { if(Effect* e = new Effect) { e->param[0] = (int)(sub->m_scalex*wcstod(params[0], NULL)*8); e->param[1] = (int)(sub->m_scaley*wcstod(params[1], NULL)*8); sub->m_effects[EF_ORG] = e; } } } else if(cmd == L"pbo") { m_polygonBaselineOffset = wcstol(p, NULL, 10); } else if(cmd == L"pos") { if(params.GetCount() == 2 && !sub->m_effects[EF_MOVE]) { if(Effect* e = new Effect) { e->param[0] = e->param[2] = (int)(sub->m_scalex*wcstod(params[0], NULL)*8); e->param[1] = e->param[3] = (int)(sub->m_scaley*wcstod(params[1], NULL)*8); e->t[0] = e->t[1] = 0; sub->m_effects[EF_MOVE] = e; } } } else if(cmd == L"p") { int n = wcstol(p, NULL, 10); m_nPolygon = (n <= 0 ? 0 : n); } else if(cmd == L"q") { int n = wcstol(p, NULL, 10); sub->m_wrapStyle = !p.IsEmpty() && (0 <= n && n <= 3) ? n : m_defaultWrapStyle; } else if(cmd == L"r") { STSStyle* val; style = (!p.IsEmpty() && m_styles.Lookup(CString(p), val) && val) ? *val : org; } else if(cmd == L"shad") { double dst = wcstod(p, NULL); double nx = CalcAnimation(dst, style.shadowDepthX, fAnimate); style.shadowDepthX = !p.IsEmpty() ? (nx < 0 ? 0 : nx) : org.shadowDepthX; double ny = CalcAnimation(dst, style.shadowDepthY, fAnimate); style.shadowDepthY = !p.IsEmpty() ? (ny < 0 ? 0 : ny) : org.shadowDepthY; } else if(cmd == L"s") { int n = wcstol(p, NULL, 10); style.fStrikeOut = !p.IsEmpty() ? (n == 0 ? false : n == 1 ? true : org.fStrikeOut) : org.fStrikeOut; } else if(cmd == L"t") // \t([,,][,]