Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mpc-hc/mpc-hc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXhmikosR <xhmikosr@users.sourceforge.net>2010-07-10 16:26:52 +0400
committerXhmikosR <xhmikosr@users.sourceforge.net>2010-07-10 16:26:52 +0400
commit34a277661cf90b26107cce9fd46cf08b30fe6478 (patch)
treeb88c4901d2673989ab615947a2ba8173147b8b63 /src/Subtitles
parent9da4064ac2e01b42480d20d0a6a43e3d5f801a5a (diff)
renamed all lowercase filenames to mixedcase
git-svn-id: https://mpc-hc.svn.sourceforge.net/svnroot/mpc-hc/trunk@2114 10f7b99b-c216-0410-bff0-8a66a9350fd8
Diffstat (limited to 'src/Subtitles')
-rw-r--r--src/Subtitles/BaseSub.cpp33
-rw-r--r--src/Subtitles/BaseSub.h54
-rw-r--r--src/Subtitles/CCDecoder.cpp380
-rw-r--r--src/Subtitles/CCDecoder.h48
-rw-r--r--src/Subtitles/CompositionObject.cpp400
-rw-r--r--src/Subtitles/CompositionObject.h87
-rw-r--r--src/Subtitles/DVBSub.cpp596
-rw-r--r--src/Subtitles/DVBSub.h228
-rw-r--r--src/Subtitles/GFN.cpp178
-rw-r--r--src/Subtitles/GFN.h29
-rw-r--r--src/Subtitles/HdmvSub.cpp381
-rw-r--r--src/Subtitles/HdmvSub.h124
-rw-r--r--src/Subtitles/RTS.cpp3975
-rw-r--r--src/Subtitles/RTS.h293
-rw-r--r--src/Subtitles/Rasterizer.cpp2116
-rw-r--r--src/Subtitles/Rasterizer.h186
-rw-r--r--src/Subtitles/RealTextParser.cpp635
-rw-r--r--src/Subtitles/RealTextParser.h102
-rw-r--r--src/Subtitles/RenderedHdmvSubtitle.cpp180
-rw-r--r--src/Subtitles/RenderedHdmvSubtitle.h71
-rw-r--r--src/Subtitles/SSF.cpp243
-rw-r--r--src/Subtitles/SSF.h70
-rw-r--r--src/Subtitles/STS.cpp3802
-rw-r--r--src/Subtitles/STS.h358
-rw-r--r--src/Subtitles/SeparableFilter.h121
-rw-r--r--src/Subtitles/SubtitleInputPin.cpp416
-rw-r--r--src/Subtitles/SubtitleInputPin.h54
-rw-r--r--src/Subtitles/Subtitles.vcproj497
-rw-r--r--src/Subtitles/TextFile.cpp537
-rw-r--r--src/Subtitles/TextFile.h79
-rw-r--r--src/Subtitles/USFSubtitles.cpp789
-rw-r--r--src/Subtitles/USFSubtitles.h104
-rw-r--r--src/Subtitles/VobSubFile.cpp2460
-rw-r--r--src/Subtitles/VobSubFile.h184
-rw-r--r--src/Subtitles/VobSubFileRipper.cpp1259
-rw-r--r--src/Subtitles/VobSubFileRipper.h197
-rw-r--r--src/Subtitles/VobSubImage.cpp1196
-rw-r--r--src/Subtitles/VobSubImage.h104
-rw-r--r--src/Subtitles/libssf/Arabic.cpp327
-rw-r--r--src/Subtitles/libssf/Arabic.h34
-rw-r--r--src/Subtitles/libssf/Array.cpp28
-rw-r--r--src/Subtitles/libssf/Array.h108
-rw-r--r--src/Subtitles/libssf/Exception.cpp35
-rw-r--r--src/Subtitles/libssf/Exception.h35
-rw-r--r--src/Subtitles/libssf/File.cpp261
-rw-r--r--src/Subtitles/libssf/File.h45
-rw-r--r--src/Subtitles/libssf/FontWrapper.cpp57
-rw-r--r--src/Subtitles/libssf/FontWrapper.h41
-rw-r--r--src/Subtitles/libssf/Glyph.cpp306
-rw-r--r--src/Subtitles/libssf/Glyph.h63
-rw-r--r--src/Subtitles/libssf/GlyphPath.cpp164
-rw-r--r--src/Subtitles/libssf/GlyphPath.h45
-rw-r--r--src/Subtitles/libssf/Node.cpp551
-rw-r--r--src/Subtitles/libssf/Node.h122
-rw-r--r--src/Subtitles/libssf/NodeFactory.cpp182
-rw-r--r--src/Subtitles/libssf/NodeFactory.h58
-rw-r--r--src/Subtitles/libssf/Rasterizer.cpp706
-rw-r--r--src/Subtitles/libssf/Rasterizer.h82
-rw-r--r--src/Subtitles/libssf/Renderer.cpp820
-rw-r--r--src/Subtitles/libssf/Renderer.h160
-rw-r--r--src/Subtitles/libssf/Split.cpp74
-rw-r--r--src/Subtitles/libssf/Split.h38
-rw-r--r--src/Subtitles/libssf/Stream.cpp360
-rw-r--r--src/Subtitles/libssf/Stream.h143
-rw-r--r--src/Subtitles/libssf/StringMap.cpp28
-rw-r--r--src/Subtitles/libssf/StringMap.h46
-rw-r--r--src/Subtitles/libssf/Subtitle.cpp702
-rw-r--r--src/Subtitles/libssf/Subtitle.h138
-rw-r--r--src/Subtitles/libssf/SubtitleFile.cpp403
-rw-r--r--src/Subtitles/libssf/SubtitleFile.h73
-rw-r--r--src/Subtitles/libssf/demo/demo.ssa87
-rw-r--r--src/Subtitles/libssf/demo/demo.ssf595
-rw-r--r--src/Subtitles/libssf/docs/ssf-specs.txt613
-rw-r--r--src/Subtitles/libssf/libssf.vcproj491
-rw-r--r--src/Subtitles/libssf/stdafx.cpp29
-rw-r--r--src/Subtitles/libssf/stdafx.h69
-rw-r--r--src/Subtitles/stdafx.cpp29
-rw-r--r--src/Subtitles/stdafx.h44
78 files changed, 30758 insertions, 0 deletions
diff --git a/src/Subtitles/BaseSub.cpp b/src/Subtitles/BaseSub.cpp
new file mode 100644
index 000000000..d93c57050
--- /dev/null
+++ b/src/Subtitles/BaseSub.cpp
@@ -0,0 +1,33 @@
+/*
+ * $Id$
+ *
+ * (C) 2006-2010 see AUTHORS
+ *
+ * This file is part of mplayerc.
+ *
+ * Mplayerc 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.
+ *
+ * Mplayerc 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "stdafx.h"
+#include "BaseSub.h"
+
+CBaseSub::CBaseSub(SUBTITLE_TYPE nType)
+ : m_nType(nType)
+{
+}
+
+CBaseSub::~CBaseSub()
+{
+} \ No newline at end of file
diff --git a/src/Subtitles/BaseSub.h b/src/Subtitles/BaseSub.h
new file mode 100644
index 000000000..3a824e614
--- /dev/null
+++ b/src/Subtitles/BaseSub.h
@@ -0,0 +1,54 @@
+/*
+ * $Id$
+ *
+ * (C) 2006-2010 see AUTHORS
+ *
+ * This file is part of mplayerc.
+ *
+ * Mplayerc 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.
+ *
+ * Mplayerc 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#pragma once
+
+#include "CompositionObject.h"
+
+enum SUBTITLE_TYPE
+{
+ ST_DVB,
+ ST_HDMV
+};
+
+class CBaseSub
+{
+public:
+
+ static const REFERENCE_TIME INVALID_TIME = _I64_MIN;
+
+ CBaseSub(SUBTITLE_TYPE nType);
+ virtual ~CBaseSub();
+
+ virtual HRESULT ParseSample (IMediaSample* pSample) = NULL;
+ virtual void Reset() = NULL;
+ virtual POSITION GetStartPosition(REFERENCE_TIME rt, double fps) = NULL;
+ virtual POSITION GetNext(POSITION pos) = NULL;
+ virtual REFERENCE_TIME GetStart(POSITION nPos) = NULL;
+ virtual REFERENCE_TIME GetStop(POSITION nPos) = NULL;
+ virtual void Render(SubPicDesc& spd, REFERENCE_TIME rt, RECT& bbox) = NULL;
+ virtual HRESULT GetTextureSize (POSITION pos, SIZE& MaxTextureSize, SIZE& VideoSize, POINT& VideoTopLeft) = NULL;
+
+protected :
+ SUBTITLE_TYPE m_nType;
+};
diff --git a/src/Subtitles/CCDecoder.cpp b/src/Subtitles/CCDecoder.cpp
new file mode 100644
index 000000000..735122ac2
--- /dev/null
+++ b/src/Subtitles/CCDecoder.cpp
@@ -0,0 +1,380 @@
+/*
+ * 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 "ccdecoder.h"
+
+CCDecoder::CCDecoder(CString fn, CString rawfn) : m_fn(fn), m_rawfn(rawfn)
+{
+ m_sts.CreateDefaultStyle(ANSI_CHARSET);
+
+ m_time = 0;
+ m_fEndOfCaption = false;
+ memset(m_buff, 0, sizeof(m_buff));
+ memset(m_disp, 0, sizeof(m_disp));
+ m_cursor = CPoint(0, 0);
+
+ if(!m_rawfn.IsEmpty())
+ _tremove(m_rawfn);
+}
+
+CCDecoder::~CCDecoder()
+{
+ if(!m_sts.IsEmpty() && !m_fn.IsEmpty())
+ {
+ m_sts.Sort();
+ m_sts.SaveAs(m_fn, EXTSRT, -1, CTextFile::ASCII);
+ m_sts.SaveAs(m_fn.Left(m_fn.ReverseFind('.')+1) + _T("utf8.srt"), EXTSRT, -1, CTextFile::UTF8);
+ m_sts.SaveAs(m_fn.Left(m_fn.ReverseFind('.')+1) + _T("utf16le.srt"), EXTSRT, -1, CTextFile::LE16);
+ m_sts.SaveAs(m_fn.Left(m_fn.ReverseFind('.')+1) + _T("utf16be.srt"), EXTSRT, -1, CTextFile::BE16);
+ }
+}
+
+void CCDecoder::MoveCursor(int x, int y)
+{
+ m_cursor = CPoint(x, y);
+ if(m_cursor.x < 0) m_cursor.x = 0;
+ if(m_cursor.y < 0) m_cursor.y = 0;
+ if(m_cursor.x >= 32) m_cursor.x = 0, m_cursor.y++;
+ if(m_cursor.y >= 16) m_cursor.y = 0;
+}
+
+void CCDecoder::OffsetCursor(int x, int y)
+{
+ MoveCursor(m_cursor.x + x, m_cursor.y + y);
+}
+
+void CCDecoder::PutChar(WCHAR c)
+{
+ m_buff[m_cursor.y][m_cursor.x] = c;
+ OffsetCursor(1, 0);
+}
+
+void CCDecoder::SaveDisp(__int64 time)
+{
+ CStringW str;
+
+ for(ptrdiff_t row = 0; row < 16; row++)
+ {
+ bool fNonEmptyRow = false;
+
+ for(ptrdiff_t col = 0; col < 32; col++)
+ {
+ if(m_disp[row][col])
+ {
+ CStringW str2(&m_disp[row][col]);
+ if(fNonEmptyRow) str += ' ';
+ str += str2;
+ col += str2.GetLength();
+ fNonEmptyRow = true;
+ }
+ }
+
+ if(fNonEmptyRow) str += '\n';
+ }
+
+ if(str.IsEmpty()) return;
+
+ m_sts.Add(str, true, (int)m_time, (int)time);
+}
+
+void CCDecoder::DecodeCC(BYTE* buff, int len, __int64 time)
+{
+ if(!m_rawfn.IsEmpty())
+ {
+ if(FILE* f = _tfopen(m_rawfn, _T("at")))
+ {
+ _ftprintf(f, _T("%02d:%02d:%02d.%03d\n"),
+ (int)(time/1000/60/60),
+ (int)((time/1000/60)%60),
+ (int)((time/1000)%60),
+ (int)(time%1000));
+
+ for(ptrdiff_t i = 0; i < len; i++)
+ {
+ _ftprintf(f, _T("%02x"), buff[i]);
+ if(i < len-1) _ftprintf(f, _T(" "));
+ if(i > 0 && (i&15)==15) _ftprintf(f, _T("\n"));
+ }
+ if(len > 0) _ftprintf(f, _T("\n\n"));
+ fclose(f);
+ }
+ }
+
+ for(ptrdiff_t i = 0; i < len; i++)
+ {
+ BYTE c = buff[i]&0x7f;
+ if(c >= 0x20)
+ {
+ static WCHAR charmap[0x60] =
+ {
+ ' ','!','"','#','$','%','&','\'','(',')',0xE1,'+',',','-','.','/',
+ '0','1','2','3','4','5','6','7','8','9',':',';','<','=','>',0x3F,
+ '@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
+ 'P','Q','R','S','T','U','V','W','X','Y','Z','[',0xE9,']',0xED,0xF3,
+ 0xFA,'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o',
+ 'p','q','r','s','t','u','v','w','x','y','z',0xE7,0xF7,'N','n',0x3F
+ };
+
+ PutChar(charmap[c - 0x20]);
+ }
+ else if(buff[i] != 0x80 && i < len-1)
+ {
+ // codes and special characters are supposed to be doubled
+ if(i < len-3 && buff[i] == buff[i+2] && buff[i+1] == buff[i+3])
+ i += 2;
+
+ c = buff[i+1]&0x7f;
+ if(buff[i] == 0x91 && c >= 0x20 && c < 0x30) // formating
+ {
+ // TODO
+ }
+ else if(buff[i] == 0x91 && c == 0x39) // transparent space
+ {
+ OffsetCursor(1, 0);
+ }
+ else if(buff[i] == 0x91 && c >= 0x30 && c < 0x40) // special characters
+ {
+ static WCHAR charmap[0x10] =
+ {
+ 0x00ae, // (r)egistered
+ 0x00b0, // degree
+ 0x00bd, // 1/2
+ 0x00bf, // inverted question mark
+ 0x2122, // trade mark
+ 0x00a2, // cent
+ 0x00a3, // pound
+ 0x266a, // music
+ 0x00e0, // a`
+ 0x00ff, // transparent space, handled above
+ 0x00e8, // e`
+ 0x00e2, // a^
+ 0x00ea, // e^
+ 0x00ee, // i^
+ 0x00f4, // o^
+ 0x00fb, // u^
+ };
+
+ PutChar(charmap[c - 0x30]);
+ }
+ else if(buff[i] == 0x92 && c >= 0x20 && c < 0x40) // extended characters
+ {
+ static WCHAR charmap[0x20] =
+ {
+ 0x00c0, // A'
+ 0x00c9, // E'
+ 0x00d3, // O'
+ 0x00da, // U'
+ 0x00dc, // U:
+ 0x00fc, // u:
+ 0x2018, // `
+ 0x00a1, // inverted !
+ 0x002a, // *
+ 0x2019, // '
+ 0x002d, // -
+ 0x00a9, // (c)opyright
+ 0x2120, // SM
+ 0x00b7, // . (dot in the middle)
+ 0x201c, // inverted "
+ 0x201d, // "
+
+ 0x00c1, // A`
+ 0x00c2, // A^
+ 0x00c7, // C,
+ 0x00c8, // E`
+ 0x00ca, // E^
+ 0x00cb, // E:
+ 0x00eb, // e:
+ 0x00ce, // I^
+ 0x00cf, // I:
+ 0x00ef, // i:
+ 0x00d4, // O^
+ 0x00d9, // U`
+ 0x00f9, // u`
+ 0x00db, // U^
+ 0x00ab, // <<
+ 0x00bb, // >>
+ };
+
+ PutChar(charmap[c - 0x20]);
+ }
+ else if(buff[i] == 0x13 && c >= 0x20 && c < 0x40) // more extended characters
+ {
+ static WCHAR charmap[0x20] =
+ {
+ 0x00c3, // A~
+ 0x00e3, // a~
+ 0x00cd, // I'
+ 0x00cc, // I`
+ 0x00ec, // i`
+ 0x00d2, // O`
+ 0x00f2, // o`
+ 0x00d5, // O~
+ 0x00f5, // o~
+ 0x007b, // {
+ 0x007d, // }
+ 0x005c, // /* \ */
+ 0x005e, // ^
+ 0x005f, // _
+ 0x00a6, // |
+ 0x007e, // ~
+
+ 0x00c4, // A:
+ 0x00e4, // a:
+ 0x00d6, // O:
+ 0x00f6, // o:
+ 0x00df, // B (ss in german)
+ 0x00a5, // Y=
+ 0x00a4, // ox
+ 0x007c, // |
+ 0x00c5, // Ao
+ 0x00e5, // ao
+ 0x00d8, // O/
+ 0x00f8, // o/
+ 0x250c, // |-
+ 0x2510, // -|
+ 0x2514, // |_
+ 0x2518, // _|
+ };
+
+ PutChar(charmap[c - 0x20]);
+ }
+ else if(buff[i] == 0x94 && buff[i+1] == 0xae) // Erase Non-displayed [buffer] Memory
+ {
+ memset(m_buff, 0, sizeof(m_buff));
+ }
+ else if(buff[i] == 0x94 && buff[i+1] == 0x20) // Resume Caption Loading
+ {
+ memset(m_buff, 0, sizeof(m_buff));
+ }
+ else if(buff[i] == 0x94 && buff[i+1] == 0x2f) // End Of Caption
+ {
+ if(memcmp(m_disp, m_buff, sizeof(m_disp)) != 0)
+ {
+ if(m_fEndOfCaption)
+ SaveDisp(time + (i/2)*1000/30);
+
+ m_fEndOfCaption = true;
+ memcpy(m_disp, m_buff, sizeof(m_disp));
+ m_time = time + (i/2)*1000/30;
+ }
+ }
+ else if(buff[i] == 0x94 && buff[i+1] == 0x2c) // Erase Displayed Memory
+ {
+ if(m_fEndOfCaption)
+ {
+ m_fEndOfCaption = false;
+ SaveDisp(time + (i/2)*1000/30);
+ }
+
+ memset(m_disp, 0, sizeof(m_disp));
+ }
+ else if(buff[i] == 0x97 && (buff[i+1] == 0xa1 || buff[i+1] == 0xa2 || buff[i+1] == 0x23)) // Tab Over
+ {
+ OffsetCursor(buff[i+1]&3, 0);
+ }
+ else if(buff[i] == 0x91 || buff[i] == 0x92 || buff[i] == 0x15 || buff[i] == 0x16
+ || buff[i] == 0x97 || buff[i] == 0x10 || buff[i] == 0x13 || buff[i] == 0x94) // curpos, color, underline
+ {
+ int row = 0;
+ switch(buff[i])
+ {
+ default:
+ case 0x91: row = 0; break;
+ case 0x92: row = 2; break;
+ case 0x15: row = 4; break;
+ case 0x16: row = 6; break;
+ case 0x97: row = 8; break;
+ case 0x10: row = 10; break;
+ case 0x13: row = 12; break;
+ case 0x94: row = 14; break;
+ }
+ if(buff[i+1]&0x20) row++;
+
+ int col = buff[i+1]&0xe;
+ if(col == 0 || (col > 0 && !(buff[i+1]&0x10))) col = 0;
+ else col <<= 1;
+
+ MoveCursor(col, row);
+ }
+
+ i++;
+ }
+ }
+}
+
+void CCDecoder::ExtractCC(BYTE* buff, int len, __int64 time)
+{
+ for(ptrdiff_t i = 0; i < len-9; i++)
+ {
+ if(*(DWORD*)&buff[i] == 0xb2010000 && *(DWORD*)&buff[i+4] == 0xf8014343)
+ {
+ i += 8;
+ int nBytes = buff[i++]&0x3f;
+ if(nBytes > 0)
+ {
+ nBytes = (nBytes+1)&~1;
+
+ BYTE* pData1 = new BYTE[nBytes];
+ BYTE* pData2 = new BYTE[nBytes];
+
+ if(pData1 && pData2)
+ {
+ int nBytes1 = 0, nBytes2 = 0;
+
+ for(ptrdiff_t j = 0; j < nBytes && i < 0x800;)
+ {
+ if(buff[i++] == 0xff)
+ {
+ pData1[nBytes1++] = buff[i++];
+ pData1[nBytes1++] = buff[i++];
+ }
+ else i+=2;
+
+ j++;
+
+ if(j >= nBytes) break;
+
+ if(buff[i++] == 0xff)
+ {
+ pData2[nBytes2++] = buff[i++];
+ pData2[nBytes2++] = buff[i++];
+ }
+ else i+=2;
+
+ j++;
+ }
+
+ if(nBytes1 > 0)
+ DecodeCC(pData1, nBytes1, time);
+
+ if(nBytes2 > 0)
+ DecodeCC(pData2, nBytes2, time);
+ }
+
+ if(pData1) delete [] pData1;
+ if(pData2) delete [] pData2;
+ }
+
+ break;
+ }
+ }
+}
diff --git a/src/Subtitles/CCDecoder.h b/src/Subtitles/CCDecoder.h
new file mode 100644
index 000000000..5733de9ca
--- /dev/null
+++ b/src/Subtitles/CCDecoder.h
@@ -0,0 +1,48 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include "STS.h"
+
+class CCDecoder
+{
+ CSimpleTextSubtitle m_sts;
+ CString m_fn, m_rawfn;
+ __int64 m_time;
+ bool m_fEndOfCaption;
+ WCHAR m_buff[16][33], m_disp[16][33];
+ CPoint m_cursor;
+
+ void SaveDisp(__int64 time);
+ void MoveCursor(int x, int y);
+ void OffsetCursor(int x, int y);
+ void PutChar(WCHAR c);
+
+public:
+ CCDecoder(CString fn = _T(""), CString rawfn = _T(""));
+ virtual ~CCDecoder();
+ void DecodeCC(BYTE* buff, int len, __int64 time);
+ void ExtractCC(BYTE* buff, int len, __int64 time);
+ CSimpleTextSubtitle& GetSTS() {return m_sts;}
+};
+
+
diff --git a/src/Subtitles/CompositionObject.cpp b/src/Subtitles/CompositionObject.cpp
new file mode 100644
index 000000000..e8b2c0f57
--- /dev/null
+++ b/src/Subtitles/CompositionObject.cpp
@@ -0,0 +1,400 @@
+/*
+ * $Id$
+ *
+ * (C) 2006-2010 see AUTHORS
+ *
+ * This file is part of mplayerc.
+ *
+ * Mplayerc 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.
+ *
+ * Mplayerc 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "stdafx.h"
+#include "CompositionObject.h"
+#include "../DSUtil/GolombBuffer.h"
+
+
+
+CompositionObject::CompositionObject()
+{
+ m_rtStart = 0;
+ m_rtStop = 0;
+ m_pRLEData = NULL;
+ m_nRLEDataSize = 0;
+ m_nRLEPos = 0;
+ m_nColorNumber = 0;
+ memsetd (m_Colors, 0xFF000000, sizeof(m_Colors));
+}
+
+CompositionObject::~CompositionObject()
+{
+ delete[] m_pRLEData;
+}
+
+void CompositionObject::SetPalette (int nNbEntry, HDMV_PALETTE* pPalette, bool bIsHD)
+{
+ m_nColorNumber = nNbEntry;
+
+ for (int i=0; i<m_nColorNumber; i++)
+ {
+// if (pPalette[i].T != 0) // Prevent ugly background when Alpha=0 (but RGB different from 0)
+ {
+ if (bIsHD)
+ m_Colors[pPalette[i].entry_id] = YCrCbToRGB_Rec709 (pPalette[i].T, pPalette[i].Y, pPalette[i].Cr, pPalette[i].Cb);
+ else
+ m_Colors[pPalette[i].entry_id] = YCrCbToRGB_Rec601 (pPalette[i].T, pPalette[i].Y, pPalette[i].Cr, pPalette[i].Cb);
+ }
+// TRACE_HDMVSUB ("%03d : %08x\n", pPalette[i].entry_id, m_Colors[pPalette[i].entry_id]);
+ }
+}
+
+
+void CompositionObject::SetRLEData(BYTE* pBuffer, int nSize, int nTotalSize)
+{
+ delete[] m_pRLEData;
+ m_pRLEData = DNew BYTE[nTotalSize];
+ m_nRLEDataSize = nTotalSize;
+ m_nRLEPos = nSize;
+
+ memcpy (m_pRLEData, pBuffer, nSize);
+}
+
+void CompositionObject::AppendRLEData(BYTE* pBuffer, int nSize)
+{
+ ASSERT (m_nRLEPos+nSize <= m_nRLEDataSize);
+ if (m_nRLEPos+nSize <= m_nRLEDataSize)
+ {
+ memcpy (m_pRLEData+m_nRLEPos, pBuffer, nSize);
+ m_nRLEPos += nSize;
+ }
+}
+
+
+void CompositionObject::RenderHdmv(SubPicDesc& spd)
+{
+ if (!m_pRLEData)
+ return;
+
+ CGolombBuffer GBuffer (m_pRLEData, m_nRLEDataSize);
+ BYTE bTemp;
+ BYTE bSwitch;
+
+ BYTE nPaletteIndex = 0;
+ SHORT nCount;
+ SHORT nX = 0;
+ SHORT nY = 0;
+
+ while ((nY < m_height) && !GBuffer.IsEOF())
+ {
+ bTemp = GBuffer.ReadByte();
+ if (bTemp != 0)
+ {
+ nPaletteIndex = bTemp;
+ nCount = 1;
+ }
+ else
+ {
+ bSwitch = GBuffer.ReadByte();
+ if (!(bSwitch & 0x80))
+ {
+ if (!(bSwitch & 0x40))
+ {
+ nCount = bSwitch & 0x3F;
+ if (nCount > 0)
+ nPaletteIndex = 0;
+ }
+ else
+ {
+ nCount = (bSwitch&0x3F) <<8 | (SHORT)GBuffer.ReadByte();
+ nPaletteIndex = 0;
+ }
+ }
+ else
+ {
+ if (!(bSwitch & 0x40))
+ {
+ nCount = bSwitch & 0x3F;
+ nPaletteIndex = GBuffer.ReadByte();
+ }
+ else
+ {
+ nCount = (bSwitch&0x3F) <<8 | (SHORT)GBuffer.ReadByte();
+ nPaletteIndex = GBuffer.ReadByte();
+ }
+ }
+ }
+
+ if (nCount>0)
+ {
+ if (nPaletteIndex != 0xFF) // Fully transparent (§9.14.4.2.2.1.1)
+ FillSolidRect (spd, nX, nY, nCount, 1, m_Colors[nPaletteIndex]);
+ nX += nCount;
+ }
+ else
+ {
+ nY++;
+ nX = 0;
+ }
+ }
+}
+
+
+void CompositionObject::RenderDvb(SubPicDesc& spd, SHORT nX, SHORT nY)
+{
+ if (!m_pRLEData)
+ return;
+
+ CGolombBuffer gb (m_pRLEData, m_nRLEDataSize);
+ SHORT sTopFieldLength;
+ SHORT sBottomFieldLength;
+
+ sTopFieldLength = gb.ReadShort();
+ sBottomFieldLength = gb.ReadShort();
+
+ DvbRenderField (spd, gb, nX, nY, sTopFieldLength);
+ DvbRenderField (spd, gb, nX, nY+1, sBottomFieldLength);
+}
+
+
+void CompositionObject::DvbRenderField(SubPicDesc& spd, CGolombBuffer& gb, SHORT nXStart, SHORT nYStart, SHORT nLength)
+{
+ //FillSolidRect (spd, 0, 0, 300, 10, 0xFFFF0000); // Red opaque
+ //FillSolidRect (spd, 0, 10, 300, 10, 0xCC00FF00); // Green 80%
+ //FillSolidRect (spd, 0, 20, 300, 10, 0x100000FF); // Blue 60%
+ //return;
+ SHORT nX = nXStart;
+ SHORT nY = nYStart;
+ INT64 nEnd = gb.GetPos()+nLength;
+ while (gb.GetPos() < nEnd)
+ {
+ BYTE bType = gb.ReadByte();
+ switch (bType)
+ {
+ case 0x10 :
+ Dvb2PixelsCodeString(spd, gb, nX, nY);
+ break;
+ case 0x11 :
+ Dvb4PixelsCodeString(spd, gb, nX, nY);
+ break;
+ case 0x12 :
+ Dvb8PixelsCodeString(spd, gb, nX, nY);
+ break;
+ case 0x20 :
+ gb.SkipBytes (2);
+ break;
+ case 0x21 :
+ gb.SkipBytes (4);
+ break;
+ case 0x22 :
+ gb.SkipBytes (16);
+ break;
+ case 0xF0 :
+ nX = nXStart;
+ nY += 2;
+ break;
+ default :
+ ASSERT(FALSE);
+ break;
+ }
+ }
+}
+
+
+void CompositionObject::Dvb2PixelsCodeString(SubPicDesc& spd, CGolombBuffer& gb, SHORT& nX, SHORT& nY)
+{
+ BYTE bTemp;
+ BYTE nPaletteIndex = 0;
+ SHORT nCount;
+ bool bQuit = false;
+
+ while (!bQuit && !gb.IsEOF())
+ {
+ nCount = 0;
+ nPaletteIndex = 0;
+ bTemp = (BYTE)gb.BitRead(2);
+ if (bTemp != 0)
+ {
+ nPaletteIndex = bTemp;
+ nCount = 1;
+ }
+ else
+ {
+ if (gb.BitRead(1) == 1) // switch_1
+ {
+ nCount = 3 + (SHORT)gb.BitRead(3); // run_length_3-9
+ nPaletteIndex = (BYTE)gb.BitRead(2);
+ }
+ else
+ {
+ if (gb.BitRead(1) == 0) // switch_2
+ {
+ switch (gb.BitRead(2)) // switch_3
+ {
+ case 0 :
+ bQuit = true;
+ break;
+ case 1 :
+ nCount = 2;
+ break;
+ case 2 : // if (switch_3 == '10')
+ nCount = 12 + (SHORT)gb.BitRead(4); // run_length_12-27
+ nPaletteIndex = (BYTE)gb.BitRead(2); // 4-bit_pixel-code
+ break;
+ case 3 :
+ nCount = 29 + gb.ReadByte(); // run_length_29-284
+ nPaletteIndex = (BYTE)gb.BitRead(2); // 4-bit_pixel-code
+ break;
+ }
+ }
+ else
+ nCount = 1;
+ }
+ }
+
+ if (nX+nCount > m_width)
+ {
+ ASSERT (FALSE);
+ break;
+ }
+
+ if (nCount>0)
+ {
+ FillSolidRect (spd, nX, nY, nCount, 1, m_Colors[nPaletteIndex]);
+ nX += nCount;
+ }
+ }
+
+ gb.BitByteAlign();
+}
+
+void CompositionObject::Dvb4PixelsCodeString(SubPicDesc& spd, CGolombBuffer& gb, SHORT& nX, SHORT& nY)
+{
+ BYTE bTemp;
+ BYTE nPaletteIndex = 0;
+ SHORT nCount;
+ bool bQuit = false;
+
+ while (!bQuit && !gb.IsEOF())
+ {
+ nCount = 0;
+ nPaletteIndex = 0;
+ bTemp = (BYTE)gb.BitRead(4);
+ if (bTemp != 0)
+ {
+ nPaletteIndex = bTemp;
+ nCount = 1;
+ }
+ else
+ {
+ if (gb.BitRead(1) == 0) // switch_1
+ {
+ nCount = (SHORT)gb.BitRead(3); // run_length_3-9
+ if (nCount != 0)
+ nCount += 2;
+ else
+ bQuit = true;
+ }
+ else
+ {
+ if (gb.BitRead(1) == 0) // switch_2
+ {
+ nCount = 4 + (SHORT)gb.BitRead(2); // run_length_4-7
+ nPaletteIndex = (BYTE)gb.BitRead(4); // 4-bit_pixel-code
+ }
+ else
+ {
+ switch (gb.BitRead(2)) // switch_3
+ {
+ case 0 :
+ nCount = 1;
+ break;
+ case 1 :
+ nCount = 2;
+ break;
+ case 2 : // if (switch_3 == '10')
+ nCount = 9 + (SHORT)gb.BitRead(4); // run_length_9-24
+ nPaletteIndex = (BYTE)gb.BitRead(4); // 4-bit_pixel-code
+ break;
+ case 3 :
+ nCount = 25 + gb.ReadByte(); // run_length_25-280
+ nPaletteIndex = (BYTE)gb.BitRead(4); // 4-bit_pixel-code
+ break;
+ }
+ }
+ }
+ }
+
+ if (nX+nCount > m_width)
+ {
+ ASSERT (FALSE);
+ break;
+ }
+
+ if (nCount>0)
+ {
+ FillSolidRect (spd, nX, nY, nCount, 1, m_Colors[nPaletteIndex]);
+ nX += nCount;
+ }
+ }
+
+ gb.BitByteAlign();
+}
+
+void CompositionObject::Dvb8PixelsCodeString(SubPicDesc& spd, CGolombBuffer& gb, SHORT& nX, SHORT& nY)
+{
+ BYTE bTemp;
+ BYTE nPaletteIndex = 0;
+ SHORT nCount;
+ bool bQuit = false;
+
+ while (!bQuit && !gb.IsEOF())
+ {
+ nCount = 0;
+ nPaletteIndex = 0;
+ bTemp = gb.ReadByte();
+ if (bTemp != 0)
+ {
+ nPaletteIndex = bTemp;
+ nCount = 1;
+ }
+ else
+ {
+ if (gb.BitRead(1) == 0) // switch_1
+ {
+ nCount = (SHORT)gb.BitRead(7); // run_length_1-127
+ if (nCount == 0)
+ bQuit = true;
+ }
+ else
+ {
+ nCount = (SHORT)gb.BitRead(7); // run_length_3-127
+ nPaletteIndex = gb.ReadByte();
+ }
+ }
+
+ if (nX+nCount > m_width)
+ {
+ ASSERT (FALSE);
+ break;
+ }
+
+ if (nCount>0)
+ {
+ FillSolidRect (spd, nX, nY, nCount, 1, m_Colors[nPaletteIndex]);
+ nX += nCount;
+ }
+ }
+
+ gb.BitByteAlign();
+}
diff --git a/src/Subtitles/CompositionObject.h b/src/Subtitles/CompositionObject.h
new file mode 100644
index 000000000..672b3d6d1
--- /dev/null
+++ b/src/Subtitles/CompositionObject.h
@@ -0,0 +1,87 @@
+/*
+ * $Id$
+ *
+ * (C) 2006-2010 see AUTHORS
+ *
+ * This file is part of mplayerc.
+ *
+ * Mplayerc 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.
+ *
+ * Mplayerc 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#pragma once
+
+#include "Rasterizer.h"
+
+
+struct HDMV_PALETTE
+{
+ BYTE entry_id;
+ BYTE Y;
+ BYTE Cr;
+ BYTE Cb;
+ BYTE T; // HDMV rule : 0 transparent, 255 opaque (compatible DirectX)
+};
+
+class CGolombBuffer;
+
+class CompositionObject : Rasterizer
+{
+public :
+ SHORT m_object_id_ref;
+ BYTE m_window_id_ref;
+ bool m_object_cropped_flag;
+ bool m_forced_on_flag;
+ BYTE m_version_number;
+
+ SHORT m_horizontal_position;
+ SHORT m_vertical_position;
+ SHORT m_width;
+ SHORT m_height;
+
+ SHORT m_cropping_horizontal_position;
+ SHORT m_cropping_vertical_position;
+ SHORT m_cropping_width;
+ SHORT m_cropping_height;
+
+ REFERENCE_TIME m_rtStart;
+ REFERENCE_TIME m_rtStop;
+
+ CompositionObject();
+ ~CompositionObject();
+
+ void SetRLEData(BYTE* pBuffer, int nSize, int nTotalSize);
+ void AppendRLEData(BYTE* pBuffer, int nSize);
+ int GetRLEDataSize() { return m_nRLEDataSize; };
+ bool IsRLEComplete() { return m_nRLEPos >= m_nRLEDataSize; };
+ void RenderHdmv(SubPicDesc& spd);
+ void RenderDvb(SubPicDesc& spd, SHORT nX, SHORT nY);
+ void WriteSeg (SubPicDesc& spd, SHORT nX, SHORT nY, SHORT nCount, SHORT nPaletteIndex);
+ void SetPalette (int nNbEntry, HDMV_PALETTE* pPalette, bool bIsHD);
+ void SetPalette (int nNbEntry, DWORD* dwColors);
+ bool HavePalette() { return m_nColorNumber>0; };
+
+private :
+ BYTE* m_pRLEData;
+ int m_nRLEDataSize;
+ int m_nRLEPos;
+ int m_nColorNumber;
+ DWORD m_Colors[256];
+
+ void DvbRenderField(SubPicDesc& spd, CGolombBuffer& gb, SHORT nXStart, SHORT nYStart, SHORT nLength);
+ void Dvb2PixelsCodeString(SubPicDesc& spd, CGolombBuffer& gb, SHORT& nX, SHORT& nY);
+ void Dvb4PixelsCodeString(SubPicDesc& spd, CGolombBuffer& gb, SHORT& nX, SHORT& nY);
+ void Dvb8PixelsCodeString(SubPicDesc& spd, CGolombBuffer& gb, SHORT& nX, SHORT& nY);
+};
diff --git a/src/Subtitles/DVBSub.cpp b/src/Subtitles/DVBSub.cpp
new file mode 100644
index 000000000..0f1801ea4
--- /dev/null
+++ b/src/Subtitles/DVBSub.cpp
@@ -0,0 +1,596 @@
+/*
+ * $Id$
+ *
+ * (C) 2006-2010 see AUTHORS
+ *
+ * This file is part of mplayerc.
+ *
+ * Mplayerc 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.
+ *
+ * Mplayerc 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include "stdafx.h"
+#include "DVBSub.h"
+#include "../DSUtil/GolombBuffer.h"
+
+#if (1) // Set to 1 to activate DVB subtitles traces
+ #define TRACE_DVB TRACE
+#else
+ #define TRACE_DVB
+#endif
+
+#define BUFFER_CHUNK_GROW 0x1000
+
+CDVBSub::CDVBSub(void)
+ : CBaseSub(ST_DVB)
+{
+ m_nBufferReadPos = 0;
+ m_nBufferWritePos = 0;
+ m_nBufferSize = 0;
+ m_pBuffer = NULL;
+}
+
+CDVBSub::~CDVBSub(void)
+{
+ Reset();
+ SAFE_DELETE(m_pBuffer);
+}
+
+CDVBSub::DVB_PAGE* CDVBSub::FindPage(REFERENCE_TIME rt)
+{
+ POSITION pos = m_Pages.GetHeadPosition();
+
+ while (pos)
+ {
+ DVB_PAGE* pPage = m_Pages.GetAt (pos);
+
+ if (rt >= pPage->rtStart && rt < pPage->rtStop)
+ return pPage;
+
+ m_Pages.GetNext(pos);
+ }
+
+ return NULL;
+}
+
+CDVBSub::DVB_REGION* CDVBSub::FindRegion(DVB_PAGE* pPage, BYTE bRegionId)
+{
+ if (pPage != NULL)
+ {
+ for (int i=0; i<pPage->RegionCount; i++)
+ {
+ if (pPage->Regions[i].Id == bRegionId)
+ return &pPage->Regions[i];
+ }
+ }
+ return NULL;
+}
+
+CDVBSub::DVB_CLUT* CDVBSub::FindClut(DVB_PAGE* pPage, BYTE bClutId)
+{
+ if (pPage != NULL)
+ {
+ for (int i=0; i<pPage->RegionCount; i++)
+ {
+ if (pPage->Regions[i].CLUT_id == bClutId)
+ return &pPage->Regions[i].Clut;
+ }
+ }
+ return NULL;
+}
+
+CompositionObject* CDVBSub::FindObject(DVB_PAGE* pPage, SHORT sObjectId)
+{
+ if (pPage != NULL)
+ {
+ POSITION pos = pPage->Objects.GetHeadPosition();
+
+ while (pos)
+ {
+ CompositionObject* pObject = pPage->Objects.GetAt (pos);
+
+ if (pObject->m_object_id_ref == sObjectId)
+ return pObject;
+
+ pPage->Objects.GetNext(pos);
+ }
+ }
+ return NULL;
+}
+
+HRESULT CDVBSub::AddToBuffer(BYTE* pData, int nSize)
+{
+ bool bFirstChunk = (*((LONG*)pData) & 0x00FFFFFF) == 0x000f0020; // DVB sub start with 0x20 0x00 0x0F ...
+
+ if (m_nBufferWritePos > 0 || bFirstChunk)
+ {
+ if (bFirstChunk)
+ {
+ m_nBufferWritePos = 0;
+ m_nBufferReadPos = 0;
+ }
+
+ if (m_nBufferWritePos+nSize > m_nBufferSize)
+ {
+ if (m_nBufferWritePos+nSize > 20*BUFFER_CHUNK_GROW)
+ {
+ // Too big to be a DVB sub !
+ TRACE_DVB ("DVB - Too much data receive...\n");
+ ASSERT (FALSE);
+
+ Reset();
+ return E_INVALIDARG;
+ }
+
+ BYTE* pPrev = m_pBuffer;
+ m_nBufferSize = max (m_nBufferWritePos+nSize, m_nBufferSize+BUFFER_CHUNK_GROW);
+ m_pBuffer = new BYTE[m_nBufferSize];
+ if (pPrev != NULL)
+ {
+ memcpy_s (m_pBuffer, m_nBufferSize, pPrev, m_nBufferWritePos);
+ SAFE_DELETE (pPrev);
+ }
+ }
+ memcpy_s (m_pBuffer+m_nBufferWritePos, m_nBufferSize, pData, nSize);
+ m_nBufferWritePos += nSize;
+ return S_OK;
+ }
+ return S_FALSE;
+}
+
+#define MARKER if(gb.BitRead(1) != 1) {ASSERT(0); return(E_FAIL);}
+
+HRESULT CDVBSub::ParseSample (IMediaSample* pSample)
+{
+ CheckPointer (pSample, E_POINTER);
+ HRESULT hr;
+ BYTE* pData = NULL;
+ int nSize;
+ DVB_SEGMENT_TYPE nCurSegment;
+
+ hr = pSample->GetPointer(&pData);
+ if(FAILED(hr) || pData == NULL) return hr;
+ nSize = pSample->GetActualDataLength();
+
+ if (*((LONG*)pData) == 0xBD010000)
+ {
+ CGolombBuffer gb (pData, nSize);
+
+ gb.SkipBytes(4);
+ WORD wLength = (WORD)gb.BitRead(16);
+ UNUSED_ALWAYS(wLength);
+
+ if (gb.BitRead(2) != 2) return E_FAIL; // type
+
+ gb.BitRead(2); // scrambling
+ gb.BitRead(1); // priority
+ gb.BitRead(1); // alignment
+ gb.BitRead(1); // copyright
+ gb.BitRead(1); // original
+ BYTE fpts = (BYTE)gb.BitRead(1); // fpts
+ BYTE fdts = (BYTE)gb.BitRead(1); // fdts
+ gb.BitRead(1); // escr
+ gb.BitRead(1); // esrate
+ gb.BitRead(1); // dsmtrickmode
+ gb.BitRead(1); // morecopyright
+ gb.BitRead(1); // crc
+ gb.BitRead(1); // extension
+ gb.BitRead(8); // hdrlen
+
+ if(fpts)
+ {
+ BYTE b = (BYTE)gb.BitRead(4);
+ if(!(fdts && b == 3 || !fdts && b == 2)) {ASSERT(0); return(E_FAIL);}
+
+ REFERENCE_TIME pts = 0;
+ pts |= gb.BitRead(3) << 30; MARKER; // 32..30
+ pts |= gb.BitRead(15) << 15; MARKER; // 29..15
+ pts |= gb.BitRead(15); MARKER; // 14..0
+ pts = 10000*pts/90;
+
+ m_rtStart = pts;
+ m_rtStop = pts+1;
+ }
+ else
+ {
+ m_rtStart = INVALID_TIME;
+ m_rtStop = INVALID_TIME;
+ }
+
+ nSize -= 14;
+ pData += 14;
+ pSample->GetTime(&m_rtStart, &m_rtStop);
+ pSample->GetMediaTime(&m_rtStart, &m_rtStop);
+ }
+ else
+ if (SUCCEEDED (pSample->GetTime(&m_rtStart, &m_rtStop)))
+ pSample->SetTime(&m_rtStart, &m_rtStop);
+
+ //FILE* hFile = fopen ("D:\\Sources\\mpc-hc\\A garder\\TestSubRip\\dvbsub.dat", "ab");
+ //if(hFile != NULL)
+ //{
+ // //BYTE Buff[5] = {48};
+
+ // //*((DWORD*)(Buff+1)) = lSampleLen;
+ // //fwrite (Buff, 1, sizeof(Buff), hFile);
+ // fwrite (pData, 1, lSampleLen, hFile);
+ // fclose(hFile);
+ //}
+
+ if (AddToBuffer (pData, nSize) == S_OK)
+ {
+ CGolombBuffer gb (m_pBuffer+m_nBufferReadPos, m_nBufferWritePos-m_nBufferReadPos);
+ int nLastPos = 0;
+
+ while (!gb.IsEOF())
+ {
+ if (gb.ReadByte() == 0x0F)
+ {
+ WORD wPageId;
+ WORD wSegLength;
+
+ nCurSegment = (DVB_SEGMENT_TYPE) gb.ReadByte();
+ wPageId = gb.ReadShort();
+ wSegLength = gb.ReadShort();
+
+ if (gb.RemainingSize() < wSegLength)
+ {
+ hr = S_FALSE;
+ break;
+ }
+
+ switch (nCurSegment)
+ {
+ case PAGE :
+ {
+ CAutoPtr<DVB_PAGE> pPage;
+ ParsePage(gb, wSegLength, pPage);
+
+ if (pPage->PageState == DPS_ACQUISITION)
+ {
+ m_pCurrentPage = pPage;
+ m_pCurrentPage->rtStart = m_rtStart;
+ TRACE_DVB ("DVB - Page started %S\n", ReftimeToString(m_rtStart));
+ m_rtStart = INVALID_TIME;
+ }
+ else
+ TRACE_DVB ("DVB - Page update\n");
+ }
+ break;
+ case REGION :
+ ParseRegion(gb, wSegLength);
+ TRACE_DVB ("DVB - Region\n");
+ break;
+ case CLUT :
+ ParseClut(gb, wSegLength);
+ TRACE_DVB ("DVB - Clut \n");
+ break;
+ case OBJECT :
+ ParseObject(gb, wSegLength);
+ TRACE_DVB ("DVB - Object\n");
+ break;
+ case DISPLAY :
+ ParseDisplay(gb, wSegLength);
+ break;
+ case END_OF_DISPLAY :
+ if (m_pCurrentPage != NULL && m_rtStart != INVALID_TIME)
+ {
+ m_pCurrentPage->rtStop = m_rtStart;
+ TRACE_DVB ("DVB - End display %S - %S\n", ReftimeToString(m_pCurrentPage->rtStart), ReftimeToString(m_pCurrentPage->rtStop));
+ m_Pages.AddTail (m_pCurrentPage.Detach());
+ }
+ break;
+ default :
+// gb.SkipBytes(wSegLength);
+ break;
+ }
+ nLastPos = gb.GetPos();
+ }
+ }
+ m_nBufferReadPos += nLastPos;
+ }
+
+ return hr;
+}
+
+void CDVBSub::Render(SubPicDesc& spd, REFERENCE_TIME rt, RECT& bbox)
+{
+ DVB_PAGE* pPage = FindPage (rt);
+
+ if (pPage != NULL)
+ {
+ pPage->Rendered = true;
+ for (int i=0; i<pPage->RegionCount; i++)
+ {
+ CDVBSub::DVB_REGION* pRegion = &pPage->Regions[i];
+ for (int j=0; j<pRegion->ObjectCount; j++)
+ {
+ CompositionObject* pObject = FindObject (pPage, pRegion->Objects[j].object_id);
+ if (pObject)
+ {
+ SHORT nX, nY;
+ nX = pRegion->HorizAddr + pRegion->Objects[j].object_horizontal_position;
+ nY = pRegion->VertAddr + pRegion->Objects[j].object_vertical_position;
+ pObject->m_width = pRegion->width;
+ pObject->m_height = pRegion->height;
+ pObject->SetPalette(pRegion->Clut.Size, pRegion->Clut.Palette, false);
+ pObject->RenderDvb(spd, nX, nY);
+ }
+ }
+ }
+
+ bbox.left = 0;
+ bbox.top = 0;
+ bbox.right = m_Display.width;
+ bbox.bottom = m_Display.height;
+
+ }
+}
+
+HRESULT CDVBSub::GetTextureSize (POSITION pos, SIZE& MaxTextureSize, SIZE& VideoSize, POINT& VideoTopLeft)
+{
+ // TODO : limit size for HDTV
+
+ // Texture size should be video size width. Height is limited (to prevent performances issues with
+ // more than 1024x768 pixels)
+ MaxTextureSize.cx = min (m_Display.width, 1920);
+ MaxTextureSize.cy = min (m_Display.height, 1024*768/MaxTextureSize.cx);
+
+ VideoSize.cx = m_Display.width;
+ VideoSize.cy = m_Display.height;
+
+ VideoTopLeft.x = 0;
+ VideoTopLeft.y = 0;
+
+ return S_OK;
+}
+
+POSITION CDVBSub::GetStartPosition(REFERENCE_TIME rt, double fps)
+{
+ DVB_PAGE* pPage;
+
+ // Cleanup old PG
+ while (m_Pages.GetCount()>0)
+ {
+ pPage = m_Pages.GetHead();
+ if (pPage->rtStop < rt)
+ {
+ if (!pPage->Rendered)
+ TRACE_DVB ("DVB - remove unrendered object, %S - %S\n", ReftimeToString(pPage->rtStart), ReftimeToString(pPage->rtStop));
+
+ //TRACE_HDMVSUB ("CHdmvSub:HDMV remove object %d %S => %S (rt=%S)\n", pPage->GetRLEDataSize(),
+ // ReftimeToString (pPage->rtStart), ReftimeToString(pPage->rtStop), ReftimeToString(rt));
+ m_Pages.RemoveHead();
+ delete pPage;
+ }
+ else
+ break;
+ }
+
+ return m_Pages.GetHeadPosition();
+}
+
+POSITION CDVBSub::GetNext(POSITION pos)
+{
+ m_Pages.GetNext(pos);
+ return pos;
+}
+
+
+REFERENCE_TIME CDVBSub::GetStart(POSITION nPos)
+{
+ DVB_PAGE* pPage = m_Pages.GetAt(nPos);
+ return pPage!=NULL ? pPage->rtStart : INVALID_TIME;
+}
+
+REFERENCE_TIME CDVBSub::GetStop(POSITION nPos)
+{
+ DVB_PAGE* pPage = m_Pages.GetAt(nPos);
+ return pPage!=NULL ? pPage->rtStop : INVALID_TIME;
+}
+
+
+void CDVBSub::Reset()
+{
+ m_nBufferReadPos = 0;
+ m_nBufferWritePos = 0;
+ m_pCurrentPage.Free();
+
+ DVB_PAGE* pPage;
+ while (m_Pages.GetCount() > 0)
+ {
+ pPage = m_Pages.RemoveHead();
+ delete pPage;
+ }
+
+}
+
+HRESULT CDVBSub::ParsePage(CGolombBuffer& gb, WORD wSegLength, CAutoPtr<DVB_PAGE>& pPage)
+{
+ WORD wEnd = (WORD)gb.GetPos() + wSegLength;
+ int nPos = 0;
+
+ pPage.Attach (DNew DVB_PAGE());
+ pPage->PageTimeOut = gb.ReadByte();
+ pPage->PageVersionNumber = (BYTE)gb.BitRead(4);
+ pPage->PageState = (BYTE)gb.BitRead(2);
+ pPage->RegionCount = 0;
+ gb.BitRead(2); // Reserved
+ while (gb.GetPos() < wEnd)
+ {
+ if (nPos < MAX_REGIONS)
+ {
+ pPage->Regions[nPos].Id = gb.ReadByte();
+ gb.ReadByte(); // Reserved
+ pPage->Regions[nPos].HorizAddr = gb.ReadShort();
+ pPage->Regions[nPos].VertAddr = gb.ReadShort();
+ pPage->RegionCount++;
+ }
+ nPos++;
+ }
+
+ return S_OK;
+}
+
+HRESULT CDVBSub::ParseDisplay(CGolombBuffer& gb, WORD wSegLength)
+{
+ m_Display.version_number = (BYTE)gb.BitRead (4);
+ m_Display.display_window_flag = (BYTE)gb.BitRead (1);
+ gb.BitRead(3); // reserved
+ m_Display.width = gb.ReadShort();
+ m_Display.height = gb.ReadShort();
+ if (m_Display.display_window_flag)
+ {
+ m_Display.horizontal_position_minimun = gb.ReadShort();
+ m_Display.horizontal_position_maximum = gb.ReadShort();
+ m_Display.vertical_position_minimun = gb.ReadShort();
+ m_Display.vertical_position_maximum = gb.ReadShort();
+ }
+
+ return S_OK;
+}
+
+HRESULT CDVBSub::ParseRegion(CGolombBuffer& gb, WORD wSegLength)
+{
+ WORD wEnd = (WORD)gb.GetPos() + wSegLength;
+ CDVBSub::DVB_REGION* pRegion;
+ CDVBSub::DVB_REGION DummyRegion;
+
+ pRegion = FindRegion (m_pCurrentPage, gb.ReadByte());
+
+ if (pRegion == NULL)
+ pRegion = &DummyRegion;
+
+ if (pRegion != NULL)
+ {
+ pRegion->version_number = (BYTE)gb.BitRead(4);
+ pRegion->fill_flag = (BYTE)gb.BitRead(1);
+ gb.BitRead(3); // Reserved
+ pRegion->width = gb.ReadShort();
+ pRegion->height = gb.ReadShort();
+ pRegion->level_of_compatibility = (BYTE)gb.BitRead(3);
+ pRegion->depth = (BYTE)gb.BitRead(3);
+ gb.BitRead(2); // Reserved
+ pRegion->CLUT_id = gb.ReadByte();
+ pRegion->_8_bit_pixel_code = gb.ReadByte();
+ pRegion->_4_bit_pixel_code = (BYTE)gb.BitRead(4);
+ pRegion->_2_bit_pixel_code = (BYTE)gb.BitRead(2);
+ gb.BitRead(2); // Reserved
+
+ pRegion->ObjectCount = 0;
+ while (gb.GetPos() < wEnd)
+ {
+ DVB_OBJECT* pObject = &pRegion->Objects[pRegion->ObjectCount];
+ pObject->object_id = gb.ReadShort();
+ pObject->object_type = (BYTE)gb.BitRead(2);
+ pObject->object_provider_flag = (BYTE)gb.BitRead(2);
+ pObject->object_horizontal_position = (SHORT)gb.BitRead(12);
+ gb.BitRead(4); // Reserved
+ pObject->object_vertical_position = (SHORT)gb.BitRead(12);
+ if (pObject->object_type == 0x01 || pObject->object_type == 0x02)
+ {
+ pObject->foreground_pixel_code = gb.ReadByte();
+ pObject->background_pixel_code = gb.ReadByte();
+ }
+ pRegion->ObjectCount++;
+ }
+ }
+ else
+ gb.SkipBytes (wSegLength-1);
+
+ return S_OK;
+}
+
+HRESULT CDVBSub::ParseClut(CGolombBuffer& gb, WORD wSegLength)
+{
+ HRESULT hr = S_OK;
+ WORD wEnd = (WORD)gb.GetPos() + wSegLength;
+ CDVBSub::DVB_CLUT* pClut;
+
+ pClut = FindClut (m_pCurrentPage, gb.ReadByte());
+// ASSERT (pClut != NULL);
+ if (pClut != NULL)
+ {
+ pClut->version_number = (BYTE)gb.BitRead(4);
+ gb.BitRead(4); // Reserved
+
+ pClut->Size = 0;
+ while (gb.GetPos() < wEnd)
+ {
+ BYTE entry_id = gb.ReadByte()+1;
+ BYTE _2_bit = (BYTE)gb.BitRead(1);
+ BYTE _4_bit = (BYTE)gb.BitRead(1);
+ BYTE _8_bit = (BYTE)gb.BitRead(1);
+ UNUSED_ALWAYS(_2_bit);
+ UNUSED_ALWAYS(_4_bit);
+ UNUSED_ALWAYS(_8_bit);
+ gb.BitRead(4); // Reserved
+
+ pClut->Palette[entry_id].entry_id = entry_id;
+ if (gb.BitRead(1))
+ {
+ pClut->Palette[entry_id].Y = gb.ReadByte();
+ pClut->Palette[entry_id].Cr = gb.ReadByte();
+ pClut->Palette[entry_id].Cb = gb.ReadByte();
+ pClut->Palette[entry_id].T = 255-gb.ReadByte();
+ }
+ else
+ {
+ pClut->Palette[entry_id].Y = (BYTE)gb.BitRead(6)<<2;
+ pClut->Palette[entry_id].Cr = (BYTE)gb.BitRead(4)<<4;
+ pClut->Palette[entry_id].Cb = (BYTE)gb.BitRead(4)<<4;
+ pClut->Palette[entry_id].T = 255-((BYTE)gb.BitRead(2)<<6);
+ }
+ pClut->Size = max (pClut->Size, entry_id);
+ }
+ }
+
+ return hr;
+}
+
+HRESULT CDVBSub::ParseObject(CGolombBuffer& gb, WORD wSegLength)
+{
+ HRESULT hr = E_FAIL;
+
+ if (m_pCurrentPage && wSegLength > 2)
+ {
+ CompositionObject* pObject = DNew CompositionObject();
+ BYTE object_coding_method;
+
+ pObject->m_object_id_ref = gb.ReadShort();
+ pObject->m_version_number = (BYTE)gb.BitRead(4);
+
+ object_coding_method = (BYTE)gb.BitRead(2); // object_coding_method
+ gb.BitRead(1); // non_modifying_colour_flag
+ gb.BitRead(1); // reserved
+
+ if (object_coding_method == 0x00)
+ {
+ pObject->SetRLEData (gb.GetBufferPos(), wSegLength-3, wSegLength-3);
+ gb.SkipBytes(wSegLength-3);
+ m_pCurrentPage->Objects.AddTail (pObject);
+ hr = S_OK;
+ }
+ else
+ {
+ delete pObject;
+ hr = E_NOTIMPL;
+ }
+ }
+
+
+ return hr;
+}
+
diff --git a/src/Subtitles/DVBSub.h b/src/Subtitles/DVBSub.h
new file mode 100644
index 000000000..f0eb65362
--- /dev/null
+++ b/src/Subtitles/DVBSub.h
@@ -0,0 +1,228 @@
+/*
+ * $Id$
+ *
+ * (C) 2006-2010 see AUTHORS
+ *
+ * This file is part of mplayerc.
+ *
+ * Mplayerc 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.
+ *
+ * Mplayerc 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#pragma once
+
+#include "BaseSub.h"
+
+#define MAX_REGIONS 10
+#define MAX_OBJECTS 10 // Max number of objects per region
+
+class CGolombBuffer;
+
+class CDVBSub : public CBaseSub
+{
+public:
+ CDVBSub(void);
+ ~CDVBSub(void);
+
+ virtual HRESULT ParseSample (IMediaSample* pSample);
+ virtual void Render(SubPicDesc& spd, REFERENCE_TIME rt, RECT& bbox);
+ virtual HRESULT GetTextureSize (POSITION pos, SIZE& MaxTextureSize, SIZE& VideoSize, POINT& VideoTopLeft);
+ virtual POSITION GetStartPosition(REFERENCE_TIME rt, double fps);
+ virtual POSITION GetNext(POSITION pos);
+ virtual REFERENCE_TIME GetStart(POSITION nPos);
+ virtual REFERENCE_TIME GetStop(POSITION nPos);
+ virtual void Reset();
+
+ // EN 300-743, table 2
+ enum DVB_SEGMENT_TYPE
+ {
+ NO_SEGMENT = 0xFFFF,
+ PAGE = 0x10,
+ REGION = 0x11,
+ CLUT = 0x12,
+ OBJECT = 0x13,
+ DISPLAY = 0x14,
+ END_OF_DISPLAY = 0x80
+ };
+
+ // EN 300-743, table 6
+ enum DVB_OBJECT_TYPE
+ {
+ OT_BASIC_BITMAP = 0x00,
+ OT_BASIC_CHAR = 0x01,
+ OT_COMPOSITE_STRING = 0x02
+ };
+
+ enum DVB_PAGE_STATE
+ {
+ DPS_NORMAL = 0x00,
+ DPS_ACQUISITION = 0x01,
+ DPS_MODE = 0x02,
+ DPS_RESERVED = 0x03
+ };
+
+ struct DVB_CLUT
+ {
+ BYTE id;
+ BYTE version_number;
+ BYTE Size;
+
+ HDMV_PALETTE Palette[256];
+
+ DVB_CLUT()
+ {
+ memset (Palette, 0, sizeof(Palette));
+ }
+ };
+
+ struct DVB_DISPLAY
+ {
+ BYTE version_number;
+ BYTE display_window_flag;
+ SHORT width;
+ SHORT height;
+ SHORT horizontal_position_minimun;
+ SHORT horizontal_position_maximum;
+ SHORT vertical_position_minimun;
+ SHORT vertical_position_maximum;
+
+ DVB_DISPLAY()
+ {
+ // Default value (§5.1.3)
+ version_number = 0;
+ width = 720;
+ height = 576;
+ }
+ };
+
+ struct DVB_OBJECT
+ {
+ SHORT object_id;
+ BYTE object_type;
+ BYTE object_provider_flag;
+ SHORT object_horizontal_position;
+ SHORT object_vertical_position;
+ BYTE foreground_pixel_code;
+ BYTE background_pixel_code;
+
+ DVB_OBJECT()
+ {
+ object_id = 0xFF;
+ object_type = 0;
+ object_provider_flag = 0;
+ object_horizontal_position = 0;
+ object_vertical_position = 0;
+ foreground_pixel_code = 0;
+ background_pixel_code = 0;
+ }
+ };
+
+ struct DVB_REGION
+ {
+ BYTE Id;
+ WORD HorizAddr;
+ WORD VertAddr;
+ BYTE version_number;
+ BYTE fill_flag;
+ WORD width;
+ WORD height;
+ BYTE level_of_compatibility;
+ BYTE depth;
+ BYTE CLUT_id;
+ BYTE _8_bit_pixel_code;
+ BYTE _4_bit_pixel_code;
+ BYTE _2_bit_pixel_code;
+ int ObjectCount;
+ DVB_OBJECT Objects[MAX_OBJECTS];
+
+ DVB_CLUT Clut;
+
+ DVB_REGION()
+ {
+ Id = 0;
+ HorizAddr = 0;
+ VertAddr = 0;
+ version_number = 0;
+ fill_flag = 0;
+ width = 0;
+ height = 0;
+ level_of_compatibility = 0;
+ depth = 0;
+ CLUT_id = 0;
+ _8_bit_pixel_code = 0;
+ _4_bit_pixel_code = 0;
+ _2_bit_pixel_code = 0;
+ }
+ };
+
+ class DVB_PAGE
+ {
+ public :
+ REFERENCE_TIME rtStart;
+ REFERENCE_TIME rtStop;
+ BYTE PageTimeOut;
+ BYTE PageVersionNumber;
+ BYTE PageState;
+ int RegionCount;
+ DVB_REGION Regions[MAX_REGIONS];
+ CAtlList<CompositionObject*> Objects;
+ bool Rendered;
+
+ DVB_PAGE()
+ {
+ PageTimeOut = 0;
+ PageVersionNumber = 0;
+ PageState = 0;
+ RegionCount = 0;
+ Rendered = false;
+ }
+
+ ~DVB_PAGE()
+ {
+ CompositionObject* pPage;
+ while (Objects.GetCount() > 0)
+ {
+ pPage = Objects.RemoveHead();
+ delete pPage;
+ }
+ }
+ };
+
+private:
+ static const REFERENCE_TIME INVALID_TIME = _I64_MIN;
+
+ int m_nBufferSize;
+ int m_nBufferReadPos;
+ int m_nBufferWritePos;
+ BYTE* m_pBuffer;
+ CAtlList<DVB_PAGE*> m_Pages;
+ CAutoPtr<DVB_PAGE> m_pCurrentPage;
+ DVB_DISPLAY m_Display;
+ REFERENCE_TIME m_rtStart;
+ REFERENCE_TIME m_rtStop;
+
+ HRESULT AddToBuffer(BYTE* pData, int nSize);
+ DVB_PAGE* FindPage(REFERENCE_TIME rt);
+ DVB_REGION* FindRegion(DVB_PAGE* pPage, BYTE bRegionId);
+ DVB_CLUT* FindClut(DVB_PAGE* pPage, BYTE bClutId);
+ CompositionObject* FindObject(DVB_PAGE* pPage, SHORT sObjectId);
+
+ HRESULT ParsePage(CGolombBuffer& gb, WORD wSegLength, CAutoPtr<DVB_PAGE>& pPage);
+ HRESULT ParseDisplay(CGolombBuffer& gb, WORD wSegLength);
+ HRESULT ParseRegion(CGolombBuffer& gb, WORD wSegLength);
+ HRESULT ParseClut(CGolombBuffer& gb, WORD wSegLength);
+ HRESULT ParseObject(CGolombBuffer& gb, WORD wSegLength);
+
+};
diff --git a/src/Subtitles/GFN.cpp b/src/Subtitles/GFN.cpp
new file mode 100644
index 000000000..ed701680f
--- /dev/null
+++ b/src/Subtitles/GFN.cpp
@@ -0,0 +1,178 @@
+/*
+ * 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 <io.h>
+#include "TextFile.h"
+#include "GFN.h"
+
+TCHAR* exttypestr[] =
+{
+ _T("srt"), _T("sub"), _T("smi"), _T("psb"),
+ _T("ssa"), _T("ass"), _T("idx"), _T("usf"),
+ _T("xss"), _T("txt"), _T("ssf"), _T("rt"), _T("sup")
+};
+
+static TCHAR* ext[3][countof(exttypestr)] =
+{
+ {
+ _T(".srt"), _T(".sub"), _T(".smi"), _T(".psb"),
+ _T(".ssa"), _T(".ass"), _T(".idx"), _T(".usf"),
+ _T(".xss"), _T(".txt"), _T(".ssf"), _T(".rt"), _T(".sup")
+ },
+ {
+ _T(".*.srt"), _T(".*.sub"), _T(".*.smi"), _T(".*.psb"),
+ _T(".*.ssa"), _T(".*.ass"), _T(".*.dummyidx"), _T(".*.usf"),
+ _T(".*.xss"), _T(".*.txt"), _T(".*.ssf"), _T(".*.rt"), _T(".*.sup")
+ },
+ {
+ _T("-*.srt"), _T("-*.sub"), _T("-*.smi"), _T("-*.psb"),
+ _T("-*.ssa"), _T("-*.ass"), _T("-*.dummyidx"), _T("-*.usf"),
+ _T("-*.xss"), _T("-*.txt"), _T("-*.ssf"), _T("-*.rt"), _T("-*.sup")
+ },
+};
+
+#define WEBSUBEXT _T(".wse")
+
+static int SubFileCompare(const void* elem1, const void* elem2)
+{
+ return(((SubFile*)elem1)->fn.CompareNoCase(((SubFile*)elem2)->fn));
+}
+
+void GetSubFileNames(CString fn, CAtlArray<CString>& paths, CAtlArray<SubFile>& ret)
+{
+ ret.RemoveAll();
+
+ int extlistnum = countof(ext);
+ int extsubnum = countof(ext[0]);
+
+ fn.Replace('\\', '/');
+
+ bool fWeb = false;
+ {
+// int i = fn.Find(_T("://"));
+ int i = fn.Find(_T("http://"));
+ if(i > 0) {fn = _T("http") + fn.Mid(i); fWeb = true;}
+ }
+
+ int l = fn.GetLength(), l2 = l;
+ l2 = fn.ReverseFind('.');
+ l = fn.ReverseFind('/') + 1;
+ if(l2 < l) l2 = l;
+
+ CString orgpath = fn.Left(l);
+ CString title = fn.Mid(l, l2-l);
+ CString filename = title + _T(".nooneexpectsthespanishinquisition");
+
+ if(!fWeb)
+ {
+ // struct _tfinddata_t file, file2;
+ // long hFile, hFile2 = 0;
+
+ WIN32_FIND_DATA wfd, wfd2;
+ HANDLE hFile, hFile2;
+
+ for(ptrdiff_t k = 0; k < paths.GetCount(); k++)
+ {
+ CString path = paths[k];
+ path.Replace('\\', '/');
+
+ l = path.GetLength();
+ if(l > 0 && path[l-1] != '/') path += '/';
+
+ if(path.Find(':') == -1 && path.Find(_T("\\\\")) != 0) path = orgpath + path;
+
+ path.Replace(_T("/./"), _T("/"));
+ path.Replace('/', '\\');
+
+ // CAtlList<CString> sl;
+
+ bool fEmpty = true;
+
+ if((hFile = FindFirstFile(path + title + _T("*"), &wfd)) != INVALID_HANDLE_VALUE)
+ {
+ do
+ {
+ if(filename.CompareNoCase(wfd.cFileName) != 0)
+ {
+ fEmpty = false;
+ // sl.AddTail(path + file.name);
+ }
+ }
+ while(FindNextFile(hFile, &wfd));
+
+ FindClose(hFile);
+ }
+
+ // TODO: use 'sl' in the next step to find files (already a nice speedup as it is now...)
+ if(fEmpty) continue;
+
+ for(ptrdiff_t j = 0; j < extlistnum; j++)
+ {
+ for(ptrdiff_t i = 0; i < extsubnum; i++)
+ {
+ if((hFile = FindFirstFile(path + title + ext[j][i], &wfd)) != INVALID_HANDLE_VALUE)
+ {
+ do
+ {
+ CString fn = path + wfd.cFileName;
+
+ hFile2 = INVALID_HANDLE_VALUE;
+
+ if(j == 0 || (hFile2 = FindFirstFile(fn.Left(fn.ReverseFind('.')) + _T(".avi"), &wfd2)) == INVALID_HANDLE_VALUE)
+ {
+ SubFile f;
+ f.fn = fn;
+ ret.Add(f);
+ }
+
+ if(hFile2 != INVALID_HANDLE_VALUE)
+ {
+ FindClose(hFile2);
+ }
+ }
+ while(FindNextFile(hFile, &wfd));
+
+ FindClose(hFile);
+ }
+ }
+ }
+ }
+ }
+ else if(l > 7)
+ {
+ CWebTextFile wtf; // :)
+ if(wtf.Open(orgpath + title + WEBSUBEXT))
+ {
+ CString fn;
+ while(wtf.ReadString(fn) && fn.Find(_T("://")) >= 0)
+ {
+ SubFile f;
+ f.fn = fn;
+ ret.Add(f);
+ }
+ }
+ }
+
+ // sort files, this way the user can define the order (movie.00.English.srt, movie.01.Hungarian.srt, etc)
+
+ qsort(ret.GetData(), ret.GetCount(), sizeof(SubFile), SubFileCompare);
+}
diff --git a/src/Subtitles/GFN.h b/src/Subtitles/GFN.h
new file mode 100644
index 000000000..6790124b2
--- /dev/null
+++ b/src/Subtitles/GFN.h
@@ -0,0 +1,29 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <atlcoll.h>
+
+enum exttype {EXTSRT = 0, EXTSUB, EXTSMI, EXTPSB, EXTSSA, EXTASS, EXTIDX, EXTUSF, EXTXSS, EXTRT};
+extern TCHAR* exttypestr[];
+typedef struct {CString fn; /*exttype ext;*/} SubFile;
+extern void GetSubFileNames(CString fn, CAtlArray<CString>& paths, CAtlArray<SubFile>& ret);
diff --git a/src/Subtitles/HdmvSub.cpp b/src/Subtitles/HdmvSub.cpp
new file mode 100644
index 000000000..62600f903
--- /dev/null
+++ b/src/Subtitles/HdmvSub.cpp
@@ -0,0 +1,381 @@
+/*
+ * $Id$
+ *
+ * (C) 2006-2010 see AUTHORS
+ *
+ * This file is part of mplayerc.
+ *
+ * Mplayerc 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.
+ *
+ * Mplayerc 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "stdafx.h"
+#include "HdmvSub.h"
+#include "../DSUtil/GolombBuffer.h"
+
+#if (0) // Set to 1 to activate HDMV subtitles traces
+ #define TRACE_HDMVSUB TRACE
+#else
+ #define TRACE_HDMVSUB
+#endif
+
+
+CHdmvSub::CHdmvSub(void)
+ : CBaseSub(ST_HDMV)
+{
+ m_nColorNumber = 0;
+
+ m_nCurSegment = NO_SEGMENT;
+ m_pSegBuffer = NULL;
+ m_nTotalSegBuffer = 0;
+ m_nSegBufferPos = 0;
+ m_nSegSize = 0;
+ m_pCurrentObject = NULL;
+ m_pDefaultPalette = NULL;
+ m_nDefaultPaletteNbEntry = 0;
+
+ memset (&m_VideoDescriptor, 0, sizeof(VIDEO_DESCRIPTOR));
+}
+
+CHdmvSub::~CHdmvSub()
+{
+ Reset();
+
+ delete[] m_pSegBuffer;
+ delete[] m_pDefaultPalette;
+ delete m_pCurrentObject;
+}
+
+
+void CHdmvSub::AllocSegment(int nSize)
+{
+ if (nSize > m_nTotalSegBuffer)
+ {
+ delete[] m_pSegBuffer;
+ m_pSegBuffer = DNew BYTE[nSize];
+ m_nTotalSegBuffer = nSize;
+ }
+ m_nSegBufferPos = 0;
+ m_nSegSize = nSize;
+}
+
+POSITION CHdmvSub::GetStartPosition(REFERENCE_TIME rt, double fps)
+{
+ CompositionObject* pObject;
+
+ // Cleanup old PG
+ while (m_pObjects.GetCount()>0)
+ {
+ pObject = m_pObjects.GetHead();
+ if (pObject->m_rtStop < rt)
+ {
+ TRACE_HDMVSUB ("CHdmvSub:HDMV remove object %d %S => %S (rt=%S)\n", pObject->GetRLEDataSize(),
+ ReftimeToString (pObject->m_rtStart), ReftimeToString(pObject->m_rtStop), ReftimeToString(rt));
+ m_pObjects.RemoveHead();
+ delete pObject;
+ }
+ else
+ break;
+ }
+
+ return m_pObjects.GetHeadPosition();
+}
+
+HRESULT CHdmvSub::ParseSample(IMediaSample* pSample)
+{
+ CheckPointer (pSample, E_POINTER);
+ HRESULT hr;
+ REFERENCE_TIME rtStart = INVALID_TIME, rtStop = INVALID_TIME;
+ BYTE* pData = NULL;
+ int lSampleLen;
+
+ hr = pSample->GetPointer(&pData);
+ if(FAILED(hr) || pData == NULL) return hr;
+ lSampleLen = pSample->GetActualDataLength();
+
+ pSample->GetTime(&rtStart, &rtStop);
+ if (pData)
+ {
+ CGolombBuffer SampleBuffer (pData, lSampleLen);
+
+ while (!SampleBuffer.IsEOF())
+ {
+ if (m_nCurSegment == NO_SEGMENT)
+ {
+ HDMV_SEGMENT_TYPE nSegType = (HDMV_SEGMENT_TYPE)SampleBuffer.ReadByte();
+ USHORT nUnitSize = SampleBuffer.ReadShort();
+ lSampleLen -=3;
+
+ switch (nSegType)
+ {
+ case PALETTE :
+ case OBJECT :
+ case PRESENTATION_SEG :
+ case END_OF_DISPLAY :
+ m_nCurSegment = nSegType;
+ AllocSegment (nUnitSize);
+ break;
+
+ case WINDOW_DEF :
+ case INTERACTIVE_SEG :
+ case HDMV_SUB1 :
+ case HDMV_SUB2 :
+ // Ignored stuff...
+ SampleBuffer.SkipBytes(nUnitSize);
+ break;
+ default :
+ return VFW_E_SAMPLE_REJECTED;
+ }
+ }
+
+ if (m_nCurSegment != NO_SEGMENT)
+ {
+ if (m_nSegBufferPos < m_nSegSize)
+ {
+ int nSize = min (m_nSegSize-m_nSegBufferPos, lSampleLen);
+ SampleBuffer.ReadBuffer (m_pSegBuffer+m_nSegBufferPos, nSize);
+ m_nSegBufferPos += nSize;
+ }
+
+ if (m_nSegBufferPos >= m_nSegSize)
+ {
+ CGolombBuffer SegmentBuffer (m_pSegBuffer, m_nSegSize);
+
+ switch (m_nCurSegment)
+ {
+ case PALETTE :
+ TRACE_HDMVSUB ("CHdmvSub:PALETTE rtStart=%10I64d\n", rtStart);
+ ParsePalette(&SegmentBuffer, m_nSegSize);
+ break;
+ case OBJECT :
+ //TRACE_HDMVSUB ("CHdmvSub:OBJECT %S\n", ReftimeToString(rtStart));
+ ParseObject(&SegmentBuffer, m_nSegSize);
+ break;
+ case PRESENTATION_SEG :
+ TRACE_HDMVSUB ("CHdmvSub:PRESENTATION_SEG %S (size=%d)\n", ReftimeToString(rtStart), m_nSegSize);
+
+ if (m_pCurrentObject)
+ {
+ m_pCurrentObject->m_rtStop = rtStart;
+ m_pObjects.AddTail (m_pCurrentObject);
+ TRACE_HDMVSUB ("CHdmvSub:HDMV : %S => %S\n", ReftimeToString (m_pCurrentObject->m_rtStart), ReftimeToString(rtStart));
+ m_pCurrentObject = NULL;
+ }
+
+ if (ParsePresentationSegment(&SegmentBuffer) > 0)
+ {
+ m_pCurrentObject->m_rtStart = rtStart;
+ m_pCurrentObject->m_rtStop = _I64_MAX;
+ }
+ break;
+ case WINDOW_DEF :
+ // TRACE_HDMVSUB ("CHdmvSub:WINDOW_DEF %S\n", ReftimeToString(rtStart));
+ break;
+ case END_OF_DISPLAY :
+ // TRACE_HDMVSUB ("CHdmvSub:END_OF_DISPLAY %S\n", ReftimeToString(rtStart));
+ break;
+ default :
+ TRACE_HDMVSUB ("CHdmvSub:UNKNOWN Seg %d rtStart=0x%10dd\n", m_nCurSegment, rtStart);
+ }
+
+ m_nCurSegment = NO_SEGMENT;
+ }
+ }
+ }
+ }
+
+ return hr;
+}
+
+int CHdmvSub::ParsePresentationSegment(CGolombBuffer* pGBuffer)
+{
+ COMPOSITION_DESCRIPTOR CompositionDescriptor;
+ BYTE nObjectNumber;
+ bool palette_update_flag;
+ BYTE palette_id_ref;
+
+ ParseVideoDescriptor(pGBuffer, &m_VideoDescriptor);
+ ParseCompositionDescriptor(pGBuffer, &CompositionDescriptor);
+ palette_update_flag = !!(pGBuffer->ReadByte() & 0x80);
+ palette_id_ref = pGBuffer->ReadByte();
+ nObjectNumber = pGBuffer->ReadByte();
+
+ if (nObjectNumber > 0)
+ {
+ delete m_pCurrentObject;
+ m_pCurrentObject = DNew CompositionObject();
+ ParseCompositionObject (pGBuffer, m_pCurrentObject);
+ }
+
+ return nObjectNumber;
+}
+
+void CHdmvSub::ParsePalette(CGolombBuffer* pGBuffer, USHORT nSize) // #497
+{
+ int nNbEntry;
+ BYTE palette_id = pGBuffer->ReadByte();
+ BYTE palette_version_number = pGBuffer->ReadByte();
+ UNUSED_ALWAYS(palette_id);
+ UNUSED_ALWAYS(palette_version_number);
+
+ ASSERT ((nSize-2) % sizeof(HDMV_PALETTE) == 0);
+ nNbEntry = (nSize-2) / sizeof(HDMV_PALETTE);
+ HDMV_PALETTE* pPalette = (HDMV_PALETTE*)pGBuffer->GetBufferPos();
+
+ if (m_pDefaultPalette == NULL || m_nDefaultPaletteNbEntry != nNbEntry)
+ {
+ delete[] m_pDefaultPalette;
+ m_pDefaultPalette = new HDMV_PALETTE[nNbEntry];
+ m_nDefaultPaletteNbEntry = nNbEntry;
+ }
+ memcpy (m_pDefaultPalette, pPalette, nNbEntry*sizeof(HDMV_PALETTE));
+
+ if (m_pCurrentObject)
+ m_pCurrentObject->SetPalette (nNbEntry, pPalette, m_VideoDescriptor.nVideoWidth>720);
+}
+
+void CHdmvSub::ParseObject(CGolombBuffer* pGBuffer, USHORT nUnitSize) // #498
+{
+ SHORT object_id = pGBuffer->ReadShort();
+ UNUSED_ALWAYS(object_id);
+ BYTE m_sequence_desc;
+
+ ASSERT (m_pCurrentObject != NULL);
+ if (m_pCurrentObject)// && m_pCurrentObject->m_object_id_ref == object_id)
+ {
+ m_pCurrentObject->m_version_number = pGBuffer->ReadByte();
+ m_sequence_desc = pGBuffer->ReadByte();
+
+ if (m_sequence_desc & 0x80)
+ {
+ DWORD object_data_length = (DWORD)pGBuffer->BitRead(24);
+
+ m_pCurrentObject->m_width = pGBuffer->ReadShort();
+ m_pCurrentObject->m_height = pGBuffer->ReadShort();
+
+ m_pCurrentObject->SetRLEData (pGBuffer->GetBufferPos(), nUnitSize-11, object_data_length-4);
+
+ TRACE_HDMVSUB ("CHdmvSub:NewObject size=%ld, total obj=%d, %dx%d\n", object_data_length, m_pObjects.GetCount(),
+ m_pCurrentObject->m_width, m_pCurrentObject->m_height);
+ }
+ else
+ m_pCurrentObject->AppendRLEData (pGBuffer->GetBufferPos(), nUnitSize-4);
+ }
+}
+
+void CHdmvSub::ParseCompositionObject(CGolombBuffer* pGBuffer, CompositionObject* pCompositionObject)
+{
+ BYTE bTemp;
+ pCompositionObject->m_object_id_ref = pGBuffer->ReadShort();
+ pCompositionObject->m_window_id_ref = pGBuffer->ReadByte();
+ bTemp = pGBuffer->ReadByte();
+ pCompositionObject->m_object_cropped_flag = !!(bTemp & 0x80);
+ pCompositionObject->m_forced_on_flag = !!(bTemp & 0x40);
+ pCompositionObject->m_horizontal_position = pGBuffer->ReadShort();
+ pCompositionObject->m_vertical_position = pGBuffer->ReadShort();
+
+ if (pCompositionObject->m_object_cropped_flag)
+ {
+ pCompositionObject->m_cropping_horizontal_position = pGBuffer->ReadShort();
+ pCompositionObject->m_cropping_vertical_position = pGBuffer->ReadShort();
+ pCompositionObject->m_cropping_width = pGBuffer->ReadShort();
+ pCompositionObject->m_cropping_height = pGBuffer->ReadShort();
+ }
+}
+
+void CHdmvSub::ParseVideoDescriptor(CGolombBuffer* pGBuffer, VIDEO_DESCRIPTOR* pVideoDescriptor)
+{
+ pVideoDescriptor->nVideoWidth = pGBuffer->ReadShort();
+ pVideoDescriptor->nVideoHeight = pGBuffer->ReadShort();
+ pVideoDescriptor->bFrameRate = pGBuffer->ReadByte();
+}
+
+void CHdmvSub::ParseCompositionDescriptor(CGolombBuffer* pGBuffer, COMPOSITION_DESCRIPTOR* pCompositionDescriptor)
+{
+ pCompositionDescriptor->nNumber = pGBuffer->ReadShort();
+ pCompositionDescriptor->bState = pGBuffer->ReadByte();
+}
+
+void CHdmvSub::Render(SubPicDesc& spd, REFERENCE_TIME rt, RECT& bbox)
+{
+ CompositionObject* pObject = FindObject (rt);
+
+ ASSERT (pObject!=NULL && spd.w >= pObject->m_width && spd.h >= pObject->m_height);
+
+ if (pObject && spd.w >= pObject->m_width && spd.h >= pObject->m_height)
+ {
+ if (!pObject->HavePalette())
+ pObject->SetPalette (m_nDefaultPaletteNbEntry, m_pDefaultPalette, m_VideoDescriptor.nVideoWidth>720);
+
+ TRACE_HDMVSUB ("CHdmvSub:Render size=%ld, ObjRes=%dx%d, SPDRes=%dx%d\n", pObject->GetRLEDataSize(),
+ pObject->m_width, pObject->m_height, spd.w, spd.h);
+ pObject->RenderHdmv(spd);
+
+ bbox.left = 0;
+ bbox.top = 0;
+ bbox.right = bbox.left + pObject->m_width;
+ bbox.bottom = bbox.top + pObject->m_height;
+ }
+}
+
+HRESULT CHdmvSub::GetTextureSize (POSITION pos, SIZE& MaxTextureSize, SIZE& VideoSize, POINT& VideoTopLeft)
+{
+ CompositionObject* pObject = m_pObjects.GetAt (pos);
+ if (pObject)
+ {
+ MaxTextureSize.cx = m_VideoDescriptor.nVideoWidth;
+ MaxTextureSize.cy = m_VideoDescriptor.nVideoHeight;
+
+ VideoSize.cx = m_VideoDescriptor.nVideoWidth;
+ VideoSize.cy = m_VideoDescriptor.nVideoHeight;
+
+ VideoTopLeft.x = pObject->m_horizontal_position;
+ VideoTopLeft.y = pObject->m_vertical_position;
+
+ return S_OK;
+ }
+
+ ASSERT (FALSE);
+ return E_INVALIDARG;
+}
+
+
+void CHdmvSub::Reset()
+{
+ CompositionObject* pObject;
+ while (m_pObjects.GetCount() > 0)
+ {
+ pObject = m_pObjects.RemoveHead();
+ delete pObject;
+ }
+}
+
+CompositionObject* CHdmvSub::FindObject(REFERENCE_TIME rt)
+{
+ POSITION pos = m_pObjects.GetHeadPosition();
+
+ while (pos)
+ {
+ CompositionObject* pObject = m_pObjects.GetAt (pos);
+
+ if (rt >= pObject->m_rtStart && rt < pObject->m_rtStop)
+ return pObject;
+
+ m_pObjects.GetNext(pos);
+ }
+
+ return NULL;
+}
+
+
diff --git a/src/Subtitles/HdmvSub.h b/src/Subtitles/HdmvSub.h
new file mode 100644
index 000000000..950d10584
--- /dev/null
+++ b/src/Subtitles/HdmvSub.h
@@ -0,0 +1,124 @@
+/*
+ * $Id$
+ *
+ * (C) 2006-2010 see AUTHORS
+ *
+ * This file is part of mplayerc.
+ *
+ * Mplayerc 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.
+ *
+ * Mplayerc 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include "BaseSub.h"
+
+class CGolombBuffer;
+
+class CHdmvSub : public CBaseSub
+{
+public:
+
+ static const REFERENCE_TIME INVALID_TIME = _I64_MIN;
+
+ enum HDMV_SEGMENT_TYPE
+ {
+ NO_SEGMENT = 0xFFFF,
+ PALETTE = 0x14,
+ OBJECT = 0x15,
+ PRESENTATION_SEG = 0x16,
+ WINDOW_DEF = 0x17,
+ INTERACTIVE_SEG = 0x18,
+ END_OF_DISPLAY = 0x80,
+ HDMV_SUB1 = 0x81,
+ HDMV_SUB2 = 0x82
+ };
+
+
+ struct VIDEO_DESCRIPTOR
+ {
+ SHORT nVideoWidth;
+ SHORT nVideoHeight;
+ BYTE bFrameRate; // <= Frame rate here!
+ };
+
+ struct COMPOSITION_DESCRIPTOR
+ {
+ SHORT nNumber;
+ BYTE bState;
+ };
+
+ struct SEQUENCE_DESCRIPTOR
+ {
+ BYTE bFirstIn : 1;
+ BYTE bLastIn : 1;
+ BYTE bReserved : 8;
+ };
+
+ CHdmvSub();
+ ~CHdmvSub();
+
+ HRESULT ParseSample (IMediaSample* pSample);
+
+
+ POSITION GetStartPosition(REFERENCE_TIME rt, double fps);
+ POSITION GetNext(POSITION pos) { m_pObjects.GetNext(pos); return pos; };
+
+
+ virtual REFERENCE_TIME GetStart(POSITION nPos)
+ {
+ CompositionObject* pObject = m_pObjects.GetAt(nPos);
+ return pObject!=NULL ? pObject->m_rtStart : INVALID_TIME;
+ };
+ virtual REFERENCE_TIME GetStop(POSITION nPos)
+ {
+ CompositionObject* pObject = m_pObjects.GetAt(nPos);
+ return pObject!=NULL ? pObject->m_rtStop : INVALID_TIME;
+ };
+
+ void Render(SubPicDesc& spd, REFERENCE_TIME rt, RECT& bbox);
+ HRESULT GetTextureSize (POSITION pos, SIZE& MaxTextureSize, SIZE& VideoSize, POINT& VideoTopLeft);
+ void Reset();
+
+private :
+
+ HDMV_SEGMENT_TYPE m_nCurSegment;
+ BYTE* m_pSegBuffer;
+ int m_nTotalSegBuffer;
+ int m_nSegBufferPos;
+ int m_nSegSize;
+
+ VIDEO_DESCRIPTOR m_VideoDescriptor;
+
+ CompositionObject* m_pCurrentObject;
+ CAtlList<CompositionObject*> m_pObjects;
+
+ HDMV_PALETTE* m_pDefaultPalette;
+ int m_nDefaultPaletteNbEntry;
+
+ int m_nColorNumber;
+
+
+ int ParsePresentationSegment(CGolombBuffer* pGBuffer);
+ void ParsePalette(CGolombBuffer* pGBuffer, USHORT nSize);
+ void ParseObject(CGolombBuffer* pGBuffer, USHORT nUnitSize);
+
+ void ParseVideoDescriptor(CGolombBuffer* pGBuffer, VIDEO_DESCRIPTOR* pVideoDescriptor);
+ void ParseCompositionDescriptor(CGolombBuffer* pGBuffer, COMPOSITION_DESCRIPTOR* pCompositionDescriptor);
+ void ParseCompositionObject(CGolombBuffer* pGBuffer, CompositionObject* pCompositionObject);
+
+ void AllocSegment(int nSize);
+
+ CompositionObject* FindObject(REFERENCE_TIME rt);
+};
diff --git a/src/Subtitles/RTS.cpp b/src/Subtitles/RTS.cpp
new file mode 100644
index 000000000..25540a963
--- /dev/null
+++ b/src/Subtitles/RTS.cpp
@@ -0,0 +1,3975 @@
+/*
+ * 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 <math.h>
+#include <time.h>
+#include <emmintrin.h>
+#include "RTS.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(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;
+#ifdef _VSMOD // patch m007. symbol rotating
+ lf.lfOrientation = (LONG)style.mod_fontOrient;
+#endif
+
+ 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)
+{
+#ifdef _VSMOD
+ // CPUID from VDub
+ bool fSSE2 = !!(g_cpuid.m_flags & CCpuID::sse2);
+
+ if(fSSE2) // SSE code
+ Transform_SSE2(org);
+ else // C-code
+#endif
+ Transform_C(org);
+}
+
+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 = DNew CPolygon(style, str, 0, 0, 0, 1.0/8, 1.0/8, 0);
+
+ return(!!m_pOpaqueBox);
+}
+
+void CWord::Transform_C( 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);
+
+#ifdef _VSMOD
+ // patch m003. random text points
+ double xrnd = m_style.mod_rand.X*100;
+ double yrnd = m_style.mod_rand.Y*100;
+ double zrnd = m_style.mod_rand.Z*100;
+
+ srand(m_style.mod_rand.Seed);
+
+ // patch m008. distort
+ int xsz,ysz;
+ double dst1x,dst1y,dst2x,dst2y,dst3x,dst3y;
+ int minx = INT_MAX, miny = INT_MAX, maxx = -INT_MAX, maxy = -INT_MAX;
+
+ bool is_dist = m_style.mod_distort.enabled;
+ if (is_dist)
+ {
+ for(int i = 0; i < mPathPoints; i++)
+ {
+ if(minx > mpPathPoints[i].x) minx = mpPathPoints[i].x;
+ if(miny > mpPathPoints[i].y) miny = mpPathPoints[i].y;
+ if(maxx < mpPathPoints[i].x) maxx = mpPathPoints[i].x;
+ if(maxy < mpPathPoints[i].y) maxy = mpPathPoints[i].y;
+ }
+
+ xsz = max(maxx - minx, 0);
+ ysz = max(maxy - miny, 0);
+
+ dst1x = m_style.mod_distort.pointsx[0];
+ dst1y = m_style.mod_distort.pointsy[0];
+ dst2x = m_style.mod_distort.pointsx[1];
+ dst2y = m_style.mod_distort.pointsy[1];
+ dst3x = m_style.mod_distort.pointsx[2];
+ dst3y = m_style.mod_distort.pointsy[2];
+ }
+#endif
+
+ for(ptrdiff_t i = 0; i < mPathPoints; i++)
+ {
+ double x, y, z, xx, yy, zz;
+
+ x = mpPathPoints[i].x;
+ y = mpPathPoints[i].y;
+#ifdef _VSMOD
+ // patch m002. Z-coord
+ z = m_style.mod_z;
+
+ double u, v;
+ if (is_dist)
+ {
+ u = (x-minx) / xsz;
+ v = (y-miny) / ysz;
+
+ x = minx+(0 + (dst1x - 0)*u + (dst3x-0)*v+(0+dst2x-dst1x-dst3x)*u*v)*xsz;
+ y = miny+(0 + (dst1y - 0)*u + (dst3y-0)*v+(0+dst2y-dst1y-dst3y)*u*v)*ysz;
+ //P = P0 + (P1 - P0)u + (P3 - P0)v + (P0 + P2 - P1 - P3)uv
+ }
+
+ // patch m003. random text points
+ x = xrnd > 0 ? (xrnd - rand() % (int)(xrnd * 2 + 1)) / 100.0 + x : x;
+ y = yrnd > 0 ? (yrnd - rand() % (int)(yrnd * 2 + 1)) / 100.0 + y : y;
+ z = zrnd > 0 ? (zrnd - rand() % (int)(zrnd * 2 + 1)) / 100.0 + z : z;
+#else
+ z = 0;
+#endif
+ double _x = x;
+ x = scalex * (x + m_style.fontShiftX * y) - org.x;
+ y = scaley * (y + m_style.fontShiftY * _x) - org.y;
+
+ 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);
+ }
+}
+
+void CWord::Transform_SSE2( CPoint &org )
+{
+ // __m128 union data type currently not supported with Intel C++ Compiler, so just call C version
+#ifdef __ICL
+ Transform_C(org);
+#else
+ // SSE code
+ // speed up ~1.5-1.7x
+ 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);
+
+ __m128 __xshift = _mm_set_ps1(m_style.fontShiftX);
+ __m128 __yshift = _mm_set_ps1(m_style.fontShiftY);
+
+ __m128 __xorg = _mm_set_ps1(org.x);
+ __m128 __yorg = _mm_set_ps1(org.y);
+
+ __m128 __xscale = _mm_set_ps1(scalex);
+ __m128 __yscale = _mm_set_ps1(scaley);
+
+#ifdef _VSMOD
+ // patch m003. random text points
+ double xrnd = m_style.mod_rand.X*100;
+ double yrnd = m_style.mod_rand.Y*100;
+ double zrnd = m_style.mod_rand.Z*100;
+
+ srand(m_style.mod_rand.Seed);
+
+ __m128 __xsz = _mm_setzero_ps();
+ __m128 __ysz = _mm_setzero_ps();
+
+ __m128 __dst1x, __dst1y, __dst213x, __dst213y, __dst3x, __dst3y;
+
+ __m128 __miny;
+ __m128 __minx = _mm_set_ps(INT_MAX, INT_MAX, 0, 0);
+ __m128 __max = _mm_set_ps(-INT_MAX, -INT_MAX, 1, 1);
+
+ bool is_dist = m_style.mod_distort.enabled;
+ if(is_dist)
+ {
+ for(int i = 0; i < mPathPoints; i++)
+ {
+ __m128 __point = _mm_set_ps(mpPathPoints[i].x, mpPathPoints[i].y, 0, 0);
+ __minx = _mm_min_ps(__minx, __point);
+ __max = _mm_max_ps(__max, __point);
+ }
+
+ __m128 __zero = _mm_setzero_ps();
+ __max = _mm_sub_ps(__max, __minx); // xsz, ysz, 1, 1
+ __max = _mm_max_ps(__max, __zero);
+
+ __xsz = _mm_shuffle_ps(__max, __max, _MM_SHUFFLE(3,3,3,3));
+ __ysz = _mm_shuffle_ps(__max, __max, _MM_SHUFFLE(2,2,2,2));
+
+ __miny = _mm_shuffle_ps(__minx, __minx, _MM_SHUFFLE(2,2,2,2));
+ __minx = _mm_shuffle_ps(__minx, __minx, _MM_SHUFFLE(3,3,3,3));
+
+ __dst1x = _mm_set_ps1(m_style.mod_distort.pointsx[0]);
+ __dst1y = _mm_set_ps1(m_style.mod_distort.pointsy[0]);
+ __dst3x = _mm_set_ps1(m_style.mod_distort.pointsx[2]);
+ __dst3y = _mm_set_ps1(m_style.mod_distort.pointsy[2]);
+ __dst213x = _mm_set_ps1(m_style.mod_distort.pointsx[1]); // 2 - 1 - 3
+ __dst213x = _mm_sub_ps(__dst213x, __dst1x);
+ __dst213x = _mm_sub_ps(__dst213x, __dst3x);
+
+ __dst213y = _mm_set_ps1(m_style.mod_distort.pointsy[1]);
+ __dst213x = _mm_sub_ps(__dst213y, __dst1y);
+ __dst213x = _mm_sub_ps(__dst213y, __dst3y);
+ }
+#endif
+
+ __m128 __caz = _mm_set_ps1(caz);
+ __m128 __saz = _mm_set_ps1(saz);
+ __m128 __cax = _mm_set_ps1(cax);
+ __m128 __sax = _mm_set_ps1(sax);
+ __m128 __cay = _mm_set_ps1(cay);
+ __m128 __say = _mm_set_ps1(say);
+
+ // this can be paralleled for openmp
+ int mPathPointsD4 = mPathPoints / 4;
+ int mPathPointsM4 = mPathPoints % 4;
+
+ for(ptrdiff_t i = 0; i < mPathPointsD4 + 1; i++)
+ {
+ __m128 __pointx, __pointy;
+ // we can't use load .-.
+ if(i != mPathPointsD4)
+ {
+ __pointx = _mm_set_ps(mpPathPoints[4 * i + 0].x, mpPathPoints[4 * i + 1].x, mpPathPoints[4 * i + 2].x, mpPathPoints[4 * i + 3].x);
+ __pointy = _mm_set_ps(mpPathPoints[4 * i + 0].y, mpPathPoints[4 * i + 1].y, mpPathPoints[4 * i + 2].y, mpPathPoints[4 * i + 3].y);
+ }
+ else // last cycle
+ {
+ switch(mPathPointsM4)
+ {
+ default:
+ case 0: continue;
+ case 1:
+ __pointx = _mm_set_ps(mpPathPoints[4 * i + 0].x, 0, 0, 0);
+ __pointy = _mm_set_ps(mpPathPoints[4 * i + 0].y, 0, 0, 0);
+ break;
+ case 2:
+ __pointx = _mm_set_ps(mpPathPoints[4 * i + 0].x, mpPathPoints[4 * i + 1].x, 0, 0);
+ __pointy = _mm_set_ps(mpPathPoints[4 * i + 0].y, mpPathPoints[4 * i + 1].y, 0, 0);
+ break;
+ case 3:
+ __pointx = _mm_set_ps(mpPathPoints[4 * i + 0].x, mpPathPoints[4 * i + 1].x, mpPathPoints[4 * i + 2].x, 0);
+ __pointy = _mm_set_ps(mpPathPoints[4 * i + 0].y, mpPathPoints[4 * i + 1].y, mpPathPoints[4 * i + 2].y, 0);
+ break;
+ }
+ }
+
+#ifdef _VSMOD
+ __m128 __pointz = _mm_set_ps1(m_style.mod_z);
+
+ // distort
+ if(is_dist)
+ {
+ //P = P0 + (P1 - P0)u + (P3 - P0)v + (P0 + P2 - P1 - P3)uv
+ __m128 __u = _mm_sub_ps(__pointx, __minx);
+ __m128 __v = _mm_sub_ps(__pointy, __miny);
+ __m128 __1_xsz = _mm_rcp_ps(__xsz);
+ __m128 __1_ysz = _mm_rcp_ps(__ysz);
+ __u = _mm_mul_ps(__u, __1_xsz);
+ __v = _mm_mul_ps(__v, __1_ysz);
+
+ // x
+ __pointx = _mm_mul_ps(__dst213x, __u);
+ __pointx = _mm_mul_ps(__pointx, __v);
+
+ __m128 __tmpx = _mm_mul_ps(__dst3x, __v);
+ __pointx = _mm_add_ps(__pointx, __tmpx);
+ __tmpx = _mm_mul_ps(__dst1x, __u);
+ __pointx = _mm_add_ps(__pointx, __tmpx);
+
+ __pointx = _mm_mul_ps(__pointx, __xsz);
+ __pointx = _mm_add_ps(__pointx, __minx);
+
+ // y
+ __pointy = _mm_mul_ps(__dst213y, __u);
+ __pointy = _mm_mul_ps(__pointy, __v);
+
+ __m128 __tmpy = _mm_mul_ps(__dst3y, __v);
+ __pointy = _mm_add_ps(__pointy, __tmpy);
+ __tmpy = _mm_mul_ps(__dst1y, __u);
+ __pointy = _mm_add_ps(__pointy, __tmpy);
+
+ __pointy = _mm_mul_ps(__pointy, __ysz);
+ __pointy = _mm_add_ps(__pointy, __miny);
+ }
+
+ // randomize
+ if(xrnd!=0 || yrnd!=0 || zrnd!=0)
+ {
+ __declspec(align(16)) float rx[4], ry[4], rz[4];
+ for(int k=0;k<4;k++)
+ {
+ rx[k] = xrnd > 0 ? (xrnd - rand() % (int)(xrnd * 2 + 1)) : 0;
+ ry[k] = yrnd > 0 ? (yrnd - rand() % (int)(yrnd * 2 + 1)) : 0;
+ rz[k] = zrnd > 0 ? (zrnd - rand() % (int)(zrnd * 2 + 1)) : 0;
+ }
+ __m128 __001 = _mm_set_ps1(0.01f);
+
+ if(xrnd!=0)
+ {
+ __m128 __rx = _mm_load_ps(rx);
+ __rx = _mm_mul_ps(__rx, __001);
+ __pointx = _mm_add_ps(__pointx, __rx);
+ }
+
+ if(yrnd!=0)
+ {
+ __m128 __ry = _mm_load_ps(ry);
+ __ry = _mm_mul_ps(__ry, __001);
+ __pointy = _mm_add_ps(__pointy, __ry);
+ }
+
+ if(zrnd!=0)
+ {
+ __m128 __rz = _mm_load_ps(rz);
+ __rz = _mm_mul_ps(__rz, __001);
+ __pointz = _mm_add_ps(__pointz, __rz);
+ }
+ }
+#else
+ __m128 __pointz = _mm_set_ps1(0);
+#endif
+
+ // scale and shift
+ __m128 __tmpx;
+ if(m_style.fontShiftX!=0)
+ {
+ __tmpx = _mm_mul_ps(__xshift, __pointy);
+ __tmpx = _mm_add_ps(__tmpx, __pointx);
+ }
+ else
+ {
+ __tmpx = __pointx;
+ }
+ __tmpx = _mm_mul_ps(__tmpx, __xscale);
+ __tmpx = _mm_sub_ps(__tmpx, __xorg);
+
+ __m128 __tmpy;
+ if(m_style.fontShiftY!=0)
+ {
+ __tmpy = _mm_mul_ps(__yshift, __pointx);
+ __tmpy = _mm_add_ps(__tmpy, __pointy);
+ }
+ else
+ {
+ __tmpy = __pointy;
+ }
+ __tmpy = _mm_mul_ps(__tmpy, __yscale);
+ __tmpy = _mm_sub_ps(__tmpy, __yorg);
+
+ // rotate
+ __m128 __xx = _mm_mul_ps(__tmpx, __caz);
+ __m128 __yy = _mm_mul_ps(__tmpy, __saz);
+ __pointx = _mm_add_ps(__xx, __yy);
+ __xx = _mm_mul_ps(__tmpx, __saz);
+ __yy = _mm_mul_ps(__tmpy, __caz);
+ __pointy = _mm_sub_ps(__yy, __xx);
+
+ __m128 __zz = _mm_mul_ps(__pointz, __sax);
+ __yy = _mm_mul_ps(__pointy, __cax);
+ __pointy = _mm_add_ps(__yy, __zz);
+ __zz = _mm_mul_ps(__pointz, __cax);
+ __yy = _mm_mul_ps(__pointy, __sax);
+ __pointz = _mm_sub_ps(__zz, __yy);
+
+ __xx = _mm_mul_ps(__pointx, __cay);
+ __zz = _mm_mul_ps(__pointz, __say);
+ __pointx = _mm_add_ps(__xx, __zz);
+ __xx = _mm_mul_ps(__pointx, __say);
+ __zz = _mm_mul_ps(__pointz, __cay);
+ __pointz = _mm_sub_ps(__xx, __zz);
+
+ __zz = _mm_set_ps1(-19000);
+ __pointz = _mm_max_ps(__pointz, __zz);
+
+ __m128 __20000 = _mm_set_ps1(20000);
+ __zz = _mm_add_ps(__pointz, __20000);
+ __zz = _mm_rcp_ps(__zz);
+
+ __pointx = _mm_mul_ps(__pointx, __20000);
+ __pointx = _mm_mul_ps(__pointx, __zz);
+
+ __pointy = _mm_mul_ps(__pointy, __20000);
+ __pointy = _mm_mul_ps(__pointy, __zz);
+
+ __pointx = _mm_add_ps(__pointx, __xorg);
+ __pointy = _mm_add_ps(__pointy, __yorg);
+
+ __m128 __05 = _mm_set_ps1(0.5);
+
+ __pointx = _mm_add_ps(__pointx, __05);
+ __pointy = _mm_add_ps(__pointy, __05);
+
+ if(i == mPathPointsD4) // last cycle
+ {
+ for(int k=0; k<mPathPointsM4; k++)
+ {
+ mpPathPoints[i*4+k].x = static_cast<LONG>(__pointx.m128_f32[3-k]);
+ mpPathPoints[i*4+k].y = static_cast<LONG>(__pointy.m128_f32[3-k]);
+ }
+ }
+ else
+ {
+ for(int k=0; k<4; k++)
+ {
+ mpPathPoints[i*4+k].x = static_cast<LONG>(__pointx.m128_f32[3-k]);
+ mpPathPoints[i*4+k].y = static_cast<LONG>(__pointy.m128_f32[3-k]);
+ }
+ }
+ }
+#endif // __ICL
+}
+
+// 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);
+
+#ifdef _VSMOD // patch m007. symbol rotating
+ double t = (double)m_style.mod_fontOrient * 3.1415926 / 1800;
+#endif
+ if(m_style.fontSpacing || (long)GetVersion() < 0)
+ {
+ for(LPCWSTR s = m_str; *s; s++)
+ {
+ CSize extent;
+ if(!GetTextExtentPoint32W(g_hDC, s, 1, &extent))
+ {
+ SelectFont(g_hDC, hOldFont);
+ ASSERT(0);
+ return;
+ }
+#ifdef _VSMOD // patch m007. symbol rotating
+ m_width += (int)(extent.cx*abs(cos(t)) + extent.cy*abs(sin(t)) + m_style.fontSpacing);
+#else
+ m_width += extent.cx + (int)m_style.fontSpacing;
+#endif
+ }
+// 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;
+ }
+#ifdef _VSMOD // patch m007. symbol rotating
+ m_width += (int)(extent.cx*abs(cos(t)) + extent.cy*abs(sin(t)));
+#else
+ m_width += extent.cx;
+#endif
+ }
+
+ m_width = (int)(m_style.fontScaleX/100*m_width + 4) >> 3;
+
+ SelectFont(g_hDC, hOldFont);
+}
+
+CWord* CText::Copy()
+{
+ return(DNew CText(m_style, m_str, m_ktype, m_kstart, m_kend));
+}
+
+bool CText::Append(CWord* w)
+{
+ return(dynamic_cast<CText*>(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(DNew CPolygon(m_style, m_str, m_ktype, m_kstart, m_kend, m_scalex, m_scaley, m_baseline));
+}
+
+bool CPolygon::Append(CWord* w)
+{
+ CPolygon* p = dynamic_cast<CPolygon*>(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);
+ }
+ 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(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()
+{
+ size_t 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, bool inverse)
+ : 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)
+ return;
+
+ m_pAlphaMask = DNew BYTE[size.cx*size.cy];
+ if (!m_pAlphaMask)
+ return;
+
+ m_size = size;
+ m_inverse = inverse;
+
+ 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(ptrdiff_t wt=0; wt<w; ++wt)
+ dst[wt] = src[wt*2];
+
+ src += 2*mOverlayWidth;
+ dst += m_size.cx;
+ }
+
+ if(inverse)
+ {
+ BYTE* dst = m_pAlphaMask;
+ for(ptrdiff_t i = size.cx*size.cy; i>0; --i, ++dst)
+ *dst = 0x40 - *dst; // mask is 6 bit
+ }
+}
+
+CClipper::~CClipper()
+{
+ if(m_pAlphaMask) delete [] m_pAlphaMask;
+ m_pAlphaMask = NULL;
+}
+
+CWord* CClipper::Copy()
+{
+ return(DNew CClipper(m_str, m_size, m_scalex, m_scaley, m_inverse));
+}
+
+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 = 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);
+ }
+}
+
+#ifdef _VSMOD // patch m006. moveable vector clip
+CRect CLine::PaintShadow(SubPicDesc& spd, CRect& clipRect, BYTE* pAlphaMask, CPoint p, CPoint org, int time, int alpha, MOD_MOVEVC& mod_vc, REFERENCE_TIME rt)
+#else
+CRect CLine::PaintShadow(SubPicDesc& spd, CRect& clipRect, BYTE* pAlphaMask, CPoint p, CPoint org, int time, int alpha)
+#endif
+{
+ 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);
+#ifdef _VSMOD // patch m001. Vertical fontspacing
+ int y = p.y - w->m_style.mod_verticalSpace + m_ascent - w->m_ascent + (int)(w->m_style.shadowDepthY+0.5);
+#else
+ int y = p.y + m_ascent - w->m_ascent + (int)(w->m_style.shadowDepthY+0.5);
+#endif
+ 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);
+ DWORD sw[6] = {shadow, (DWORD)-1};
+
+#ifdef _VSMOD // patch m011. jitter
+ CPoint mod_jitter = w->m_style.mod_jitter.getOffset(rt);
+ x += mod_jitter.x;
+ y += mod_jitter.y;
+ // patch m010. png background
+ // subpixel positioning
+ w->m_style.mod_grad.subpixx = x&7;
+ w->m_style.mod_grad.subpixy = y&7;
+ w->m_style.mod_grad.fadalpha = alpha;
+#endif
+ w->Paint(CPoint(x, y), org);
+
+ if(w->m_style.borderStyle == 0)
+ {
+#ifdef _VSMOD // patch m004. gradient colors
+ 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),3,w->m_style.mod_grad, mod_vc);
+#else
+ 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));
+#endif
+ }
+ else if(w->m_style.borderStyle == 1 && w->m_pOpaqueBox)
+ {
+#ifdef _VSMOD // patch m004. gradient colors
+ bbox |= w->m_pOpaqueBox->Draw(spd, clipRect, pAlphaMask, x, y, sw, true, false,3,w->m_style.mod_grad, mod_vc);
+#else
+ bbox |= w->m_pOpaqueBox->Draw(spd, clipRect, pAlphaMask, x, y, sw, true, false);
+#endif
+ }
+ }
+
+ p.x += w->m_width;
+ }
+
+ return(bbox);
+}
+
+#ifdef _VSMOD // patch m006. moveable vector clip
+CRect CLine::PaintOutline(SubPicDesc& spd, CRect& clipRect, BYTE* pAlphaMask, CPoint p, CPoint org, int time, int alpha, MOD_MOVEVC& mod_vc, REFERENCE_TIME rt)
+#else
+CRect CLine::PaintOutline(SubPicDesc& spd, CRect& clipRect, BYTE* pAlphaMask, CPoint p, CPoint org, int time, int alpha)
+#endif
+{
+ 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;
+#ifdef _VSMOD // patch m001. Vertical fontspacing
+ int y = p.y - w->m_style.mod_verticalSpace + m_ascent - w->m_ascent;
+#else
+ int y = p.y + m_ascent - w->m_ascent;
+#endif
+ 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);
+ DWORD sw[6] = {outline, (DWORD)-1};
+
+#ifdef _VSMOD // patch m011. jitter
+ CPoint mod_jitter = w->m_style.mod_jitter.getOffset(rt);
+ x += mod_jitter.x;
+ y += mod_jitter.y;
+ // patch m010. png background
+ // subpixel positioning
+ w->m_style.mod_grad.subpixx = x&7;
+ w->m_style.mod_grad.subpixy = y&7;
+ w->m_style.mod_grad.fadalpha = alpha;
+#endif
+
+ w->Paint(CPoint(x, y), org);
+
+ if(w->m_style.borderStyle == 0)
+ {
+#ifdef _VSMOD // patch m004. gradient colors
+ bbox |= w->Draw(spd, clipRect, pAlphaMask, x, y, sw, !w->m_style.alpha[0] && !w->m_style.alpha[1] && !alpha, true,2,w->m_style.mod_grad, mod_vc);
+#else
+ bbox |= w->Draw(spd, clipRect, pAlphaMask, x, y, sw, !w->m_style.alpha[0] && !w->m_style.alpha[1] && !alpha, true);
+#endif
+ }
+ else if(w->m_style.borderStyle == 1 && w->m_pOpaqueBox)
+ {
+#ifdef _VSMOD // patch m004. gradient colors
+ bbox |= w->m_pOpaqueBox->Draw(spd, clipRect, pAlphaMask, x, y, sw, true, false,2,w->m_style.mod_grad, mod_vc);
+#else
+ bbox |= w->m_pOpaqueBox->Draw(spd, clipRect, pAlphaMask, x, y, sw, true, false);
+#endif
+ }
+ }
+
+ p.x += w->m_width;
+ }
+
+ return(bbox);
+}
+
+#ifdef _VSMOD // patch m006. moveable vector clip
+CRect CLine::PaintBody(SubPicDesc& spd, CRect& clipRect, BYTE* pAlphaMask, CPoint p, CPoint org, int time, int alpha, MOD_MOVEVC& mod_vc, REFERENCE_TIME rt)
+#else
+CRect CLine::PaintBody(SubPicDesc& spd, CRect& clipRect, BYTE* pAlphaMask, CPoint p, CPoint org, int time, int alpha)
+#endif
+{
+ 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;
+#ifdef _VSMOD // patch m001. Vertical fontspacing
+ int y = p.y - w->m_style.mod_verticalSpace + m_ascent - w->m_ascent;
+#else
+ int y = p.y + m_ascent - w->m_ascent;
+#endif
+ // 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);
+
+ DWORD 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] = 0xFFFFFFF;
+ }
+
+ // 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;
+ double tx = w->m_style.fontAngleZ;
+ UNUSED_ALWAYS(tx);
+ sw[4] = sw[2];
+ sw[5] = 0x00ffffff;
+
+#ifdef _VSMOD // patch m011. jitter
+ CPoint mod_jitter = w->m_style.mod_jitter.getOffset(rt);
+ x += mod_jitter.x;
+ y += mod_jitter.y;
+ // patch m010. png background
+ // subpixel positioning
+ w->m_style.mod_grad.subpixx = x&7;
+ w->m_style.mod_grad.subpixy = y&7;
+ w->m_style.mod_grad.fadalpha = alpha;
+#endif
+
+ w->Paint(CPoint(x, y), org);
+
+ sw[3] = (int)(w->m_style.outlineWidthX + t*w->getOverlayWidth() + t*bluradjust) >> 3;
+
+#ifdef _VSMOD // patch m004. gradient colors
+ bbox |= w->Draw(spd, clipRect, pAlphaMask, x, y, sw, true, false,0,w->m_style.mod_grad, mod_vc);
+#else
+ bbox |= w->Draw(spd, clipRect, pAlphaMask, x, y, sw, true, false);
+#endif
+ 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(ptrdiff_t 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 = DNew 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 = DNew CClipper(str, size, 1, 1, false);
+ if(!m_pClipper) return;
+ }
+
+ 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 = min(width, w);
+
+ for(ptrdiff_t 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(ptrdiff_t 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 = DNew CClipper(str, size, 1, 1, false);
+ 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(ptrdiff_t j = k; j < l; j++, a += da)
+ {
+ for(ptrdiff_t 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(ptrdiff_t 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<int>& 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(ptrdiff_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(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, STSStyle *styleOverride, bool doOverride)
+ : CSubPicProviderImpl(pLock), m_doOverrideStyle(doOverride), m_pStyleOverride(styleOverride)
+{
+ m_size = CSize(0, 0);
+
+ if(g_hDC_refcnt == 0)
+ {
+ g_hDC = CreateCompatibleDC(NULL);
+#ifdef _VSMOD // patch m007. symbol rotating
+ SetGraphicsMode(g_hDC, GM_ADVANCED); // patch for lfOrientation
+#endif
+ 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<CRenderedTextSubtitle*>(&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 = DNew 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 = DNew 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(size_t 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 = DNew 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 = DNew 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 = DNew 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 = DNew 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<CStringW> params;
+
+ if(str[j] == '(')
+ {
+ CStringW param;
+ // complex tags search
+ int br = 1; // 1 bracket
+ for(WCHAR c = str[++j];c && br>0;param += c, c = str[++j])
+ {
+ if (c=='(') br++;
+ if (c==')') br--;
+ if (br==0) break;
+ }
+ 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);
+#ifdef _VSMOD // patch m010. png background
+ else if(!cmd.Find(L"1img") || !cmd.Find(L"2img") || !cmd.Find(L"3img") || !cmd.Find(L"4img"))
+ ;//params.Add(cmd.Mid(4)), cmd = cmd.Left(4);
+#endif
+#ifdef _VSMOD // patch m004. gradient colors
+ else if(!cmd.Find(L"1vc") || !cmd.Find(L"2vc") || !cmd.Find(L"3vc") || !cmd.Find(L"4vc"))
+ ;//params.Add(cmd.Mid(3)), cmd = cmd.Left(3);
+ else if(!cmd.Find(L"1va") || !cmd.Find(L"2va") || !cmd.Find(L"3va") || !cmd.Find(L"4va"))
+ ;//params.Add(cmd.Mid(3)), cmd = cmd.Left(3);
+#endif
+ 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);
+#ifdef _VSMOD // patch m008. distort
+ else if(!cmd.Find(L"distort"))
+ ;
+#endif
+ 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);
+#ifdef _VSMOD // patch m007. symbol rotating
+ else if(!cmd.Find(L"frs"))
+ params.Add(cmd.Mid(3)), cmd = cmd.Left(3);
+#endif
+ 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);
+#ifdef _VSMOD// patch m001. Vertical fontspacing
+ else if(!cmd.Find(L"fsvp"))
+ params.Add(cmd.Mid(4)), cmd = cmd.Left(4);
+#endif
+ 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);
+#ifdef _VSMOD // patch m011. jitter
+ else if(!cmd.Find(L"jitter"))
+ ;
+#endif
+ 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);
+#ifdef _VSMOD // patch m005. add some move types
+ else if(!cmd.Find(L"mover")) // radial move
+ ;
+ else if(!cmd.Find(L"moves3")) // square spline
+ ;
+ else if(!cmd.Find(L"moves4")) // cubic spline
+ ;
+#endif
+#ifdef _VSMOD // patch m006. moveable vector clip
+ else if(!cmd.Find(L"movevc"))
+ ;
+#endif
+ 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);
+#ifdef _VSMOD // patch m003. random text points
+ else if(!cmd.Find(L"rndx") || !cmd.Find(L"rndy") || !cmd.Find(L"rndz") || !cmd.Find(L"rnds"))
+ params.Add(cmd.Mid(4)), cmd = cmd.Left(4);
+ else if(!cmd.Find(L"rnd"))
+ params.Add(cmd.Mid(3)), cmd = cmd.Left(3);
+#endif
+ 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);
+#ifdef _VSMOD // patch m002. Z-coord
+ else if(!cmd.Find(L"z"))
+ params.Add(cmd.Mid(1)), cmd = cmd.Left(1);
+#endif
+ 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];
+#ifdef _VSMOD // patch m004. gradient colors
+ style.mod_grad.colors[i] = style.colors[i];
+ if (!fAnimate)
+ {
+ style.mod_grad.mode[i] = 0;
+ for (int j=0;j<4;j++)
+ {
+ style.mod_grad.color[i][j] = !p.IsEmpty()
+ ? ((int)c&0xff
+ |(int)c&0xff00
+ |(int)c&0xff0000)
+ : org.mod_grad.color[i][j];
+ }
+ }
+ else if(style.mod_grad.mode[i] != 0)
+ {
+ for (int j=0;j<4;j++)
+ {
+ style.mod_grad.color[i][j] = !p.IsEmpty()
+ ? (((int)CalcAnimation(c&0xff, style.mod_grad.color[i][j]&0xff, fAnimate))&0xff
+ |((int)CalcAnimation(c&0xff00, style.mod_grad.color[i][j]&0xff00, fAnimate))&0xff00
+ |((int)CalcAnimation(c&0xff0000, style.mod_grad.color[i][j]&0xff0000, fAnimate))&0xff0000)
+ : org.colors[i];
+ }
+ }
+#endif
+ }
+ else if(cmd == L"1a" || cmd == L"2a" || cmd == L"3a" || cmd == L"4a")
+ {
+ DWORD al = wcstol(p, NULL, 16)&0xff;
+ int i = cmd[0] - '1';
+
+ style.alpha[i] = !p.IsEmpty()
+ ? (BYTE)CalcAnimation(al, style.alpha[i], fAnimate)
+ : org.alpha[i];
+#ifdef _VSMOD // patch m004. gradient colors
+ style.mod_grad.alphas[i] = style.alpha[i];
+ style.mod_grad.b_images[i].alpha = 255-style.alpha[i];
+ if (!fAnimate)
+ {
+ //style.mod_grad.mode[i] = 0;
+ for (int j=0;j<4;j++)
+ {
+ style.mod_grad.alpha[i][j] = !p.IsEmpty()
+ ? al
+ : org.mod_grad.alpha[i][j];
+ }
+ }
+ else if (style.mod_grad.mode[i] != 0)
+ {
+ for (int j=0;j<4;j++)
+ {
+ style.mod_grad.alpha[i][j] = !p.IsEmpty()
+ ? (((int)CalcAnimation(al, style.mod_grad.alpha[i][j], fAnimate)))
+ : org.alpha[i];
+ }
+ }
+#endif
+ }
+#ifdef _VSMOD // patch m010. png background
+ else if(cmd == L"1img" || cmd == L"2img" || cmd == L"3img" || cmd == L"4img")
+ {
+ int i = cmd[0] - '1';
+
+ if(params.GetCount() >= 1)// file[,xoffset,yoffset[,angle]]
+ {
+ if (!fAnimate)
+ {
+ CString fpath = m_path.Left(m_path.ReverseFind('\\')+1);
+ bool t_init = false;
+ // buffer
+ for(ptrdiff_t k = 0, j = mod_images.GetCount(); k < j; k++)
+ {
+ MOD_PNGIMAGE t_temp = mod_images[k];
+ if(t_temp.filename==params[0]) // found buffered image
+ {
+
+ style.mod_grad.b_images[i] = t_temp;
+ t_init = true;
+ break;
+ }
+ if(t_temp.filename==fpath+params[0]) // found buffered image
+ {
+ style.mod_grad.b_images[i] = t_temp;
+ t_init = true;
+ break;
+ }
+ }
+ if(t_init)
+ {
+ style.mod_grad.mode[i] = 2;
+ }
+ else
+ {
+ // not found
+ MOD_PNGIMAGE t_temp;
+ if(t_temp.initImage(params[0])) // absolute path or default directory
+ {
+ style.mod_grad.mode[i] = 2;
+ style.mod_grad.b_images[i] = t_temp;
+ mod_images.Add(t_temp);
+ }
+ else if(t_temp.initImage(fpath+params[0])) // path + relative path
+ {
+ style.mod_grad.mode[i] = 2;
+ style.mod_grad.b_images[i] = t_temp;
+ mod_images.Add(t_temp);
+ }
+ }
+ }
+ if(params.GetCount() >= 3)
+ {
+ style.mod_grad.b_images[i].xoffset = !p.IsEmpty()
+ ? CalcAnimation(wcstol(params[1], NULL, 10), style.mod_grad.b_images[i].xoffset, fAnimate)
+ : org.mod_grad.b_images[i].xoffset;
+ style.mod_grad.b_images[i].yoffset = !p.IsEmpty()
+ ? CalcAnimation(wcstol(params[2], NULL, 10), style.mod_grad.b_images[i].yoffset, fAnimate)
+ : org.mod_grad.b_images[i].yoffset;
+ }
+ }
+ }
+#endif
+#ifdef _VSMOD // patch m004. gradient colors
+ else if(cmd == L"1vc" || cmd == L"2vc" || cmd == L"3vc" || cmd == L"4vc")
+ {
+ int i = cmd[0] - '1';
+
+ if(params.GetCount() >= 4)
+ {
+ DWORD c;
+ for (int j=0;j<4;j++)
+ {
+ c = wcstol(params[j].Trim(L"&H"), NULL, 16);
+ style.mod_grad.color[i][j] = !p.IsEmpty()
+ ? (((int)CalcAnimation((c&0xff0000)>>16, style.mod_grad.color[i][j]&0xff, fAnimate))&0xff
+ |((int)CalcAnimation(c&0xff00, style.mod_grad.color[i][j]&0xff00, fAnimate))&0xff00
+ |((int)CalcAnimation((c&0xff)<<16, style.mod_grad.color[i][j]&0xff0000, fAnimate))&0xff0000)
+ : org.mod_grad.color[i][j];
+ }
+ if (style.mod_grad.mode[i]==0)
+ {
+ for (int j=0;j<4;j++)
+ {
+ style.mod_grad.alpha[i][j] = style.alpha[i];
+ }
+ }
+ //if (!fAnimate)
+ style.mod_grad.mode[i] = 1;
+ }
+ }
+ else if(cmd == L"1va" || cmd == L"2va" || cmd == L"3va" || cmd == L"4va")
+ {
+ int i = cmd[0] - '1';
+
+ if(params.GetCount() >= 4)
+ {
+ int a;
+ for (int j=0;j<4;j++)
+ {
+ a = wcstol(params[j].Trim(L"&H"), NULL, 16);
+ style.mod_grad.alpha[i][j] = !p.IsEmpty()
+ ? (int)CalcAnimation(a, style.mod_grad.alpha[i][j], fAnimate) : org.mod_grad.alpha[i][j];
+ }
+ if (style.mod_grad.mode[i]==0)
+ {
+ for (int j=0;j<4;j++)
+ {
+ style.mod_grad.color[i][j] = style.colors[i];
+ }
+ }
+ //if (!fAnimate)
+ style.mod_grad.mode[i] = 1;
+ }
+ }
+#endif
+ else if(cmd == L"alpha")
+ {
+ for(ptrdiff_t i = 0; i < 4; i++)
+ {
+ DWORD al = wcstol(p, NULL, 16)&0xff;
+ style.alpha[i] = !p.IsEmpty()
+ ? (BYTE)CalcAnimation(al, style.alpha[i], fAnimate)
+ : org.alpha[i];
+#ifdef _VSMOD // patch m004. gradient colors
+ style.mod_grad.alphas[i] = style.alpha[i];
+ if (!fAnimate)
+ {
+ //style.mod_grad.mode[i] = 0;
+ for (int j=0;j<4;j++)
+ {
+ style.mod_grad.alpha[i][j] = !p.IsEmpty()
+ ? al
+ : org.mod_grad.alpha[i][j];
+ style.mod_grad.b_images[i].alpha = 255-al;
+ }
+ }
+ else if(style.mod_grad.mode[i] != 0)
+ {
+ for (int j=0;j<4;j++)
+ {
+ style.mod_grad.alpha[i][j] = !p.IsEmpty()
+ ? (((int)CalcAnimation(al, style.mod_grad.alpha[i][j], fAnimate)))
+ : org.alpha[i];
+ style.mod_grad.b_images[i].alpha = 255-style.alpha[i];
+ }
+ }
+#endif
+ }
+ }
+ 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")
+ {
+ bool invert = (cmd == L"iclip");
+
+ if(params.GetCount() == 1 && !sub->m_pClipper)
+ {
+ sub->m_pClipper = DNew CClipper(params[0], CSize(m_size.cx>>3, m_size.cy>>3), sub->m_scalex, sub->m_scaley, invert);
+ }
+ else if(params.GetCount() == 2 && !sub->m_pClipper)
+ {
+ int scale = max(wcstol(p, NULL, 10), 1);
+ sub->m_pClipper = DNew CClipper(params[1], CSize(m_size.cx>>3, m_size.cy>>3), sub->m_scalex/(1<<(scale-1)), sub->m_scaley/(1<<(scale-1)), invert);
+ }
+ else if(params.GetCount() == 4)
+ {
+ CRect r;
+
+ sub->m_clipInverse = invert;
+
+ 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];
+#ifdef _VSMOD // patch m004. gradient colors
+ style.mod_grad.colors[0] = style.colors[0];
+ if (!fAnimate)
+ {
+ style.mod_grad.mode[0] = 0;
+ for (int j=0;j<4;j++)
+ {
+ style.mod_grad.color[0][j] = !p.IsEmpty()
+ ? ((int)c&0xff
+ |(int)c&0xff00
+ |(int)c&0xff0000)
+ : org.mod_grad.color[0][j];
+ }
+ }
+ else if (style.mod_grad.mode[0] != 0)
+ {
+ for (int j=0;j<4;j++)
+ {
+ style.mod_grad.color[0][j] = !p.IsEmpty()
+ ? (((int)CalcAnimation(c&0xff, style.mod_grad.color[0][j]&0xff, fAnimate))&0xff
+ |((int)CalcAnimation(c&0xff00, style.mod_grad.color[0][j]&0xff00, fAnimate))&0xff00
+ |((int)CalcAnimation(c&0xff0000, style.mod_grad.color[0][j]&0xff0000, fAnimate))&0xff0000)
+ : org.colors[0];
+ }
+ }
+#endif
+ }
+#ifdef _VSMOD // patch m008. distort
+ else if(cmd == L"distort")
+ {
+ if(params.GetCount() >= 6)
+ {
+ DWORD c;
+ for (int j=0;j<3;j++)
+ {
+ style.mod_distort.pointsx[j] = !p.IsEmpty()
+ ? (CalcAnimation(wcstod(params[j*2], NULL), style.mod_distort.pointsx[j], fAnimate))
+ : org.mod_distort.pointsx[j];
+ style.mod_distort.pointsy[j] = !p.IsEmpty()
+ ? (CalcAnimation(wcstod(params[j*2+1], NULL), style.mod_distort.pointsy[j], fAnimate))
+ : org.mod_distort.pointsy[j];
+ }
+ style.mod_distort.enabled = true;
+ }
+ }
+#endif
+ 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 = DNew Effect)
+ {
+ for(ptrdiff_t i = 0; i < 3; i++)
+ e->param[i] = wcstol(params[i], NULL, 10);
+ for(ptrdiff_t i = 0; i < 4; i++)
+ e->t[i] = wcstol(params[3+i], NULL, 10);
+
+ sub->m_effects[EF_FADE] = e;
+ }
+#ifdef _VSMOD // patch f005. don't cache animated
+ sub->m_fAnimated = true;
+#endif
+ }
+ else if(params.GetCount() == 2 && !sub->m_effects[EF_FADE]) // {\fad(t1=t[1], t2=t[2])
+ {
+ if(Effect* e = DNew Effect)
+ {
+ e->param[0] = e->param[2] = 0xff;
+ e->param[1] = 0x00;
+ for(ptrdiff_t 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;
+ }
+#ifdef _VSMOD // patch f005. don't cache animated
+ sub->m_fAnimated = true;
+#endif
+ }
+ }
+ 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;
+ }
+#ifdef _VSMOD // patch m007. symbol rotating
+ else if(cmd == L"frs")
+ {
+ double dst = wcstod(p, NULL)*10;
+
+ style.mod_fontOrient = !p.IsEmpty()
+ ? CalcAnimation(dst, style.mod_fontOrient, fAnimate)
+ : org.mod_fontOrient;
+ }
+#endif
+ 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")
+ {
+#ifdef _VSMOD // patch f004. \fsc(%f) is working
+ double dst = wcstod(p, NULL);
+ double nx = CalcAnimation(dst, style.fontScaleX, fAnimate);
+ style.fontScaleX = !p.IsEmpty()
+ ? (nx < 0 ? 0 : nx)
+ : org.fontScaleX;
+ double ny = CalcAnimation(dst, style.fontScaleY, fAnimate);
+ style.fontScaleY = !p.IsEmpty()
+ ? (ny < 0 ? 0 : ny)
+ : org.fontScaleY;
+#else
+ style.fontScaleX = org.fontScaleX;
+ style.fontScaleY = org.fontScaleY;
+#endif
+ }
+ else if(cmd == L"fsp")
+ {
+ style.fontSpacing = !p.IsEmpty()
+ ? CalcAnimation(wcstod(p, NULL), style.fontSpacing, fAnimate)
+ : org.fontSpacing;
+ }
+#ifdef _VSMOD // patch m001. Vertical fontspacing
+ else if(cmd == L"fsvp")
+ {
+ double dst = wcstod(p, NULL)*8;
+ double nx = CalcAnimation(dst, style.mod_verticalSpace, fAnimate);
+ style.mod_verticalSpace = !p.IsEmpty() ? nx : org.mod_verticalSpace;
+ }
+#endif
+ 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;
+ }
+#ifdef _VSMOD // patch m011. jitter
+ else if(cmd == L"jitter") // {\jitter(left,right,up,down,period,[seed])}
+ {
+ if((params.GetCount() >= 4))
+ {
+ int left = (int)abs(wcstol(params[0], NULL,10))*8;
+ int right = (int)abs(wcstol(params[1], NULL,10))*8;
+ int up = (int)abs(wcstol(params[2], NULL,10))*8;
+ int down = (int)abs(wcstol(params[3], NULL,10))*8;
+ style.mod_jitter.offset.top = CalcAnimation(up, style.mod_jitter.offset.top, fAnimate);
+ style.mod_jitter.offset.bottom = CalcAnimation(down, style.mod_jitter.offset.bottom, fAnimate);
+ style.mod_jitter.offset.left = CalcAnimation(left, style.mod_jitter.offset.left, fAnimate);
+ style.mod_jitter.offset.right = CalcAnimation(right, style.mod_jitter.offset.right, fAnimate);
+ style.mod_jitter.enabled = true;
+
+ if(params.GetCount() >= 5)
+ {
+ int period = wcstol(params[4], NULL, 10) * 10000;
+ style.mod_jitter.period = CalcAnimation(period, style.mod_jitter.period, fAnimate);
+ if(params.GetCount() >= 6)
+ {
+ style.mod_jitter.seed = wcstol(params[5], NULL, 10);
+ }
+ }
+ // patch f005. don't cache animated
+ sub->m_fAnimated = true;
+ }
+ }
+#endif
+ 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;
+#ifdef _VSMOD // patch f005. don't cache animated
+ sub->m_fAnimated = true;
+#endif
+ }
+ else if(cmd == L"ko")
+ {
+ m_ktype = 2;
+ m_kstart = m_kend;
+ m_kend += !p.IsEmpty()
+ ? wcstol(p, NULL, 10)*10
+ : 1000;
+#ifdef _VSMOD // patch f005. don't cache animated
+ sub->m_fAnimated = true;
+#endif
+ }
+ else if(cmd == L"k")
+ {
+ m_ktype = 0;
+ m_kstart = m_kend;
+ m_kend += !p.IsEmpty()
+ ? wcstol(p, NULL, 10)*10
+ : 1000;
+ }
+#ifdef _VSMOD // patch m005. add some move types
+ else if(cmd == L"mover") // {\mover(x1,x2,x2,y2,alp1,alp2,r1,r2,t1,t2)}
+ {
+ if((params.GetCount() == 8 || params.GetCount() == 10) && !sub->m_effects[EF_MOVE])
+ {
+ if(Effect* e = new Effect)
+ {
+ e->param[0] = 1; // ðàäèàëüíûé ìîâ
+ e->param[1] = (int)(sub->m_scalex*wcstod(params[0], NULL)*8); // x1
+ e->param[2] = (int)(sub->m_scaley*wcstod(params[1], NULL)*8); // y1
+ e->param[3] = (int)(sub->m_scalex*wcstod(params[2], NULL)*8); // x2
+ e->param[4] = (int)(sub->m_scaley*wcstod(params[3], NULL)*8); // y2
+ e->param[5] = (int)(wcstod(params[4], NULL)*10000); // alp1
+ e->param[6] = (int)(wcstod(params[5], NULL)*10000); // alp2
+ e->param[7] = (int)(sub->m_scalex*wcstod(params[6], NULL)*8); // r1
+ e->param[8] = (int)(sub->m_scaley*wcstod(params[7], NULL)*8); // r2
+
+ e->t[0] = e->t[1] = -1;
+
+ if(params.GetCount() == 10)
+ {
+ for(int i = 0; i < 2; i++)
+ e->t[i] = wcstol(params[8+i], NULL, 10);
+ }
+ sub->m_effects[EF_MOVE] = e;
+ }
+ // patch f005. don't cache animated
+// sub->m_fAnimated = true;
+ }
+ }
+ else if(cmd == L"moves3") // {\moves3(x1,x2,x2,y2,x3,y3[,t1,t2])}
+ {
+ if((params.GetCount() == 6 || params.GetCount() == 8) && !sub->m_effects[EF_MOVE])
+ {
+ if(Effect* e = new Effect)
+ {
+ e->param[0] = 2; // square spline
+ e->param[1] = (int)(sub->m_scalex*wcstod(params[0], NULL)*8); // x1
+ e->param[2] = (int)(sub->m_scaley*wcstod(params[1], NULL)*8); // y1
+ e->param[3] = (int)(sub->m_scalex*wcstod(params[2], NULL)*8); // x2
+ e->param[4] = (int)(sub->m_scaley*wcstod(params[3], NULL)*8); // y2
+ e->param[5] = (int)(sub->m_scalex*wcstod(params[4], NULL)*8); // x3
+ e->param[6] = (int)(sub->m_scaley*wcstod(params[5], NULL)*8); // y3
+ e->t[0] = e->t[1] = -1;
+
+ if(params.GetCount() == 8)
+ {
+ for(int i = 0; i < 2; i++)
+ e->t[i] = wcstol(params[6+i], NULL, 10);
+ }
+ sub->m_effects[EF_MOVE] = e;
+ }
+ // patch f005. don't cache animated
+// sub->m_fAnimated = true;
+ }
+ }
+ else if(cmd == L"moves4") // {\moves4(x1,x2,x2,y2,x3,y3,x4,y4[,t1,t2])}
+ {
+ if((params.GetCount() == 8 || params.GetCount() == 10) && !sub->m_effects[EF_MOVE])
+ {
+ if(Effect* e = new Effect)
+ {
+ e->param[0] = 3; // cubic spline
+ e->param[1] = (int)(sub->m_scalex*wcstod(params[0], NULL)*8); // x1
+ e->param[2] = (int)(sub->m_scaley*wcstod(params[1], NULL)*8); // y1
+ e->param[3] = (int)(sub->m_scalex*wcstod(params[2], NULL)*8); // x2
+ e->param[4] = (int)(sub->m_scaley*wcstod(params[3], NULL)*8); // y2
+ e->param[5] = (int)(sub->m_scalex*wcstod(params[4], NULL)*8); // x3
+ e->param[6] = (int)(sub->m_scaley*wcstod(params[5], NULL)*8); // y3
+ e->param[7] = (int)(sub->m_scalex*wcstod(params[6], NULL)*8); // x4
+ e->param[8] = (int)(sub->m_scaley*wcstod(params[7], NULL)*8); // y4
+ e->t[0] = e->t[1] = -1;
+
+ if(params.GetCount() == 10)
+ {
+ for(int i = 0; i < 2; i++)
+ e->t[i] = wcstol(params[8+i], NULL, 10);
+ }
+ sub->m_effects[EF_MOVE] = e;
+ }
+ // patch f005. don't cache animated
+// sub->m_fAnimated = true;
+ }
+ }
+#endif
+#ifdef _VSMOD // patch m006. moveable vector clip
+ else if(cmd == L"movevc")
+ {
+ if((params.GetCount() == 2 || params.GetCount() == 4 || params.GetCount() == 6) && !sub->m_effects[EF_VECTCLP])
+ {
+ if(Effect* e = new Effect)
+ {
+ e->param[0] = e->param[2] = (int)(sub->m_scalex*wcstod(params[0], NULL));
+ e->param[1] = e->param[3] = (int)(sub->m_scaley*wcstod(params[1], NULL));
+ e->t[0] = e->t[1] = -1;
+
+ if(params.GetCount() >= 4)
+ {
+ e->param[2] = (int)(sub->m_scalex*wcstod(params[2], NULL));
+ e->param[3] = (int)(sub->m_scaley*wcstod(params[3], NULL));
+ }
+ if(params.GetCount() == 6)
+ {
+ e->t[0] = (int)(sub->m_scalex*wcstod(params[4], NULL));
+ e->t[1] = (int)(sub->m_scaley*wcstod(params[5], NULL));
+ }
+ sub->m_effects[EF_VECTCLP] = e;
+ }
+ // patch f005. don't cache animated
+ sub->m_fAnimated = true;
+ }
+ }
+#endif
+ 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 = DNew Effect)
+ {
+#ifdef _VSMOD // patch m005. add some move types
+ e->param[0] = 0; // îáû÷íûé ìîâ
+ e->param[1] = (int)(sub->m_scalex*wcstod(params[0], NULL)*8);
+ e->param[2] = (int)(sub->m_scaley*wcstod(params[1], NULL)*8);
+ e->param[3] = (int)(sub->m_scalex*wcstod(params[2], NULL)*8);
+ e->param[4] = (int)(sub->m_scaley*wcstod(params[3], NULL)*8);
+#else
+ 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);
+#endif
+ e->t[0] = e->t[1] = -1;
+
+ if(params.GetCount() == 6)
+ {
+ for(ptrdiff_t i = 0; i < 2; i++)
+ e->t[i] = wcstol(params[4+i], NULL, 10);
+ }
+
+ sub->m_effects[EF_MOVE] = e;
+ }
+#ifdef _VSMOD // patch f005. don't cache animated
+// sub->m_fAnimated = true;
+#endif
+ }
+ }
+ else if(cmd == L"org") // {\org(x=param[0], y=param[1])}
+ {
+#ifdef _VSMOD // patch f003. moving \org for some karaoke effects. part 1
+ if((params.GetCount() == 2 || params.GetCount() == 4 || params.GetCount() == 6) && !sub->m_effects[EF_ORG])
+ {
+ 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] = -1;
+
+ if(params.GetCount() >= 4)
+ {
+ e->param[2] = (int)(sub->m_scalex*wcstod(params[2], NULL)*8);
+ e->param[3] = (int)(sub->m_scaley*wcstod(params[3], NULL)*8);
+ }
+ if(params.GetCount() == 6)
+ {
+ e->t[0] = (int)(sub->m_scalex*wcstod(params[4], NULL)*8);
+ e->t[1] = (int)(sub->m_scaley*wcstod(params[5], NULL)*8);
+ }
+ sub->m_effects[EF_ORG] = e;
+ }
+ }
+#else
+ if(params.GetCount() == 2 && !sub->m_effects[EF_ORG])
+ {
+ if(Effect* e = DNew 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;
+ }
+ }
+#endif
+ }
+ 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 = DNew Effect)
+ {
+#ifdef _VSMOD // patch m005. add some move types
+ e->param[0] = 0; // usual move
+ e->param[1] = e->param[3] = (int)(sub->m_scalex*wcstod(params[0], NULL)*8);
+ e->param[2] = e->param[4] = (int)(sub->m_scaley*wcstod(params[1], NULL)*8);
+#else
+ 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);
+#endif
+ e->t[0] = e->t[1] = 0;
+
+ sub->m_effects[EF_MOVE] = e;
+ }
+ }
+#ifdef _VSMOD // patch m002. Z-coord
+ else if(params.GetCount() == 3 && !sub->m_effects[EF_MOVE])
+ {
+ if(Effect* e = DNew 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;
+ style.mod_z = wcstod(params[2], NULL)*80;
+ }
+ }
+#endif
+ }
+ 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;
+ }
+#ifdef _VSMOD // patch m003. random text points
+ else if(cmd == L"rnds")
+ {
+ double dst = wcstol(p, NULL, 16);
+ double nx = CalcAnimation(dst, style.mod_rand.Seed, fAnimate);
+ style.mod_rand.Seed = !p.IsEmpty() ? nx : org.mod_rand.Seed;
+ }
+ else if(cmd == L"rndx")
+ {
+ double dst = wcstod(p, NULL)*8;
+ double nx = CalcAnimation(dst, style.mod_rand.X, fAnimate);
+ style.mod_rand.X = !p.IsEmpty() ? nx : org.mod_rand.X;
+ }
+ else if(cmd == L"rndy")
+ {
+ double dst = wcstod(p, NULL)*8;
+ double nx = CalcAnimation(dst, style.mod_rand.Y, fAnimate);
+ style.mod_rand.Y = !p.IsEmpty() ? nx : org.mod_rand.Y;
+ }
+ else if(cmd == L"rndz")
+ {
+ double dst = wcstod(p, NULL)*8;
+ double nx = CalcAnimation(dst, style.mod_rand.Z, fAnimate);
+ style.mod_rand.Z = !p.IsEmpty() ? nx : org.mod_rand.Z;
+ }
+ else if(cmd == L"rnd")
+ {
+ double dst = wcstod(p, NULL)*8;
+ style.mod_rand.X = !p.IsEmpty() ? CalcAnimation(dst, style.mod_rand.X, fAnimate) : org.mod_rand.X;
+ style.mod_rand.Y = !p.IsEmpty() ? CalcAnimation(dst, style.mod_rand.Y, fAnimate) : org.mod_rand.Y;
+ style.mod_rand.Z = !p.IsEmpty() ? CalcAnimation(dst, style.mod_rand.Z, fAnimate) : org.mod_rand.Z;
+ }
+#endif
+ 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([<t1>,<t2>,][<accel>,]<style modifiers>)
+ {
+ p.Empty();
+
+ m_animStart = m_animEnd = 0;
+ m_animAccel = 1;
+
+ if(params.GetCount() == 1)
+ {
+ p = params[0];
+ }
+ else if(params.GetCount() == 2)
+ {
+ m_animAccel = wcstod(params[0], NULL);
+ p = params[1];
+ }
+ else if(params.GetCount() == 3)
+ {
+ m_animStart = (int)wcstod(params[0], NULL);
+ m_animEnd = (int)wcstod(params[1], NULL);
+ p = params[2];
+ }
+ else if(params.GetCount() == 4)
+ {
+ m_animStart = wcstol(params[0], NULL, 10);
+ m_animEnd = wcstol(params[1], NULL, 10);
+ m_animAccel = wcstod(params[2], NULL);
+ p = params[3];
+ }
+
+ ParseSSATag(sub, p, style, org, true);
+
+ sub->m_fAnimated = true;
+ }
+ else if(cmd == L"u")
+ {
+ int n = wcstol(p, NULL, 10);
+ style.fUnderline = !p.IsEmpty()
+ ? (n == 0 ? false : n == 1 ? true : org.fUnderline)
+ : org.fUnderline;
+ }
+ else if(cmd == L"xbord")
+ {
+ double dst = wcstod(p, NULL);
+ double nx = CalcAnimation(dst, style.outlineWidthX, fAnimate);
+ style.outlineWidthX = !p.IsEmpty()
+ ? (nx < 0 ? 0 : nx)
+ : org.outlineWidthX;
+ }
+ else if(cmd == L"xshad")
+ {
+ double dst = wcstod(p, NULL);
+ double nx = CalcAnimation(dst, style.shadowDepthX, fAnimate);
+ style.shadowDepthX = !p.IsEmpty()
+ ? nx
+ : org.shadowDepthX;
+ }
+ else if(cmd == L"ybord")
+ {
+ double dst = wcstod(p, NULL);
+ double ny = CalcAnimation(dst, style.outlineWidthY, fAnimate);
+ style.outlineWidthY = !p.IsEmpty()
+ ? (ny < 0 ? 0 : ny)
+ : org.outlineWidthY;
+ }
+ else if(cmd == L"yshad")
+ {
+ double dst = wcstod(p, NULL);
+ double ny = CalcAnimation(dst, style.shadowDepthY, fAnimate);
+ style.shadowDepthY = !p.IsEmpty()
+ ? ny
+ : org.shadowDepthY;
+ }
+#ifdef _VSMOD // patch m002. Z-coord
+ else if(cmd == L"z")
+ {
+ double dst = wcstod(p, NULL)*80;
+ double nx = CalcAnimation(dst, style.mod_z, fAnimate);
+ style.mod_z = !p.IsEmpty() ? nx : org.mod_z;
+ }
+#endif
+ }
+
+// return(nUnrecognizedTags < nTags);
+ return(true); // there are ppl keeping coments inside {}, lets make them happy now
+}
+
+bool CRenderedTextSubtitle::ParseHtmlTag(CSubtitle* sub, CStringW str, STSStyle& style, STSStyle& org)
+{
+ if(str.Find(L"!--") == 0)
+ return(true);
+
+ bool fClosing = str[0] == '/';
+ str.Trim(L" /");
+
+ int i = str.Find(' ');
+ if(i < 0) i = str.GetLength();
+
+ CStringW tag = str.Left(i).MakeLower();
+ str = str.Mid(i).Trim();
+
+ CAtlArray<CStringW> attribs, params;
+ while((i = str.Find('=')) > 0)
+ {
+ attribs.Add(str.Left(i).Trim().MakeLower());
+ str = str.Mid(i+1);
+ for(i = 0; _istspace(str[i]); i++);
+ str = str.Mid(i);
+ if(str[0] == '\"')
+ {
+ str = str.Mid(1);
+ i = str.Find('\"');
+ }
+ else i = str.Find(' ');
+ if(i < 0) i = str.GetLength();
+ params.Add(str.Left(i).Trim().MakeLower());
+ str = str.Mid(i+1);
+ }
+
+ if(tag == L"text")
+ ;
+ else if(tag == L"b" || tag == L"strong")
+ style.fontWeight = !fClosing ? FW_BOLD : org.fontWeight;
+ else if(tag == L"i" || tag == L"em")
+ style.fItalic = !fClosing ? true : org.fItalic;
+ else if(tag == L"u")
+ style.fUnderline = !fClosing ? true : org.fUnderline;
+ else if(tag == L"s" || tag == L"strike" || tag == L"del")
+ style.fStrikeOut = !fClosing ? true : org.fStrikeOut;
+ else if(tag == L"font")
+ {
+ if(!fClosing)
+ {
+ for(i = 0; i < attribs.GetCount(); i++)
+ {
+ if(params[i].IsEmpty()) continue;
+
+ int nColor = -1;
+
+ if(attribs[i] == L"face")
+ {
+ style.fontName = params[i];
+ }
+ else if(attribs[i] == L"size")
+ {
+ if(params[i][0] == '+')
+ style.fontSize += wcstol(params[i], NULL, 10);
+ else if(params[i][0] == '-')
+ style.fontSize -= wcstol(params[i], NULL, 10);
+ else
+ style.fontSize = wcstol(params[i], NULL, 10);
+ }
+ else if(attribs[i] == L"color")
+ {
+ nColor = 0;
+ }
+ else if(attribs[i] == L"outline-color")
+ {
+ nColor = 2;
+ }
+ else if(attribs[i] == L"outline-level")
+ {
+ style.outlineWidthX = style.outlineWidthY = wcstol(params[i], NULL, 10);
+ }
+ else if(attribs[i] == L"shadow-color")
+ {
+ nColor = 3;
+ }
+ else if(attribs[i] == L"shadow-level")
+ {
+ style.shadowDepthX = style.shadowDepthY = wcstol(params[i], NULL, 10);
+ }
+
+ if(nColor >= 0 && nColor < 4)
+ {
+ CString key = WToT(params[i]).TrimLeft('#');
+ DWORD val;
+ if(g_colors.Lookup(key, val))
+ style.colors[nColor] = val;
+ else if((style.colors[nColor] = _tcstol(key, NULL, 16)) == 0)
+ style.colors[nColor] = 0x00ffffff; // default is white
+ style.colors[nColor] = ((style.colors[nColor]>>16)&0xff)|((style.colors[nColor]&0xff)<<16)|(style.colors[nColor]&0x00ff00);
+ }
+ }
+ }
+ else
+ {
+ style.fontName = org.fontName;
+ style.fontSize = org.fontSize;
+ memcpy(style.colors, org.colors, sizeof(style.colors));
+ }
+ }
+ else if(tag == L"k" && attribs.GetCount() == 1 && attribs[0] == L"t")
+ {
+ m_ktype = 1;
+ m_kstart = m_kend;
+ m_kend += wcstol(params[0], NULL, 10);
+ }
+ else
+ return(false);
+
+ return(true);
+}
+
+double CRenderedTextSubtitle::CalcAnimation(double dst, double src, bool fAnimate)
+{
+ int s = m_animStart ? m_animStart : 0;
+ int e = m_animEnd ? m_animEnd : m_delay;
+
+ if(fabs(dst-src) >= 0.0001 && fAnimate)
+ {
+ if(m_time < s) dst = src;
+ else if(s <= m_time && m_time < e)
+ {
+ double t = pow(1.0 * (m_time - s) / (e - s), m_animAccel);
+ dst = (1 - t) * src + t * dst;
+ }
+// else dst = dst;
+ }
+
+ return(dst);
+}
+
+CSubtitle* CRenderedTextSubtitle::GetSubtitle(int entry)
+{
+ CSubtitle* sub;
+ if(m_subtitleCache.Lookup(entry, sub))
+ {
+ if(sub->m_fAnimated)
+ {
+ delete sub;
+ sub = NULL;
+ }
+ else return(sub);
+ }
+
+ sub = DNew CSubtitle();
+ if(!sub) return(NULL);
+
+ CStringW str = GetStrW(entry, true);
+
+ STSStyle stss, orgstss;
+ if(m_doOverrideStyle && m_pStyleOverride != NULL)
+ {
+ // this RTS has been signaled to ignore embedded styles, use the built-in one
+ stss = *m_pStyleOverride;
+ }
+ else
+ {
+ // find the appropriate embedded style
+ GetStyle(entry, stss);
+ }
+ if (m_ePARCompensationType == EPCTUpscale)
+ {
+ if (stss.fontScaleX / stss.fontScaleY == 1.0 && m_dPARCompensation != 1.0)
+ {
+ if (m_dPARCompensation < 1.0)
+ stss.fontScaleY /= m_dPARCompensation;
+ else
+ stss.fontScaleX *= m_dPARCompensation;
+ }
+ }
+ else if (m_ePARCompensationType == EPCTDownscale)
+ {
+ if (stss.fontScaleX / stss.fontScaleY == 1.0 && m_dPARCompensation != 1.0)
+ {
+ if (m_dPARCompensation < 1.0)
+ stss.fontScaleX *= m_dPARCompensation;
+ else
+ stss.fontScaleY /= m_dPARCompensation;
+ }
+ }
+ else if (m_ePARCompensationType == EPCTAccurateSize)
+ {
+ if (stss.fontScaleX / stss.fontScaleY == 1.0 && m_dPARCompensation != 1.0)
+ {
+ stss.fontScaleX *= m_dPARCompensation;
+ }
+ }
+
+ orgstss = stss;
+
+ sub->m_clip.SetRect(0, 0, m_size.cx>>3, m_size.cy>>3);
+ sub->m_scrAlignment = -stss.scrAlignment;
+ sub->m_wrapStyle = m_defaultWrapStyle;
+ sub->m_fAnimated = false;
+ sub->m_relativeTo = stss.relativeTo;
+ // this whole conditional is a work-around for what happens in STS.cpp:
+ // in CSimpleTextSubtitle::Open, we have m_dstScreenSize = CSize(384, 288)
+ // now, files containing embedded subtitles (and with styles) set m_dstScreenSize to a correct value
+ // but where no style is given, those defaults are taken - 384, 288
+ if(m_doOverrideStyle && m_pStyleOverride != NULL)
+ {
+ // so mind the default values, stated here to increase comprehension
+ sub->m_scalex = (stss.relativeTo == 1 ? m_vidrect.Width() : m_size.cx) / (384 * 8);
+ sub->m_scaley = (stss.relativeTo == 1 ? m_vidrect.Height() : m_size.cy) / (288 * 8);
+ }
+ else
+ {
+ sub->m_scalex = m_dstScreenSize.cx > 0 ? 1.0 * (stss.relativeTo == 1 ? m_vidrect.Width() : m_size.cx) / (m_dstScreenSize.cx*8) : 1.0;
+ sub->m_scaley = m_dstScreenSize.cy > 0 ? 1.0 * (stss.relativeTo == 1 ? m_vidrect.Height() : m_size.cy) / (m_dstScreenSize.cy*8) : 1.0;
+ }
+
+ m_animStart = m_animEnd = 0;
+ m_animAccel = 1;
+ m_ktype = m_kstart = m_kend = 0;
+ m_nPolygon = 0;
+ m_polygonBaselineOffset = 0;
+#ifdef _VSMOD // patch m004. gradient colors
+ // allow init gradient without \$vc \$va
+ for (int i=0;i<4;i++)
+ for (int j=0;j<4;j++)
+ {
+ stss.mod_grad.alpha[i][j] = stss.alpha[i];
+ stss.mod_grad.color[i][j] = stss.colors[i];
+ }
+#endif
+ ParseEffect(sub, GetAt(entry).effect);
+
+ while(!str.IsEmpty())
+ {
+ bool fParsed = false;
+
+ int i;
+
+ if(str[0] == '{' && (i = str.Find(L'}')) > 0)
+ {
+ fParsed = ParseSSATag(sub, str.Mid(1, i-1), stss, orgstss);
+ if(fParsed)
+ str = str.Mid(i+1);
+ }
+ else if(str[0] == '<' && (i = str.Find(L'>')) > 0)
+ {
+ fParsed = ParseHtmlTag(sub, str.Mid(1, i-1), stss, orgstss);
+ if(fParsed)
+ str = str.Mid(i+1);
+ }
+
+ if(fParsed)
+ {
+ i = str.FindOneOf(L"{<");
+ if(i < 0) i = str.GetLength();
+ if(i == 0) continue;
+ }
+ else
+ {
+ i = str.Mid(1).FindOneOf(L"{<");
+ if(i < 0) i = str.GetLength()-1;
+ i++;
+ }
+
+ STSStyle tmp;
+
+ tmp = stss;
+
+ tmp.fontSize = sub->m_scaley*tmp.fontSize*64;
+ tmp.fontSpacing = sub->m_scalex*tmp.fontSpacing*64;
+ tmp.outlineWidthX *= (m_fScaledBAS ? sub->m_scalex : 1) * 8;
+ tmp.outlineWidthY *= (m_fScaledBAS ? sub->m_scaley : 1) * 8;
+ tmp.shadowDepthX *= (m_fScaledBAS ? sub->m_scalex : 1) * 8;
+ tmp.shadowDepthY *= (m_fScaledBAS ? sub->m_scaley : 1) * 8;
+
+ if(m_nPolygon)
+ {
+ ParsePolygon(sub, str.Left(i), tmp);
+ }
+ else
+ {
+ ParseString(sub, str.Left(i), tmp);
+ }
+
+ str = str.Mid(i);
+ }
+
+ // just a "work-around" solution... in most cases nobody will want to use \org together with moving but without rotating the subs
+ if(sub->m_effects[EF_ORG] && (sub->m_effects[EF_MOVE] || sub->m_effects[EF_BANNER] || sub->m_effects[EF_SCROLL]))
+ sub->m_fAnimated = true;
+
+ sub->m_scrAlignment = abs(sub->m_scrAlignment);
+
+ STSEntry stse = GetAt(entry);
+ CRect marginRect = stse.marginRect;
+ if(marginRect.left == 0) marginRect.left = orgstss.marginRect.left;
+ if(marginRect.top == 0) marginRect.top = orgstss.marginRect.top;
+ if(marginRect.right == 0) marginRect.right = orgstss.marginRect.right;
+ if(marginRect.bottom == 0) marginRect.bottom = orgstss.marginRect.bottom;
+ marginRect.left = (int)(sub->m_scalex*marginRect.left*8);
+ marginRect.top = (int)(sub->m_scaley*marginRect.top*8);
+ marginRect.right = (int)(sub->m_scalex*marginRect.right*8);
+ marginRect.bottom = (int)(sub->m_scaley*marginRect.bottom*8);
+
+ if(stss.relativeTo == 1)
+ {
+ marginRect.left += m_vidrect.left;
+ marginRect.top += m_vidrect.top;
+ marginRect.right += m_size.cx - m_vidrect.right;
+ marginRect.bottom += m_size.cy - m_vidrect.bottom;
+ }
+
+ sub->CreateClippers(m_size);
+
+ sub->MakeLines(m_size, marginRect);
+
+ m_subtitleCache[entry] = sub;
+
+ return(sub);
+}
+
+//
+
+STDMETHODIMP CRenderedTextSubtitle::NonDelegatingQueryInterface(REFIID riid, void** ppv)
+{
+ CheckPointer(ppv, E_POINTER);
+ *ppv = NULL;
+
+ return
+ QI(IPersist)
+ QI(ISubStream)
+ QI(ISubPicProvider)
+ __super::NonDelegatingQueryInterface(riid, ppv);
+}
+
+// ISubPicProvider
+
+STDMETHODIMP_(POSITION) CRenderedTextSubtitle::GetStartPosition(REFERENCE_TIME rt, double fps)
+{
+ int iSegment = -1;
+ SearchSubs((int)(rt/10000), fps, &iSegment, NULL);
+
+ if(iSegment < 0) iSegment = 0;
+
+ return(GetNext((POSITION)iSegment));
+}
+
+STDMETHODIMP_(POSITION) CRenderedTextSubtitle::GetNext(POSITION pos)
+{
+ int iSegment = (int)pos;
+
+ const STSSegment* stss = GetSegment(iSegment);
+ while(stss && stss->subs.GetCount() == 0)
+ {
+ iSegment++;
+ stss = GetSegment(iSegment);
+ }
+
+ return(stss ? (POSITION)(iSegment+1) : NULL);
+}
+
+STDMETHODIMP_(REFERENCE_TIME) CRenderedTextSubtitle::GetStart(POSITION pos, double fps)
+{
+ return(10000i64 * TranslateSegmentStart((int)pos-1, fps));
+}
+
+STDMETHODIMP_(REFERENCE_TIME) CRenderedTextSubtitle::GetStop(POSITION pos, double fps)
+{
+ return(10000i64 * TranslateSegmentEnd((int)pos-1, fps));
+}
+
+STDMETHODIMP_(bool) CRenderedTextSubtitle::IsAnimated(POSITION pos)
+{
+ return(true);
+}
+
+struct LSub
+{
+ int idx, layer, readorder;
+};
+
+static int lscomp(const void* ls1, const void* ls2)
+{
+ int ret = ((LSub*)ls1)->layer - ((LSub*)ls2)->layer;
+ if(!ret) ret = ((LSub*)ls1)->readorder - ((LSub*)ls2)->readorder;
+ return(ret);
+}
+
+STDMETHODIMP CRenderedTextSubtitle::Render(SubPicDesc& spd, REFERENCE_TIME rt, double fps, RECT& bbox)
+{
+ CRect bbox2(0,0,0,0);
+
+ if(m_size != CSize(spd.w*8, spd.h*8) || m_vidrect != CRect(spd.vidrect.left*8, spd.vidrect.top*8, spd.vidrect.right*8, spd.vidrect.bottom*8))
+ Init(CSize(spd.w, spd.h), spd.vidrect);
+
+ int t = (int)(rt / 10000);
+
+ int segment;
+ const STSSegment* stss = SearchSubs(t, fps, &segment);
+ if(!stss) return S_FALSE;
+
+ // clear any cached subs not in the range of +/-30secs measured from the segment's bounds
+ {
+ POSITION pos = m_subtitleCache.GetStartPosition();
+ while(pos)
+ {
+ int key;
+ CSubtitle* value;
+ m_subtitleCache.GetNextAssoc(pos, key, value);
+
+ STSEntry& stse = GetAt(key);
+ if(stse.end <= (t-30000) || stse.start > (t+30000))
+ {
+ delete value;
+ m_subtitleCache.RemoveKey(key);
+ pos = m_subtitleCache.GetStartPosition();
+ }
+ }
+ }
+
+ m_sla.AdvanceToSegment(segment, stss->subs);
+
+ CAtlArray<LSub> subs;
+
+ for(ptrdiff_t i = 0, j = stss->subs.GetCount(); i < j; i++)
+ {
+ LSub ls;
+ ls.idx = stss->subs[i];
+ ls.layer = GetAt(stss->subs[i]).layer;
+ ls.readorder = GetAt(stss->subs[i]).readorder;
+ subs.Add(ls);
+ }
+
+ qsort(subs.GetData(), subs.GetCount(), sizeof(LSub), lscomp);
+
+ for(ptrdiff_t i = 0, j = subs.GetCount(); i < j; i++)
+ {
+ int entry = subs[i].idx;
+
+ STSEntry stse = GetAt(entry);
+
+ {
+ int start = TranslateStart(entry, fps);
+ m_time = t - start;
+ m_delay = TranslateEnd(entry, fps) - start;
+ }
+
+ CSubtitle* s = GetSubtitle(entry);
+ if(!s) continue;
+
+ CRect clipRect = s->m_clip;
+ CRect r = s->m_rect;
+ CSize spaceNeeded = r.Size();
+
+ // apply the effects
+
+ bool fPosOverride = false, fOrgOverride = false;
+
+ int alpha = 0x00;
+
+ CPoint org2;
+
+ BYTE* pAlphaMask = s->m_pClipper?s->m_pClipper->m_pAlphaMask:NULL;
+
+#ifdef _VSMOD // patch m006. moveable vector clip
+ MOD_MOVEVC mod_vc;
+ mod_vc.spd = CSize(spd.w,spd.h);
+ //mod_vc.alphamask = pAlphaMask;
+ mod_vc.size = s->m_pClipper?s->m_pClipper->m_size : CSize(0,0);
+#endif
+
+ for(int k = 0; k < EF_NUMBEROFEFFECTS; k++)
+ {
+ if(!s->m_effects[k]) continue;
+
+ switch(k)
+ {
+ case EF_MOVE: // {\move(x1=param[0], y1=param[1], x2=param[2], y2=param[3], t1=t[0], t2=t[1])}
+ {
+ CPoint p;
+#ifdef _VSMOD // patch m005. add some move types
+ if (s->m_effects[k]->param[0]==0)
+ {
+ CPoint p1(s->m_effects[k]->param[1], s->m_effects[k]->param[2]);
+ CPoint p2(s->m_effects[k]->param[3], s->m_effects[k]->param[4]);
+#else
+ CPoint p1(s->m_effects[k]->param[0], s->m_effects[k]->param[1]);
+ CPoint p2(s->m_effects[k]->param[2], s->m_effects[k]->param[3]);
+#endif
+ int t1 = s->m_effects[k]->t[0];
+ int t2 = s->m_effects[k]->t[1];
+
+ if(t2 < t1)
+ {
+ int t = t1;
+ t1 = t2;
+ t2 = t;
+ }
+
+ if(t1 <= 0 && t2 <= 0)
+ {
+ t1 = 0;
+ t2 = m_delay;
+ }
+
+ if(m_time <= t1) p = p1;
+ else if (p1 == p2) p = p1;
+ else if(t1 < m_time && m_time < t2)
+ {
+ double t = 1.0*(m_time-t1)/(t2-t1);
+ p.x = (int)((1-t)*p1.x + t*p2.x);
+ p.y = (int)((1-t)*p1.y + t*p2.y);
+ }
+ else p = p2;
+#ifdef _VSMOD // patch m005. add some move types
+ }
+ else if (s->m_effects[k]->param[0]==1) // radial move
+ {
+ CPoint p1(s->m_effects[k]->param[1], s->m_effects[k]->param[2]); // p0
+ CPoint p2(s->m_effects[k]->param[3], s->m_effects[k]->param[4]); // p1
+ CPoint r0(s->m_effects[k]->param[7], s->m_effects[k]->param[8]);
+ double alp1 = s->m_effects[k]->param[5]*PI/1800000; // alp1
+ double alp2 = s->m_effects[k]->param[6]*PI/1800000; // alp2
+ int t1 = s->m_effects[k]->t[0];
+ int t2 = s->m_effects[k]->t[1];
+
+ CPoint pr1 = (p1.x + cos(alp1)*r0.x, p1.y + sin(alp1)*r0.x);
+ CPoint pr2 = (p2.x + cos(alp2)*r0.y, p2.y + sin(alp2)*r0.y);
+
+ if(t2 < t1)
+ {
+ int t = t1;
+ t1 = t2;
+ t2 = t;
+ }
+
+ if(t1 <= 0 && t2 <= 0)
+ {
+ t1 = 0;
+ t2 = m_delay;
+ }
+
+ if(m_time <= t1) p = pr1;
+ //else if (p1 == p2) p = pr1; // jfs: avoid rounding error problems sometimes causing subtitles with \pos to jump around a bit
+ else if((t1 < m_time) && (m_time < t2))
+ {
+ double t = (double)(m_time-t1)/(t2-t1);
+ double alp = ((1-t)*alp1 + t*alp2);
+ double rt = ((1-t)*r0.x + t*r0.y);
+
+ p.x = (int)((1-t)*p1.x + t*p2.x); // origin point
+ p.y = (int)((1-t)*p1.y + t*p2.y);
+ p.x += (int)(cos(alp)*rt);
+ p.y -= (int)(sin(alp)*rt);
+ }
+ else p = pr2;
+ }
+ else if (s->m_effects[k]->param[0]==2) // square spline
+ {
+ CPoint p1(s->m_effects[k]->param[1], s->m_effects[k]->param[2]);
+ CPoint p2(s->m_effects[k]->param[3], s->m_effects[k]->param[4]);
+ CPoint p3(s->m_effects[k]->param[5], s->m_effects[k]->param[6]);
+
+ int t1 = s->m_effects[k]->t[0];
+ int t2 = s->m_effects[k]->t[1];
+
+ if(t2 < t1)
+ {
+ int t = t1;
+ t1 = t2;
+ t2 = t;
+ }
+
+ if(t1 <= 0 && t2 <= 0)
+ {
+ t1 = 0;
+ t2 = m_delay;
+ }
+
+ if(m_time <= t1) p = p1;
+ else if (p1 == p2) p = p1; // jfs: avoid rounding error problems sometimes causing subtitles with \pos to jump around a bit
+ else if(t1 < m_time && m_time < t2)
+ {
+ double t = (double)(m_time-t1)/(t2-t1);
+ p.x = (int)((1-t)*(1-t)*p1.x + 2*t*(1-t)*p2.x + t*t*p3.x);
+ p.y = (int)((1-t)*(1-t)*p1.y + 2*t*(1-t)*p2.y + t*t*p3.y);
+ }
+ else p = p3;
+ }
+ else if (s->m_effects[k]->param[0]==3) // cubic spline
+ {
+ CPoint p1(s->m_effects[k]->param[1], s->m_effects[k]->param[2]);
+ CPoint p2(s->m_effects[k]->param[3], s->m_effects[k]->param[4]);
+ CPoint p3(s->m_effects[k]->param[5], s->m_effects[k]->param[6]);
+ CPoint p4(s->m_effects[k]->param[7], s->m_effects[k]->param[8]);
+
+ int t1 = s->m_effects[k]->t[0];
+ int t2 = s->m_effects[k]->t[1];
+
+ if(t2 < t1)
+ {
+ int t = t1;
+ t1 = t2;
+ t2 = t;
+ }
+
+ if(t1 <= 0 && t2 <= 0)
+ {
+ t1 = 0;
+ t2 = m_delay;
+ }
+
+ if(m_time <= t1) p = p1;
+ else if (p1 == p2) p = p1; // jfs: avoid rounding error problems sometimes causing subtitles with \pos to jump around a bit
+ else if(t1 < m_time && m_time < t2)
+ {
+ double t = (double)(m_time-t1)/(t2-t1);
+ p.x = (int)((1-t)*(1-t)*(1-t)*p1.x + 3*t*(1-t)*(1-t)*p2.x + 3*t*t*(1-t)*p3.x + t*t*t*p4.x);
+ p.y = (int)((1-t)*(1-t)*(1-t)*p1.y + 3*t*(1-t)*(1-t)*p2.y + 3*t*t*(1-t)*p3.y + t*t*t*p4.y);
+ }
+ else p = p4;
+ }
+#endif
+ r = CRect(
+ CPoint((s->m_scrAlignment%3) == 1 ? p.x : (s->m_scrAlignment%3) == 0 ? p.x - spaceNeeded.cx : p.x - (spaceNeeded.cx+1)/2,
+ s->m_scrAlignment <= 3 ? p.y - spaceNeeded.cy : s->m_scrAlignment <= 6 ? p.y - (spaceNeeded.cy+1)/2 : p.y),
+ spaceNeeded);
+
+ if(s->m_relativeTo == 1)
+ r.OffsetRect(m_vidrect.TopLeft());
+ fPosOverride = true;
+ }
+ break;
+ case EF_ORG: // {\org(x=param[0], y=param[1])}
+ {
+#ifdef _VSMOD // patch f003. moving \org for some karaoke effects
+ CPoint orgA(s->m_effects[k]->param[0], s->m_effects[k]->param[1]);
+ CPoint orgB(s->m_effects[k]->param[2], s->m_effects[k]->param[3]);
+ int to1 = s->m_effects[k]->t[0];
+ int to2 = s->m_effects[k]->t[1];
+
+ if(to2 < to1)
+ {
+ int to = to1;
+ to1 = to2;
+ to2 = to;
+ }
+
+ if(to1 <= 0 && to2 <= 0)
+ {
+ to1 = 0;
+ to2 = m_delay;
+ }
+
+ if(m_time <= to1) org2 = orgA;
+ else if (to1 == to2) org2 = orgA; // jfs: avoid rounding error problems sometimes causing subtitles with \pos to jump around a bit
+ else if(to1 < m_time && m_time < to2)
+ {
+ double t = 1.0*(m_time-to1)/(to2-to1);
+ org2.x = (int)((1-t)*orgA.x + t*orgB.x);
+ org2.y = (int)((1-t)*orgA.y + t*orgB.y);
+ }
+ else org2 = orgB;
+#else
+ org2 = CPoint(s->m_effects[k]->param[0], s->m_effects[k]->param[1]);
+#endif
+ fOrgOverride = true;
+ }
+ break;
+ case EF_FADE: // {\fade(a1=param[0], a2=param[1], a3=param[2], t1=t[0], t2=t[1], t3=t[2], t4=t[3]) or {\fad(t1=t[1], t2=t[2])
+ {
+ int t1 = s->m_effects[k]->t[0];
+ int t2 = s->m_effects[k]->t[1];
+ int t3 = s->m_effects[k]->t[2];
+ int t4 = s->m_effects[k]->t[3];
+
+ if(t1 == -1 && t4 == -1)
+ {
+ t1 = 0;
+ t3 = m_delay - t3;
+ t4 = m_delay;
+ }
+
+ if(m_time < t1) alpha = s->m_effects[k]->param[0];
+ else if(m_time >= t1 && m_time < t2)
+ {
+ double t = 1.0 * (m_time - t1) / (t2 - t1);
+ alpha = (int)(s->m_effects[k]->param[0]*(1-t) + s->m_effects[k]->param[1]*t);
+ }
+ else if(m_time >= t2 && m_time < t3) alpha = s->m_effects[k]->param[1];
+ else if(m_time >= t3 && m_time < t4)
+ {
+ double t = 1.0 * (m_time - t3) / (t4 - t3);
+ alpha = (int)(s->m_effects[k]->param[1]*(1-t) + s->m_effects[k]->param[2]*t);
+ }
+ else if(m_time >= t4) alpha = s->m_effects[k]->param[2];
+ }
+ break;
+ case EF_BANNER: // Banner;delay=param[0][;leftoright=param[1];fadeawaywidth=param[2]]
+ {
+ int left = s->m_relativeTo == 1 ? m_vidrect.left : 0,
+ right = s->m_relativeTo == 1 ? m_vidrect.right : m_size.cx;
+
+ r.left = !!s->m_effects[k]->param[1]
+ ? (left/*marginRect.left*/ - spaceNeeded.cx) + (int)(m_time*8.0/s->m_effects[k]->param[0])
+ : (right /*- marginRect.right*/) - (int)(m_time*8.0/s->m_effects[k]->param[0]);
+
+ r.right = r.left + spaceNeeded.cx;
+
+ clipRect &= CRect(left>>3, clipRect.top, right>>3, clipRect.bottom);
+
+ fPosOverride = true;
+ }
+ break;
+ case EF_SCROLL: // Scroll up/down(toptobottom=param[3]);top=param[0];bottom=param[1];delay=param[2][;fadeawayheight=param[4]]
+ {
+ r.top = !!s->m_effects[k]->param[3]
+ ? s->m_effects[k]->param[0] + (int)(m_time*8.0/s->m_effects[k]->param[2]) - spaceNeeded.cy
+ : s->m_effects[k]->param[1] - (int)(m_time*8.0/s->m_effects[k]->param[2]);
+
+ r.bottom = r.top + spaceNeeded.cy;
+
+ CRect cr(0, (s->m_effects[k]->param[0] + 4) >> 3, spd.w, (s->m_effects[k]->param[1] + 4) >> 3);
+
+ if(s->m_relativeTo == 1)
+ r.top += m_vidrect.top,
+ r.bottom += m_vidrect.top,
+ cr.top += m_vidrect.top>>3,
+ cr.bottom += m_vidrect.top>>3;
+
+ clipRect &= cr;
+
+ fPosOverride = true;
+ }
+#ifdef _VSMOD // patch m006. moveable vector clip
+ case EF_VECTCLP:
+ {
+ CPoint vcpos1(s->m_effects[k]->param[0], s->m_effects[k]->param[1]);
+ CPoint vcpos2(s->m_effects[k]->param[2], s->m_effects[k]->param[3]);
+ int to1 = s->m_effects[k]->t[0];
+ int to2 = s->m_effects[k]->t[1];
+
+ if(to2 < to1)
+ {
+ int to = to1;
+ to1 = to2;
+ to2 = to;
+ }
+
+ if(to1 <= 0 && to2 <= 0)
+ {
+ to1 = 0;
+ to2 = m_delay;
+ }
+
+ if(m_time <= to1) mod_vc.pos = vcpos1;
+ else if (to1 == to2) mod_vc.pos = vcpos1; // jfs: avoid rounding error problems sometimes causing subtitles with \pos to jump around a bit
+ else if(to1 < m_time && m_time < to2)
+ {
+ double t = 1.0*(m_time-to1)/(to2-to1);
+ mod_vc.pos.x = (int)((1-t)*vcpos1.x + t*vcpos2.x);
+ mod_vc.pos.y = (int)((1-t)*vcpos1.y + t*vcpos2.y);
+ }
+ else mod_vc.pos = vcpos2;
+ mod_vc.enable = true;
+ }
+#endif
+ break;
+ default:
+ break;
+ }
+ }
+
+ if(!fPosOverride && !fOrgOverride && !s->m_fAnimated)
+ r = m_sla.AllocRect(s, segment, entry, stse.layer, m_collisions);
+
+ CPoint org;
+ org.x = (s->m_scrAlignment%3) == 1 ? r.left : (s->m_scrAlignment%3) == 2 ? r.CenterPoint().x : r.right;
+ org.y = s->m_scrAlignment <= 3 ? r.bottom : s->m_scrAlignment <= 6 ? r.CenterPoint().y : r.top;
+
+ if(!fOrgOverride) org2 = org;
+
+ CPoint p, p2(0, r.top);
+
+ POSITION pos;
+
+ p = p2;
+
+ // Rectangles for inverse clip
+ CRect iclipRect[4];
+ iclipRect[0] = CRect(0, 0, spd.w, clipRect.top);
+ iclipRect[1] = CRect(0, clipRect.top, clipRect.left, clipRect.bottom);
+ iclipRect[2] = CRect(clipRect.right, clipRect.top, spd.w, clipRect.bottom);
+ iclipRect[3] = CRect(0, clipRect.bottom, spd.w, spd.h);
+
+ pos = s->GetHeadPosition();
+ while(pos)
+ {
+ CLine* l = s->GetNext(pos);
+
+ p.x = (s->m_scrAlignment%3) == 1 ? org.x
+ : (s->m_scrAlignment%3) == 0 ? org.x - l->m_width
+ : org.x - (l->m_width/2);
+
+#ifdef _VSMOD // patch m006. moveable vector clip
+ if (s->m_clipInverse)
+ {
+ bbox2 |= l->PaintShadow(spd, iclipRect[0], pAlphaMask, p, org2, m_time, alpha, mod_vc, rt);
+ bbox2 |= l->PaintShadow(spd, iclipRect[1], pAlphaMask, p, org2, m_time, alpha, mod_vc, rt);
+ bbox2 |= l->PaintShadow(spd, iclipRect[2], pAlphaMask, p, org2, m_time, alpha, mod_vc, rt);
+ bbox2 |= l->PaintShadow(spd, iclipRect[3], pAlphaMask, p, org2, m_time, alpha, mod_vc, rt);
+ }
+ else
+ {
+ bbox2 |= l->PaintShadow(spd, clipRect, pAlphaMask, p, org2, m_time, alpha, mod_vc, rt);
+ }
+#else
+ if (s->m_clipInverse)
+ {
+ bbox2 |= l->PaintShadow(spd, iclipRect[0], pAlphaMask, p, org2, m_time, alpha);
+ bbox2 |= l->PaintShadow(spd, iclipRect[1], pAlphaMask, p, org2, m_time, alpha);
+ bbox2 |= l->PaintShadow(spd, iclipRect[2], pAlphaMask, p, org2, m_time, alpha);
+ bbox2 |= l->PaintShadow(spd, iclipRect[3], pAlphaMask, p, org2, m_time, alpha);
+ }
+ else
+ {
+ bbox2 |= l->PaintShadow(spd, clipRect, pAlphaMask, p, org2, m_time, alpha);
+ }
+#endif
+ p.y += l->m_ascent + l->m_descent;
+ }
+
+ p = p2;
+
+ pos = s->GetHeadPosition();
+ while(pos)
+ {
+ CLine* l = s->GetNext(pos);
+
+ p.x = (s->m_scrAlignment%3) == 1 ? org.x
+ : (s->m_scrAlignment%3) == 0 ? org.x - l->m_width
+ : org.x - (l->m_width/2);
+
+#ifdef _VSMOD // patch m006. movable vector clip
+ if (s->m_clipInverse)
+ {
+ bbox2 |= l->PaintOutline(spd, iclipRect[0], pAlphaMask, p, org2, m_time, alpha, mod_vc, rt);
+ bbox2 |= l->PaintOutline(spd, iclipRect[1], pAlphaMask, p, org2, m_time, alpha, mod_vc, rt);
+ bbox2 |= l->PaintOutline(spd, iclipRect[2], pAlphaMask, p, org2, m_time, alpha, mod_vc, rt);
+ bbox2 |= l->PaintOutline(spd, iclipRect[3], pAlphaMask, p, org2, m_time, alpha, mod_vc, rt);
+ }
+ else
+ {
+ bbox2 |= l->PaintOutline(spd, clipRect, pAlphaMask, p, org2, m_time, alpha, mod_vc, rt);
+ }
+#else
+ if (s->m_clipInverse)
+ {
+ bbox2 |= l->PaintOutline(spd, iclipRect[0], pAlphaMask, p, org2, m_time, alpha);
+ bbox2 |= l->PaintOutline(spd, iclipRect[1], pAlphaMask, p, org2, m_time, alpha);
+ bbox2 |= l->PaintOutline(spd, iclipRect[2], pAlphaMask, p, org2, m_time, alpha);
+ bbox2 |= l->PaintOutline(spd, iclipRect[3], pAlphaMask, p, org2, m_time, alpha);
+ }
+ else
+ {
+ bbox2 |= l->PaintOutline(spd, clipRect, pAlphaMask, p, org2, m_time, alpha);
+ }
+#endif
+ p.y += l->m_ascent + l->m_descent;
+ }
+
+ p = p2;
+
+ pos = s->GetHeadPosition();
+ while(pos)
+ {
+ CLine* l = s->GetNext(pos);
+
+ p.x = (s->m_scrAlignment%3) == 1 ? org.x
+ : (s->m_scrAlignment%3) == 0 ? org.x - l->m_width
+ : org.x - (l->m_width/2);
+
+#ifdef _VSMOD // patch m006. moveable vector clip
+ if (s->m_clipInverse)
+ {
+ bbox2 |= l->PaintBody(spd, iclipRect[0], pAlphaMask, p, org2, m_time, alpha, mod_vc, rt);
+ bbox2 |= l->PaintBody(spd, iclipRect[1], pAlphaMask, p, org2, m_time, alpha, mod_vc, rt);
+ bbox2 |= l->PaintBody(spd, iclipRect[2], pAlphaMask, p, org2, m_time, alpha, mod_vc, rt);
+ bbox2 |= l->PaintBody(spd, iclipRect[3], pAlphaMask, p, org2, m_time, alpha, mod_vc, rt);
+ }
+ else
+ {
+ bbox2 |= l->PaintBody(spd, clipRect, pAlphaMask, p, org2, m_time, alpha, mod_vc, rt);
+ }
+#else
+ if (s->m_clipInverse)
+ {
+ bbox2 |= l->PaintBody(spd, iclipRect[0], pAlphaMask, p, org2, m_time, alpha);
+ bbox2 |= l->PaintBody(spd, iclipRect[1], pAlphaMask, p, org2, m_time, alpha);
+ bbox2 |= l->PaintBody(spd, iclipRect[2], pAlphaMask, p, org2, m_time, alpha);
+ bbox2 |= l->PaintBody(spd, iclipRect[3], pAlphaMask, p, org2, m_time, alpha);
+ }
+ else
+ {
+ bbox2 |= l->PaintBody(spd, clipRect, pAlphaMask, p, org2, m_time, alpha);
+ }
+#endif
+ p.y += l->m_ascent + l->m_descent;
+ }
+ }
+
+ bbox = bbox2;
+
+ return (subs.GetCount() && !bbox2.IsRectEmpty()) ? S_OK : S_FALSE;
+}
+
+// IPersist
+
+STDMETHODIMP CRenderedTextSubtitle::GetClassID(CLSID* pClassID)
+{
+ return pClassID ? *pClassID = __uuidof(this), S_OK : E_POINTER;
+}
+
+// ISubStream
+
+STDMETHODIMP_(int) CRenderedTextSubtitle::GetStreamCount()
+{
+ return(1);
+}
+
+STDMETHODIMP CRenderedTextSubtitle::GetStreamInfo(int iStream, WCHAR** ppName, LCID* pLCID)
+{
+ USES_CONVERSION;
+ if(iStream != 0) return E_INVALIDARG;
+
+ if(ppName)
+ {
+ *ppName = (WCHAR*)CoTaskMemAlloc((m_name.GetLength()+1)*sizeof(WCHAR));
+ if(!(*ppName))
+ return E_OUTOFMEMORY;
+
+ wcscpy(*ppName, CStringW(m_name));
+
+ if(pLCID)
+ {
+ *pLCID = ISO6391ToLcid (W2A(*ppName));
+ if (*pLCID == 0)
+ *pLCID = ISO6392ToLcid (W2A(*ppName));
+ }
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP_(int) CRenderedTextSubtitle::GetStream()
+{
+ return(0);
+}
+
+STDMETHODIMP CRenderedTextSubtitle::SetStream(int iStream)
+{
+ return iStream == 0 ? S_OK : E_FAIL;
+}
+
+STDMETHODIMP CRenderedTextSubtitle::Reload()
+{
+ CFileStatus s;
+ if(!CFile::GetStatus(m_path, s)) return E_FAIL;
+ return !m_path.IsEmpty() && Open(m_path, DEFAULT_CHARSET) ? S_OK : E_FAIL;
+} \ No newline at end of file
diff --git a/src/Subtitles/RTS.h b/src/Subtitles/RTS.h
new file mode 100644
index 000000000..020e1dcc2
--- /dev/null
+++ b/src/Subtitles/RTS.h
@@ -0,0 +1,293 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include "STS.h"
+#include "Rasterizer.h"
+#include "../SubPic/SubPicProviderImpl.h"
+
+class CMyFont : public CFont
+{
+public:
+ int m_ascent, m_descent;
+
+ CMyFont(STSStyle& style);
+};
+
+class CPolygon;
+
+class CWord : public Rasterizer
+{
+ bool m_fDrawn;
+ CPoint m_p;
+
+ void Transform(CPoint org);
+
+ void Transform_C( CPoint &org );
+ void Transform_SSE2( CPoint &org );
+ bool CreateOpaqueBox();
+
+protected:
+ CStringW m_str;
+
+ virtual bool CreatePath() = 0;
+
+public:
+ bool m_fWhiteSpaceChar, m_fLineBreak;
+
+ STSStyle m_style;
+
+ CPolygon* m_pOpaqueBox;
+
+ int m_ktype, m_kstart, m_kend;
+
+ int m_width, m_ascent, m_descent;
+
+ CWord(STSStyle& style, CStringW str, int ktype, int kstart, int kend); // str[0] = 0 -> m_fLineBreak = true (in this case we only need and use the height of m_font from the whole class)
+ virtual ~CWord();
+
+ virtual CWord* Copy() = 0;
+ virtual bool Append(CWord* w);
+
+ void Paint(CPoint p, CPoint org);
+};
+
+class CText : public CWord
+{
+protected:
+ virtual bool CreatePath();
+
+public:
+ CText(STSStyle& style, CStringW str, int ktype, int kstart, int kend);
+
+ virtual CWord* Copy();
+ virtual bool Append(CWord* w);
+};
+
+class CPolygon : public CWord
+{
+ bool GetLONG(CStringW& str, LONG& ret);
+ bool GetPOINT(CStringW& str, POINT& ret);
+ bool ParseStr();
+
+protected:
+ double m_scalex, m_scaley;
+ int m_baseline;
+
+ CAtlArray<BYTE> m_pathTypesOrg;
+ CAtlArray<CPoint> m_pathPointsOrg;
+
+ virtual bool CreatePath();
+
+public:
+ CPolygon(STSStyle& style, CStringW str, int ktype, int kstart, int kend, double scalex, double scaley, int baseline);
+ virtual ~CPolygon();
+
+ virtual CWord* Copy();
+ virtual bool Append(CWord* w);
+};
+
+class CClipper : public CPolygon
+{
+private:
+ CWord* Copy();
+ virtual bool Append(CWord* w);
+
+public:
+ CClipper(CStringW str, CSize size, double scalex, double scaley, bool inverse);
+ virtual ~CClipper();
+
+ CSize m_size;
+ bool m_inverse;
+ BYTE* m_pAlphaMask;
+};
+
+class CLine : public CAtlList<CWord*>
+{
+public:
+ int m_width, m_ascent, m_descent, m_borderX, m_borderY;
+
+ virtual ~CLine();
+
+ void Compact();
+
+#ifdef _VSMOD // patch m006. moveable vector clip
+ CRect PaintShadow(SubPicDesc& spd, CRect& clipRect, BYTE* pAlphaMask, CPoint p, CPoint org, int time, int alpha, MOD_MOVEVC& mod_vc, REFERENCE_TIME rt);
+ CRect PaintOutline(SubPicDesc& spd, CRect& clipRect, BYTE* pAlphaMask, CPoint p, CPoint org, int time, int alpha, MOD_MOVEVC& mod_vc, REFERENCE_TIME rt);
+ CRect PaintBody(SubPicDesc& spd, CRect& clipRect, BYTE* pAlphaMask, CPoint p, CPoint org, int time, int alpha, MOD_MOVEVC& mod_vc, REFERENCE_TIME rt);
+#else
+ CRect PaintShadow(SubPicDesc& spd, CRect& clipRect, BYTE* pAlphaMask, CPoint p, CPoint org, int time, int alpha);
+ CRect PaintOutline(SubPicDesc& spd, CRect& clipRect, BYTE* pAlphaMask, CPoint p, CPoint org, int time, int alpha);
+ CRect PaintBody(SubPicDesc& spd, CRect& clipRect, BYTE* pAlphaMask, CPoint p, CPoint org, int time, int alpha);
+#endif
+};
+
+enum eftype
+{
+ EF_MOVE = 0, // {\move(x1=param[0], y1=param[1], x2=param[2], y2=param[3], t1=t[0], t2=t[1])} or {\pos(x=param[0], y=param[1])}
+ EF_ORG, // {\org(x=param[0], y=param[1])}
+ EF_FADE, // {\fade(a1=param[0], a2=param[1], a3=param[2], t1=t[0], t2=t[1], t3=t[2], t4=t[3])} or {\fad(t1=t[1], t2=t[2])
+ EF_BANNER, // Banner;delay=param[0][;lefttoright=param[1];fadeawaywidth=param[2]]
+ EF_SCROLL, // Scroll up/down=param[3];top=param[0];bottom=param[1];delay=param[2][;fadeawayheight=param[4]]
+#ifdef _VSMOD // patch m006. moveable vector clip
+ EF_VECTCLP
+#endif
+};
+
+#ifdef _VSMOD // patch m006. moveable vector clip
+ #define EF_NUMBEROFEFFECTS 6
+#else
+ #define EF_NUMBEROFEFFECTS 5
+#endif
+
+class Effect
+{
+public:
+ enum eftype type;
+ int param[9];
+ int t[4];
+};
+
+class CSubtitle : public CAtlList<CLine*>
+{
+ int GetFullWidth();
+ int GetFullLineWidth(POSITION pos);
+ int GetWrapWidth(POSITION pos, int maxwidth);
+ CLine* GetNextLine(POSITION& pos, int maxwidth);
+
+public:
+ int m_scrAlignment;
+ int m_wrapStyle;
+ bool m_fAnimated;
+ int m_relativeTo;
+
+ Effect* m_effects[EF_NUMBEROFEFFECTS];
+
+ CAtlList<CWord*> m_words;
+
+ CClipper* m_pClipper;
+
+ CRect m_rect, m_clip;
+ int m_topborder, m_bottomborder;
+ bool m_clipInverse;
+
+ double m_scalex, m_scaley;
+
+public:
+ CSubtitle();
+ virtual ~CSubtitle();
+ virtual void Empty();
+
+ void CreateClippers(CSize size);
+
+ void MakeLines(CSize size, CRect marginRect);
+};
+
+class CScreenLayoutAllocator
+{
+ typedef struct
+ {
+ CRect r;
+ int segment, entry, layer;
+ } SubRect;
+
+ CAtlList<SubRect> m_subrects;
+
+public:
+ virtual void Empty();
+
+ void AdvanceToSegment(int segment, const CAtlArray<int>& sa);
+ CRect AllocRect(CSubtitle* s, int segment, int entry, int layer, int collisions);
+};
+
+class __declspec(uuid("537DCACA-2812-4a4f-B2C6-1A34C17ADEB0"))
+CRenderedTextSubtitle : public CSimpleTextSubtitle, public CSubPicProviderImpl, public ISubStream
+{
+ CAtlMap<int, CSubtitle*> m_subtitleCache;
+
+ CScreenLayoutAllocator m_sla;
+
+ CSize m_size;
+ CRect m_vidrect;
+
+ // temp variables, used when parsing the script
+ int m_time, m_delay;
+ int m_animStart, m_animEnd;
+ double m_animAccel;
+ int m_ktype, m_kstart, m_kend;
+ int m_nPolygon;
+ int m_polygonBaselineOffset;
+ STSStyle *m_pStyleOverride; // the app can decide to use this style instead of a built-in one
+ bool m_doOverrideStyle;
+
+ void ParseEffect(CSubtitle* sub, CString str);
+ void ParseString(CSubtitle* sub, CStringW str, STSStyle& style);
+ void ParsePolygon(CSubtitle* sub, CStringW str, STSStyle& style);
+ bool ParseSSATag(CSubtitle* sub, CStringW str, STSStyle& style, STSStyle& org, bool fAnimate = false);
+ bool ParseHtmlTag(CSubtitle* sub, CStringW str, STSStyle& style, STSStyle& org);
+
+ double CalcAnimation(double dst, double src, bool fAnimate);
+
+ CSubtitle* GetSubtitle(int entry);
+
+protected:
+ virtual void OnChanged();
+
+public:
+ CRenderedTextSubtitle(CCritSec* pLock, STSStyle *styleOverride = NULL, bool doOverride = false);
+ virtual ~CRenderedTextSubtitle();
+
+ virtual void Copy(CSimpleTextSubtitle& sts);
+ virtual void Empty();
+
+ // call to signal this RTS to ignore any of the styles and apply the given override style
+ void SetOverride(bool doOverride = true, STSStyle *styleOverride = NULL)
+ {
+ m_doOverrideStyle = doOverride;
+ if(styleOverride != NULL) m_pStyleOverride = styleOverride;
+ }
+
+public:
+ bool Init(CSize size, CRect vidrect); // will call Deinit()
+ void Deinit();
+
+ DECLARE_IUNKNOWN
+ STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void** ppv);
+
+ // ISubPicProvider
+ STDMETHODIMP_(POSITION) GetStartPosition(REFERENCE_TIME rt, double fps);
+ STDMETHODIMP_(POSITION) GetNext(POSITION pos);
+ STDMETHODIMP_(REFERENCE_TIME) GetStart(POSITION pos, double fps);
+ STDMETHODIMP_(REFERENCE_TIME) GetStop(POSITION pos, double fps);
+ STDMETHODIMP_(bool) IsAnimated(POSITION pos);
+ STDMETHODIMP Render(SubPicDesc& spd, REFERENCE_TIME rt, double fps, RECT& bbox);
+
+ // IPersist
+ STDMETHODIMP GetClassID(CLSID* pClassID);
+
+ // ISubStream
+ STDMETHODIMP_(int) GetStreamCount();
+ STDMETHODIMP GetStreamInfo(int i, WCHAR** ppName, LCID* pLCID);
+ STDMETHODIMP_(int) GetStream();
+ STDMETHODIMP SetStream(int iStream);
+ STDMETHODIMP Reload();
+};
diff --git a/src/Subtitles/Rasterizer.cpp b/src/Subtitles/Rasterizer.cpp
new file mode 100644
index 000000000..0800d466e
--- /dev/null
+++ b/src/Subtitles/Rasterizer.cpp
@@ -0,0 +1,2116 @@
+/*
+ * 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 <string.h>
+#include <math.h>
+#include <vector>
+#include <algorithm>
+#include "Rasterizer.h"
+#include "SeparableFilter.h"
+
+ #ifndef _MAX /* avoid collision with common (nonconforming) macros */
+ #define _MAX (max)
+ #define _MIN (min)
+ #define _IMPL_MAX max
+ #define _IMPL_MIN min
+ #else
+ #define _IMPL_MAX _MAX
+ #define _IMPL_MIN _MIN
+ #endif
+
+int Rasterizer::getOverlayWidth()
+{
+ return mOverlayWidth*8;
+}
+
+Rasterizer::Rasterizer() : mpPathTypes(NULL), mpPathPoints(NULL), mPathPoints(0), mpOverlayBuffer(NULL)
+{
+ mOverlayWidth = mOverlayHeight = 0;
+ mPathOffsetX = mPathOffsetY = 0;
+ mOffsetX = mOffsetY = 0;
+}
+
+Rasterizer::~Rasterizer()
+{
+ _TrashPath();
+ _TrashOverlay();
+}
+
+void Rasterizer::_TrashPath()
+{
+ delete [] mpPathTypes;
+ delete [] mpPathPoints;
+ mpPathTypes = NULL;
+ mpPathPoints = NULL;
+ mPathPoints = 0;
+}
+
+void Rasterizer::_TrashOverlay()
+{
+ if (mpOverlayBuffer)
+ _aligned_free(mpOverlayBuffer);
+ mpOverlayBuffer = NULL;
+}
+
+void Rasterizer::_ReallocEdgeBuffer(int edges)
+{
+ mEdgeHeapSize = edges;
+ mpEdgeBuffer = (Edge*)realloc(mpEdgeBuffer, sizeof(Edge)*edges);
+}
+
+void Rasterizer::_EvaluateBezier(int ptbase, bool fBSpline)
+{
+ const POINT* pt0 = mpPathPoints + ptbase;
+ const POINT* pt1 = mpPathPoints + ptbase + 1;
+ const POINT* pt2 = mpPathPoints + ptbase + 2;
+ const POINT* pt3 = mpPathPoints + ptbase + 3;
+
+ double x0 = pt0->x;
+ double x1 = pt1->x;
+ double x2 = pt2->x;
+ double x3 = pt3->x;
+ double y0 = pt0->y;
+ double y1 = pt1->y;
+ double y2 = pt2->y;
+ double y3 = pt3->y;
+
+ double cx3, cx2, cx1, cx0, cy3, cy2, cy1, cy0;
+
+ if(fBSpline)
+ {
+ // 1 [-1 +3 -3 +1]
+ // - * [+3 -6 +3 0]
+ // 6 [-3 0 +3 0]
+ // [+1 +4 +1 0]
+
+ double _1div6 = 1.0/6.0;
+
+ cx3 = _1div6*(- x0+3*x1-3*x2+x3);
+ cx2 = _1div6*( 3*x0-6*x1+3*x2);
+ cx1 = _1div6*(-3*x0 +3*x2);
+ cx0 = _1div6*( x0+4*x1+1*x2);
+
+ cy3 = _1div6*(- y0+3*y1-3*y2+y3);
+ cy2 = _1div6*( 3*y0-6*y1+3*y2);
+ cy1 = _1div6*(-3*y0 +3*y2);
+ cy0 = _1div6*( y0+4*y1+1*y2);
+ }
+ else // bezier
+ {
+ // [-1 +3 -3 +1]
+ // [+3 -6 +3 0]
+ // [-3 +3 0 0]
+ // [+1 0 0 0]
+
+ cx3 = - x0+3*x1-3*x2+x3;
+ cx2 = 3*x0-6*x1+3*x2;
+ cx1 = -3*x0+3*x1;
+ cx0 = x0;
+
+ cy3 = - y0+3*y1-3*y2+y3;
+ cy2 = 3*y0-6*y1+3*y2;
+ cy1 = -3*y0+3*y1;
+ cy0 = y0;
+ }
+
+ //
+ // This equation is from Graphics Gems I.
+ //
+ // The idea is that since we're approximating a cubic curve with lines,
+ // any error we incur is due to the curvature of the line, which we can
+ // estimate by calculating the maximum acceleration of the curve. For
+ // a cubic, the acceleration (second derivative) is a line, meaning that
+ // the absolute maximum acceleration must occur at either the beginning
+ // (|c2|) or the end (|c2+c3|). Our bounds here are a little more
+ // conservative than that, but that's okay.
+ //
+ // If the acceleration of the parametric formula is zero (c2 = c3 = 0),
+ // that component of the curve is linear and does not incur any error.
+ // If a=0 for both X and Y, the curve is a line segment and we can
+ // use a step size of 1.
+
+ double maxaccel1 = fabs(2*cy2) + fabs(6*cy3);
+ double maxaccel2 = fabs(2*cx2) + fabs(6*cx3);
+
+ double maxaccel = maxaccel1 > maxaccel2 ? maxaccel1 : maxaccel2;
+ double h = 1.0;
+
+ if(maxaccel > 8.0) h = sqrt(8.0 / maxaccel);
+
+ if(!fFirstSet) {firstp.x = (LONG)cx0; firstp.y = (LONG)cy0; lastp = firstp; fFirstSet = true;}
+
+ for(double t = 0; t < 1.0; t += h)
+ {
+ double x = cx0 + t*(cx1 + t*(cx2 + t*cx3));
+ double y = cy0 + t*(cy1 + t*(cy2 + t*cy3));
+ _EvaluateLine(lastp.x, lastp.y, (int)x, (int)y);
+ }
+
+ double x = cx0 + cx1 + cx2 + cx3;
+ double y = cy0 + cy1 + cy2 + cy3;
+ _EvaluateLine(lastp.x, lastp.y, (int)x, (int)y);
+}
+
+void Rasterizer::_EvaluateLine(int pt1idx, int pt2idx)
+{
+ const POINT* pt1 = mpPathPoints + pt1idx;
+ const POINT* pt2 = mpPathPoints + pt2idx;
+
+ _EvaluateLine(pt1->x, pt1->y, pt2->x, pt2->y);
+}
+
+void Rasterizer::_EvaluateLine(int x0, int y0, int x1, int y1)
+{
+ if(lastp.x != x0 || lastp.y != y0)
+ {
+ _EvaluateLine(lastp.x, lastp.y, x0, y0);
+ }
+
+ if(!fFirstSet) {firstp.x = x0; firstp.y = y0; fFirstSet = true;}
+ lastp.x = x1; lastp.y = y1;
+
+ if(y1 > y0) // down
+ {
+ __int64 xacc = (__int64)x0 << 13;
+
+ // prestep y0 down
+
+ int dy = y1 - y0;
+ int y = ((y0 + 3)&~7) + 4;
+ int iy = y >> 3;
+
+ y1 = (y1 - 5) >> 3;
+
+ if(iy <= y1)
+ {
+ __int64 invslope = (__int64(x1 - x0) << 16) / dy;
+
+ while(mEdgeNext + y1 + 1 - iy > mEdgeHeapSize)
+ _ReallocEdgeBuffer(mEdgeHeapSize*2);
+
+ xacc += (invslope * (y - y0)) >> 3;
+
+ while(iy <= y1)
+ {
+ int ix = (int)((xacc + 32768) >> 16);
+
+ mpEdgeBuffer[mEdgeNext].next = mpScanBuffer[iy];
+ mpEdgeBuffer[mEdgeNext].posandflag = ix*2 + 1;
+
+ mpScanBuffer[iy] = mEdgeNext++;
+
+ ++iy;
+ xacc += invslope;
+ }
+ }
+ }
+ else if(y1 < y0) // up
+ {
+ __int64 xacc = (__int64)x1 << 13;
+
+ // prestep y1 down
+
+ int dy = y0 - y1;
+ int y = ((y1 + 3)&~7) + 4;
+ int iy = y >> 3;
+
+ y0 = (y0 - 5) >> 3;
+
+ if(iy <= y0)
+ {
+ __int64 invslope = (__int64(x0 - x1) << 16) / dy;
+
+ while(mEdgeNext + y0 + 1 - iy > mEdgeHeapSize)
+ _ReallocEdgeBuffer(mEdgeHeapSize*2);
+
+ xacc += (invslope * (y - y1)) >> 3;
+
+ while(iy <= y0)
+ {
+ int ix = (int)((xacc + 32768) >> 16);
+
+ mpEdgeBuffer[mEdgeNext].next = mpScanBuffer[iy];
+ mpEdgeBuffer[mEdgeNext].posandflag = ix*2;
+
+ mpScanBuffer[iy] = mEdgeNext++;
+
+ ++iy;
+ xacc += invslope;
+ }
+ }
+ }
+}
+
+bool Rasterizer::BeginPath(HDC hdc)
+{
+ _TrashPath();
+
+ return !!::BeginPath(hdc);
+}
+
+bool Rasterizer::EndPath(HDC hdc)
+{
+ ::CloseFigure(hdc);
+
+ if(::EndPath(hdc))
+ {
+ mPathPoints = GetPath(hdc, NULL, NULL, 0);
+
+ if(!mPathPoints)
+ return true;
+
+ mpPathTypes = (BYTE*)malloc(sizeof(BYTE) * mPathPoints);
+ mpPathPoints = (POINT*)malloc(sizeof(POINT) * mPathPoints);
+
+ if(mPathPoints == (size_t)GetPath(hdc, mpPathPoints, mpPathTypes, mPathPoints))
+ return true;
+ }
+
+ ::AbortPath(hdc);
+
+ return false;
+}
+
+bool Rasterizer::PartialBeginPath(HDC hdc, bool bClearPath)
+{
+ if(bClearPath)
+ _TrashPath();
+
+ return !!::BeginPath(hdc);
+}
+
+bool Rasterizer::PartialEndPath(HDC hdc, long dx, long dy)
+{
+ ::CloseFigure(hdc);
+
+ if(::EndPath(hdc))
+ {
+ int nPoints;
+ BYTE* pNewTypes;
+ POINT* pNewPoints;
+
+ nPoints = GetPath(hdc, NULL, NULL, 0);
+
+ if(!nPoints)
+ return true;
+
+ pNewTypes = (BYTE*)realloc(mpPathTypes, (mPathPoints + nPoints) * sizeof(BYTE));
+ pNewPoints = (POINT*)realloc(mpPathPoints, (mPathPoints + nPoints) * sizeof(POINT));
+
+ if(pNewTypes)
+ mpPathTypes = pNewTypes;
+
+ if(pNewPoints)
+ mpPathPoints = pNewPoints;
+
+ BYTE* pTypes = DNew BYTE[nPoints];
+ POINT* pPoints = DNew POINT[nPoints];
+
+ if(pNewTypes && pNewPoints && nPoints == GetPath(hdc, pPoints, pTypes, nPoints))
+ {
+ for(ptrdiff_t i = 0; i < nPoints; ++i)
+ {
+ mpPathPoints[mPathPoints + i].x = pPoints[i].x + dx;
+ mpPathPoints[mPathPoints + i].y = pPoints[i].y + dy;
+ mpPathTypes[mPathPoints + i] = pTypes[i];
+ }
+
+ mPathPoints += nPoints;
+
+ delete[] pTypes;
+ delete[] pPoints;
+ return true;
+ }
+ else
+ DebugBreak();
+
+ delete[] pTypes;
+ delete[] pPoints;
+ }
+
+ ::AbortPath(hdc);
+
+ return false;
+}
+
+bool Rasterizer::ScanConvert()
+{
+ size_t lastmoveto = (size_t)-1;
+ size_t i;
+
+ // Drop any outlines we may have.
+
+ mOutline.clear();
+ mWideOutline.clear();
+ mWideBorder = 0;
+
+ // Determine bounding box
+
+ if(!mPathPoints)
+ {
+ mPathOffsetX = mPathOffsetY = 0;
+ mWidth = mHeight = 0;
+ return 0;
+ }
+
+ ptrdiff_t minx = INT_MAX;
+ ptrdiff_t miny = INT_MAX;
+ ptrdiff_t maxx = INT_MIN;
+ ptrdiff_t maxy = INT_MIN;
+
+ for(i=0; i<mPathPoints; ++i)
+ {
+ ptrdiff_t ix = mpPathPoints[i].x;
+ ptrdiff_t iy = mpPathPoints[i].y;
+
+ if(ix < minx) minx = ix;
+ if(ix > maxx) maxx = ix;
+ if(iy < miny) miny = iy;
+ if(iy > maxy) maxy = iy;
+ }
+
+ minx = (minx >> 3) & ~7;
+ miny = (miny >> 3) & ~7;
+ maxx = (maxx + 7) >> 3;
+ maxy = (maxy + 7) >> 3;
+
+ for(i=0; i<mPathPoints; ++i)
+ {
+ mpPathPoints[i].x -= minx*8;
+ mpPathPoints[i].y -= miny*8;
+ }
+
+ if(minx > maxx || miny > maxy)
+ {
+ mWidth = mHeight = 0;
+ mPathOffsetX = mPathOffsetY = 0;
+ _TrashPath();
+ return true;
+ }
+
+ mWidth = maxx + 1 - minx;
+ mHeight = maxy + 1 - miny;
+
+ mPathOffsetX = minx;
+ mPathOffsetY = miny;
+
+ // Initialize edge buffer. We use edge 0 as a sentinel.
+
+ mEdgeNext = 1;
+ mEdgeHeapSize = 2048;
+ mpEdgeBuffer = (Edge*)malloc(sizeof(Edge)*mEdgeHeapSize);
+
+ // Initialize scanline list.
+
+ mpScanBuffer = DNew size_t[mHeight];
+ memset(mpScanBuffer, 0, mHeight*sizeof(size_t));
+
+ // Scan convert the outline. Yuck, Bezier curves....
+
+ // Unfortunately, Windows 95/98 GDI has a bad habit of giving us text
+ // paths with all but the first figure left open, so we can't rely
+ // on the PT_CLOSEFIGURE flag being used appropriately.
+
+ fFirstSet = false;
+ firstp.x = firstp.y = 0;
+ lastp.x = lastp.y = 0;
+
+ for(i=0; i<mPathPoints; ++i)
+ {
+ BYTE t = mpPathTypes[i] & ~PT_CLOSEFIGURE;
+
+ switch(t)
+ {
+ case PT_MOVETO:
+ if(lastmoveto >= 0 && firstp != lastp)
+ _EvaluateLine(lastp.x, lastp.y, firstp.x, firstp.y);
+ lastmoveto = i;
+ fFirstSet = false;
+ lastp = mpPathPoints[i];
+ break;
+ case PT_MOVETONC:
+ break;
+ case PT_LINETO:
+ if(mPathPoints - (i-1) >= 2) _EvaluateLine(i-1, i);
+ break;
+ case PT_BEZIERTO:
+ if(mPathPoints - (i-1) >= 4) _EvaluateBezier(i-1, false);
+ i += 2;
+ break;
+ case PT_BSPLINETO:
+ if(mPathPoints - (i-1) >= 4) _EvaluateBezier(i-1, true);
+ i += 2;
+ break;
+ case PT_BSPLINEPATCHTO:
+ if(mPathPoints - (i-3) >= 4) _EvaluateBezier(i-3, true);
+ break;
+ }
+ }
+
+ if(lastmoveto >= 0 && firstp != lastp)
+ _EvaluateLine(lastp.x, lastp.y, firstp.x, firstp.y);
+
+ // Free the path since we don't need it anymore.
+
+ _TrashPath();
+
+ // Convert the edges to spans. We couldn't do this before because some of
+ // the regions may have winding numbers >+1 and it would have been a pain
+ // to try to adjust the spans on the fly. We use one heap to detangle
+ // a scanline's worth of edges from the singly-linked lists, and another
+ // to collect the actual scans.
+
+ std::vector<int> heap;
+
+ mOutline.reserve(mEdgeNext / 2);
+
+ __int64 y = 0;
+
+ for(y=0; y<mHeight; ++y)
+ {
+ int count = 0;
+
+ // Detangle scanline into edge heap.
+
+ for(size_t ptr = (mpScanBuffer[y]&size_t(-1)); ptr; ptr = mpEdgeBuffer[ptr].next)
+ {
+ heap.push_back(mpEdgeBuffer[ptr].posandflag);
+ }
+
+ // Sort edge heap. Note that we conveniently made the opening edges
+ // one more than closing edges at the same spot, so we won't have any
+ // problems with abutting spans.
+
+ std::sort(heap.begin(), heap.end()/*begin() + heap.size()*/);
+
+ // Process edges and add spans. Since we only check for a non-zero
+ // winding number, it doesn't matter which way the outlines go!
+
+ std::vector<int>::iterator itX1 = heap.begin();
+ std::vector<int>::iterator itX2 = heap.end(); // begin() + heap.size();
+
+ size_t x1 = 0;
+ size_t x2;
+
+ for(; itX1 != itX2; ++itX1)
+ {
+ size_t x = *itX1;
+
+ if(!count)
+ x1 = (x>>1);
+
+ if(x&1)
+ ++count;
+ else
+ --count;
+
+ if(!count)
+ {
+ x2 = (x>>1);
+
+ if(x2>x1)
+ mOutline.push_back(std::pair<__int64,__int64>((y<<32)+x1+0x4000000040000000i64, (y<<32)+x2+0x4000000040000000i64)); // G: damn Avery, this is evil! :)
+ }
+ }
+
+ heap.clear();
+ }
+
+ // Dump the edge and scan buffers, since we no longer need them.
+
+ free(mpEdgeBuffer);
+ delete [] mpScanBuffer;
+
+ // All done!
+
+ return true;
+}
+
+using namespace std;
+
+void Rasterizer::_OverlapRegion(tSpanBuffer& dst, tSpanBuffer& src, int dx, int dy)
+{
+ tSpanBuffer temp;
+
+ temp.reserve(dst.size() + src.size());
+
+ dst.swap(temp);
+
+ tSpanBuffer::iterator itA = temp.begin();
+ tSpanBuffer::iterator itAE = temp.end();
+ tSpanBuffer::iterator itB = src.begin();
+ tSpanBuffer::iterator itBE = src.end();
+
+ // Don't worry -- even if dy<0 this will still work! // G: hehe, the evil twin :)
+
+ unsigned __int64 offset1 = (((__int64)dy)<<32) - dx;
+ unsigned __int64 offset2 = (((__int64)dy)<<32) + dx;
+
+ while(itA != itAE && itB != itBE)
+ {
+ if((*itB).first + offset1 < (*itA).first)
+ {
+ // B span is earlier. Use it.
+
+ unsigned __int64 x1 = (*itB).first + offset1;
+ unsigned __int64 x2 = (*itB).second + offset2;
+
+ ++itB;
+
+ // B spans don't overlap, so begin merge loop with A first.
+
+ for(;;)
+ {
+ // If we run out of A spans or the A span doesn't overlap,
+ // then the next B span can't either (because B spans don't
+ // overlap) and we exit.
+
+ if(itA == itAE || (*itA).first > x2)
+ break;
+
+ do {x2 = _MAX(x2, (*itA++).second);}
+ while(itA != itAE && (*itA).first <= x2);
+
+ // If we run out of B spans or the B span doesn't overlap,
+ // then the next A span can't either (because A spans don't
+ // overlap) and we exit.
+
+ if(itB == itBE || (*itB).first + offset1 > x2)
+ break;
+
+ do {x2 = _MAX(x2, (*itB++).second + offset2);}
+ while(itB != itBE && (*itB).first + offset1 <= x2);
+ }
+
+ // Flush span.
+
+ dst.push_back(tSpan(x1, x2));
+ }
+ else
+ {
+ // A span is earlier. Use it.
+
+ unsigned __int64 x1 = (*itA).first;
+ unsigned __int64 x2 = (*itA).second;
+
+ ++itA;
+
+ // A spans don't overlap, so begin merge loop with B first.
+
+ for(;;)
+ {
+ // If we run out of B spans or the B span doesn't overlap,
+ // then the next A span can't either (because A spans don't
+ // overlap) and we exit.
+
+ if(itB == itBE || (*itB).first + offset1 > x2)
+ break;
+
+ do {x2 = _MAX(x2, (*itB++).second + offset2);}
+ while(itB != itBE && (*itB).first + offset1 <= x2);
+
+ // If we run out of A spans or the A span doesn't overlap,
+ // then the next B span can't either (because B spans don't
+ // overlap) and we exit.
+
+ if(itA == itAE || (*itA).first > x2)
+ break;
+
+ do {x2 = _MAX(x2, (*itA++).second);}
+ while(itA != itAE && (*itA).first <= x2);
+ }
+
+ // Flush span.
+
+ dst.push_back(tSpan(x1, x2));
+ }
+ }
+
+ // Copy over leftover spans.
+
+ while(itA != itAE)
+ dst.push_back(*itA++);
+
+ while(itB != itBE)
+ {
+ dst.push_back(tSpan((*itB).first + offset1, (*itB).second + offset2));
+ ++itB;
+ }
+}
+
+bool Rasterizer::CreateWidenedRegion(int rx, int ry)
+{
+ if(rx < 0) rx = 0;
+ if(ry < 0) ry = 0;
+
+ mWideBorder = max(rx,ry);
+
+ if (ry > 0)
+ {
+ // Do a half circle.
+ // _OverlapRegion mirrors this so both halves are done.
+ for(ptrdiff_t y = -ry; y <= ry; ++y)
+ {
+ int x = (int)(0.5 + sqrt(float(ry*ry - y*y)) * float(rx)/float(ry));
+
+ _OverlapRegion(mWideOutline, mOutline, x, y);
+ }
+ }
+ else if (ry == 0 && rx > 0)
+ {
+ // There are artifacts if we don't make at least two overlaps of the line, even at same Y coord
+ _OverlapRegion(mWideOutline, mOutline, rx, 0);
+ _OverlapRegion(mWideOutline, mOutline, rx, 0);
+ }
+
+ return true;
+}
+
+void Rasterizer::DeleteOutlines()
+{
+ mWideOutline.clear();
+ mOutline.clear();
+}
+
+bool Rasterizer::Rasterize(int xsub, int ysub, int fBlur, double fGaussianBlur)
+{
+ _TrashOverlay();
+
+ if(!mWidth || !mHeight)
+ {
+ mOverlayWidth = mOverlayHeight = 0;
+ return true;
+ }
+
+ xsub &= 7;
+ ysub &= 7;
+
+ int width = mWidth + xsub;
+ int height = mHeight;// + ysub
+
+ mOffsetX = mPathOffsetX - xsub;
+ mOffsetY = mPathOffsetY - ysub;
+
+ mWideBorder = (mWideBorder+7)&~7;
+
+ if(!mWideOutline.empty() || fBlur || fGaussianBlur > 0)
+ {
+ int bluradjust = 0;
+ if (fGaussianBlur > 0)
+ bluradjust += (int)(fGaussianBlur*3*8 + 0.5) | 1;
+ if (fBlur)
+ bluradjust += 8;
+
+ // Expand the buffer a bit when we're blurring, since that can also widen the borders a bit
+ bluradjust = (bluradjust+7)&~7;
+
+ width += 2*mWideBorder + bluradjust*2;
+ height += 2*mWideBorder + bluradjust*2;
+
+ xsub += mWideBorder + bluradjust;
+ ysub += mWideBorder + bluradjust;
+
+ mOffsetX -= mWideBorder + bluradjust;
+ mOffsetY -= mWideBorder + bluradjust;
+ }
+
+ mOverlayWidth = ((width+7)>>3) + 1;
+ // fixed image height
+ mOverlayHeight=((height+14)>>3) + 1;
+
+ mpOverlayBuffer = (byte*)_aligned_malloc(2 * mOverlayWidth * mOverlayHeight, 16);
+ memset(mpOverlayBuffer, 0, 2 * mOverlayWidth * mOverlayHeight);
+
+ // Are we doing a border?
+
+ tSpanBuffer* pOutline[2] = {&mOutline, &mWideOutline};
+
+ for(ptrdiff_t i = countof(pOutline)-1; i >= 0; i--)
+ {
+ tSpanBuffer::iterator it = pOutline[i]->begin();
+ tSpanBuffer::iterator itEnd = pOutline[i]->end();
+
+ for(; it!=itEnd; ++it)
+ {
+ unsigned __int64 f = (*it).first;
+ size_t y = (f >> 32) - 0x40000000 + ysub;
+ size_t x1 = (f & 0xffffffff) - 0x40000000 + xsub;
+
+ unsigned __int64 s = (*it).second;
+ size_t x2 = (s & 0xffffffff) - 0x40000000 + xsub;
+
+ if(x2 > x1)
+ {
+ size_t first = x1>>3;
+ size_t last = (x2-1)>>3;
+ byte* dst = mpOverlayBuffer + 2*(mOverlayWidth*(y>>3) + first) + i;
+
+ if(first == last)
+ *dst += x2-x1;
+ else
+ {
+ *dst += ((first+1)<<3) - x1;
+ dst += 2;
+
+ while(++first < last)
+ {
+ *dst += 0x08;
+ dst += 2;
+ }
+
+ *dst += x2 - (last<<3);
+ }
+ }
+ }
+ }
+
+ // Do some gaussian blur magic
+ if (fGaussianBlur > 0)
+ {
+ GaussianKernel filter(fGaussianBlur);
+ if (mOverlayWidth >= filter.width && mOverlayHeight >= filter.width)
+ {
+ size_t pitch = mOverlayWidth*2;
+
+ byte *tmp = DNew byte[pitch*mOverlayHeight];
+ if(!tmp) return(false);
+
+ int border = !mWideOutline.empty() ? 1 : 0;
+
+ byte *src = mpOverlayBuffer + border;
+
+ SeparableFilterX<2>(src, tmp, mOverlayWidth, mOverlayHeight, pitch, filter.kernel, filter.width, filter.divisor);
+ SeparableFilterY<2>(tmp, src, mOverlayWidth, mOverlayHeight, pitch, filter.kernel, filter.width, filter.divisor);
+
+ delete[] tmp;
+ }
+ }
+
+ // If we're blurring, do a 3x3 box blur
+ // Can't do it on subpictures smaller than 3x3 pixels
+ for (int pass = 0; pass < fBlur; pass++)
+ {
+ if(mOverlayWidth >= 3 && mOverlayHeight >= 3)
+ {
+ int pitch = mOverlayWidth*2;
+
+ byte* tmp = DNew byte[pitch*mOverlayHeight];
+ if(!tmp) return(false);
+
+ memcpy(tmp, mpOverlayBuffer, pitch*mOverlayHeight);
+
+ int border = !mWideOutline.empty() ? 1 : 0;
+
+ // This could be done in a separated way and win some speed
+ for(ptrdiff_t j = 1; j < mOverlayHeight-1; j++)
+ {
+ byte* src = tmp + pitch*j + 2 + border;
+ byte* dst = mpOverlayBuffer + pitch*j + 2 + border;
+
+ for(ptrdiff_t i = 1; i < mOverlayWidth-1; i++, src+=2, dst+=2)
+ {
+ *dst = (src[-2-pitch] + (src[-pitch]<<1) + src[+2-pitch]
+ + (src[-2]<<1) + (src[0]<<2) + (src[+2]<<1)
+ + src[-2+pitch] + (src[+pitch]<<1) + src[+2+pitch]) >> 4;
+ }
+ }
+
+ delete [] tmp;
+ }
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+static __forceinline void pixmix(DWORD *dst, DWORD color, DWORD alpha)
+{
+ DWORD a = (((alpha)*(color>>24))>>6)&0xff;
+ DWORD ia = 256-a;
+ a+=1;
+
+ DWORD tmp = (((((*dst>>8)&0x00ff0000)*ia)&0xff000000)>>24)&0xFF;
+ UNUSED_ALWAYS(tmp);
+ *dst = ((((*dst&0x00ff00ff)*ia + (color&0x00ff00ff)*a)&0xff00ff00)>>8)
+ | ((((*dst&0x0000ff00)*ia + (color&0x0000ff00)*a)&0x00ff0000)>>8)
+ | ((((*dst>>8)&0x00ff0000)*ia)&0xff000000);
+}
+
+static __forceinline void pixmix2(DWORD *dst, DWORD color, DWORD shapealpha, DWORD clipalpha)
+{
+ DWORD a = (((shapealpha)*(clipalpha)*(color>>24))>>12)&0xff;
+ DWORD ia = 256-a;
+ a+=1;
+
+ *dst = ((((*dst&0x00ff00ff)*ia + (color&0x00ff00ff)*a)&0xff00ff00)>>8)
+ | ((((*dst&0x0000ff00)*ia + (color&0x0000ff00)*a)&0x00ff0000)>>8)
+ | ((((*dst>>8)&0x00ff0000)*ia)&0xff000000);
+}
+
+#include <xmmintrin.h>
+#include <emmintrin.h>
+
+static __forceinline void pixmix_sse2(DWORD* dst, DWORD color, DWORD alpha)
+{
+ alpha = (((alpha) * (color>>24)) >> 6) & 0xff;
+ color &= 0xffffff;
+
+ __m128i zero = _mm_setzero_si128();
+ __m128i a = _mm_set1_epi32(((alpha+1) << 16) | (0x100 - alpha));
+ __m128i d = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*dst), zero);
+ __m128i s = _mm_unpacklo_epi8(_mm_cvtsi32_si128(color), zero);
+ __m128i r = _mm_unpacklo_epi16(d, s);
+
+ r = _mm_madd_epi16(r, a);
+ r = _mm_srli_epi32(r, 8);
+ r = _mm_packs_epi32(r, r);
+ r = _mm_packus_epi16(r, r);
+
+ *dst = (DWORD)_mm_cvtsi128_si32(r);
+}
+
+static __forceinline void pixmix2_sse2(DWORD* dst, DWORD color, DWORD shapealpha, DWORD clipalpha)
+{
+ DWORD alpha = (((shapealpha)*(clipalpha)*(color>>24))>>12)&0xff;
+ color &= 0xffffff;
+
+ __m128i zero = _mm_setzero_si128();
+ __m128i a = _mm_set1_epi32(((alpha+1) << 16) | (0x100 - alpha));
+ __m128i d = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*dst), zero);
+ __m128i s = _mm_unpacklo_epi8(_mm_cvtsi32_si128(color), zero);
+ __m128i r = _mm_unpacklo_epi16(d, s);
+
+ r = _mm_madd_epi16(r, a);
+ r = _mm_srli_epi32(r, 8);
+ r = _mm_packs_epi32(r, r);
+ r = _mm_packus_epi16(r, r);
+
+ *dst = (DWORD)_mm_cvtsi128_si32(r);
+}
+
+#include <mmintrin.h>
+
+// Calculate a - b clamping to 0 instead of underflowing
+static __forceinline DWORD safe_subtract(DWORD a, DWORD b)
+{
+#ifndef _WIN64
+ __m64 ap = _mm_cvtsi32_si64(a);
+ __m64 bp = _mm_cvtsi32_si64(b);
+ __m64 rp = _mm_subs_pu16(ap, bp);
+ DWORD r = (DWORD)_mm_cvtsi64_si32(rp);
+ _mm_empty();
+ return r;
+#else
+ // For whatever reason Microsoft's x64 compiler doesn't support MMX intrinsics
+ return (b > a) ? 0 : a - b;
+#endif
+}
+
+static __forceinline DWORD safe_subtract_sse2(DWORD a, DWORD b)
+{
+ __m128i ap = _mm_cvtsi32_si128(a);
+ __m128i bp = _mm_cvtsi32_si128(b);
+ __m128i rp = _mm_subs_epu16(ap, bp);
+
+ return (DWORD)_mm_cvtsi128_si32(rp);
+}
+
+// For CPUID usage in Rasterizer::Draw
+#include "../dsutil/vd.h"
+
+static const __int64 _00ff00ff00ff00ff = 0x00ff00ff00ff00ffi64;
+
+// some helper procedures (Draw is so big)
+void Rasterizer::Draw_noAlpha_spFF_Body_0(RasterizerNfo& rnfo)
+{
+ int h = rnfo.h;
+ int color = rnfo.color;
+
+ byte* s = rnfo.s;
+ DWORD* dst = rnfo.dst;
+ // The <<6 is due to pixmix expecting the alpha parameter to be
+ // the multiplication of two 6-bit unsigned numbers but we
+ // only have one here. (No alpha mask.)
+ while(h--)
+ {
+ for(int wt=0; wt<rnfo.w; ++wt)
+ pixmix(&dst[wt], color, s[wt*2]);
+ s += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_noAlpha_spFF_noBody_0(RasterizerNfo& rnfo)
+{
+ int h = rnfo.h;
+ int color = rnfo.color;
+
+ byte* src = rnfo.src;
+ DWORD* dst = rnfo.dst;
+ // src contains two different bitmaps, interlaced per pixel.
+ // The first stored is the fill, the second is the widened
+ // fill region created by CreateWidenedRegion().
+ // Since we're drawing only the border, we must otain that
+ // by subtracting the fill from the widened region. The
+ // subtraction must be saturating since the widened region
+ // pixel value can be smaller than the fill value.
+ // This happens when blur edges is used.
+ while(h--)
+ {
+ for(int wt=0; wt<rnfo.w; ++wt)
+ pixmix(&dst[wt], color, safe_subtract(src[wt*2+1], src[wt*2]));
+ src += 2*rnfo.overlayp;
+
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_noAlpha_sp_Body_0(RasterizerNfo& rnfo)
+{
+ int h = rnfo.h;
+ int color = rnfo.color;
+
+
+ byte* s = rnfo.s;
+ DWORD* dst = rnfo.dst;
+ // xo is the offset (usually negative) we have moved into the image
+ // So if we have passed the switchpoint (?) switch to another colour
+ // (So switchpts stores both colours *and* coordinates?)
+ int gran = min(rnfo.sw[3]+1-rnfo.xo,rnfo.w);
+ int color2 = rnfo.sw[2];
+ while(h--)
+ {
+ for(int wt=0; wt<gran; ++wt)
+ pixmix(&dst[wt], color, s[wt*2]);
+ for(int wt=gran; wt<rnfo.w; ++wt)
+ pixmix(&dst[wt], color2, s[wt*2]);
+ s += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_noAlpha_sp_noBody_0(RasterizerNfo& rnfo)
+{
+ int h = rnfo.h;
+ int color = rnfo.color;
+
+ byte* src = rnfo.src;
+ DWORD* dst = rnfo.dst;
+
+ int gran = min(rnfo.sw[3]+1-rnfo.xo,rnfo.w);
+ int color2 = rnfo.sw[2];
+ while(h--)
+ {
+ for(int wt=0; wt<gran; ++wt)
+ pixmix(&dst[wt], color, safe_subtract(src[wt*2+1], src[wt*2]));
+ for(int wt=gran; wt<rnfo.w; ++wt)
+ pixmix(&dst[wt], color2, safe_subtract(src[wt*2+1], src[wt*2]));
+ src += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Alpha_spFF_Body_0(RasterizerNfo& rnfo)
+{
+#ifdef _VSMOD // patch m006. moveable vector clip
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+#else
+ byte* am = rnfo.am;
+#endif
+ int h = rnfo.h;
+ int color = rnfo.color;
+
+ byte* s = rnfo.s;
+ DWORD* dst = rnfo.dst;
+ // Both s and am contain 6-bit bitmaps of two different
+ // alpha masks; s is the subtitle shape and am is the
+ // clipping mask.
+ // Multiplying them together yields a 12-bit number.
+ // I think some imprecision is introduced here??
+ while(h--)
+ {
+ for(int wt=0; wt<rnfo.w; ++wt)
+#ifdef _VSMOD // patch m006. moveable vector clip
+ pixmix2(&dst[wt], color, s[wt*2], mod_vc.GetAlphaValue(wt,h));
+#else
+ pixmix2(&dst[wt], color, s[wt*2], am[wt]);
+ am += rnfo.spdw;
+#endif
+ s += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Alpha_spFF_noBody_0(RasterizerNfo& rnfo)
+{
+#ifdef _VSMOD // patch m006. moveable vector clip
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+#else
+ byte* am = rnfo.am;
+#endif
+ int h = rnfo.h;
+ int color = rnfo.color;
+
+ byte* src = rnfo.src;
+ DWORD* dst = rnfo.dst;
+
+ int gran = min(rnfo.sw[3]+1-rnfo.xo,rnfo.w);
+ int color2 = rnfo.sw[2];
+ while(h--)
+ {
+#ifdef _VSMOD // patch m006. moveable vector clip
+ for(int wt=0; wt<gran; ++wt)
+ pixmix2(&dst[wt], color, safe_subtract(src[wt*2+1], src[wt*2]), mod_vc.GetAlphaValue(wt,h));
+ for(int wt=gran; wt<rnfo.w; ++wt)
+ pixmix2(&dst[wt], color2, safe_subtract(src[wt*2+1], src[wt*2]), mod_vc.GetAlphaValue(wt,h));
+#else
+ for(int wt=0; wt<gran; ++wt)
+ pixmix2(&dst[wt], color, safe_subtract(src[wt*2+1], src[wt*2]), am[wt]);
+ for(int wt=gran; wt<rnfo.w; ++wt)
+ pixmix2(&dst[wt], color2, safe_subtract(src[wt*2+1], src[wt*2]), am[wt]);
+ am += rnfo.spdw;
+#endif
+ src += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Alpha_sp_Body_0(RasterizerNfo& rnfo)
+{
+#ifdef _VSMOD // patch m006. moveable vector clip
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+#else
+ byte* am = rnfo.am;
+#endif
+ int h = rnfo.h;
+ int color = rnfo.color;
+
+
+ byte* s = rnfo.s;
+ DWORD* dst = rnfo.dst;
+ while(h--)
+ {
+ for(int wt=0; wt<rnfo.w; ++wt)
+ {
+#ifdef _VSMOD // patch m006. moveable vector clip
+ pixmix2(&dst[wt], color, s[wt*2], mod_vc.GetAlphaValue(wt,h));
+ }
+#else
+ pixmix2(&dst[wt], color, s[wt*2], am[wt]);
+ }
+ am += rnfo.spdw;
+#endif
+ s += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Alpha_sp_noBody_0(RasterizerNfo& rnfo)
+{
+#ifdef _VSMOD // patch m006. moveable vector clip
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+#else
+ byte* am = rnfo.am;
+#endif
+ int h = rnfo.h;
+ int color = rnfo.color;
+
+
+ byte* src = rnfo.src;
+ DWORD* dst = rnfo.dst;
+ int gran = min(rnfo.sw[3]+1-rnfo.xo,rnfo.w);
+ int color2 = rnfo.sw[2];
+ while(h--)
+ {
+#ifdef _VSMOD // patch m006. moveable vector clip
+ for(int wt=0; wt<gran; ++wt)
+ pixmix2(&dst[wt], color, safe_subtract(src[wt*2+1], src[wt*2]), mod_vc.GetAlphaValue(wt,h));
+ for(int wt=gran; wt<rnfo.w; ++wt)
+ pixmix2(&dst[wt], color2, safe_subtract(src[wt*2+1], src[wt*2]), mod_vc.GetAlphaValue(wt,h));
+#else
+ for(int wt=0; wt<gran; ++wt)
+ pixmix2(&dst[wt], color, safe_subtract(src[wt*2+1], src[wt*2]), am[wt]);
+ for(int wt=gran; wt<rnfo.w; ++wt)
+ pixmix2(&dst[wt], color2, safe_subtract(src[wt*2+1], src[wt*2]), am[wt]);
+ am += rnfo.spdw;
+#endif
+ src += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}//Draw_Alpha_sp_noBody_0(w,h,xo,spd.w,color,spd.pitch,dst,src,sw,am);
+
+// == SSE2 func ==
+void Rasterizer::Draw_noAlpha_spFF_Body_sse2(RasterizerNfo& rnfo)
+{
+ int h = rnfo.h;
+ int color = rnfo.color;
+
+ byte* s = rnfo.s;
+ DWORD* dst = rnfo.dst;
+ // The <<6 is due to pixmix expecting the alpha parameter to be
+ // the multiplication of two 6-bit unsigned numbers but we
+ // only have one here. (No alpha mask.)
+ while(h--)
+ {
+ for(int wt=0; wt<rnfo.w; ++wt)
+ pixmix_sse2(&dst[wt], color, s[wt*2]);
+ s += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}//Draw_noAlpha_spFF_Body_sse2(w,h,color,spd.pitch,dst,s);
+
+void Rasterizer::Draw_noAlpha_spFF_noBody_sse2(RasterizerNfo& rnfo)
+{
+ int h = rnfo.h;
+ int color = rnfo.color;
+
+ byte* src = rnfo.src;
+ DWORD* dst = rnfo.dst;
+ // src contains two different bitmaps, interlaced per pixel.
+ // The first stored is the fill, the second is the widened
+ // fill region created by CreateWidenedRegion().
+ // Since we're drawing only the border, we must otain that
+ // by subtracting the fill from the widened region. The
+ // subtraction must be saturating since the widened region
+ // pixel value can be smaller than the fill value.
+ // This happens when blur edges is used.
+ while(h--)
+ {
+ for(int wt=0; wt<rnfo.w; ++wt)
+ pixmix_sse2(&dst[wt], color, safe_subtract_sse2(src[wt*2+1], src[wt*2]));
+ src += 2*rnfo.overlayp;
+
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}//Draw_noAlpha_spFF_noBody_sse2(w,h,color,spd.pitch,dst,src);
+
+void Rasterizer::Draw_noAlpha_sp_Body_sse2(RasterizerNfo& rnfo)
+{
+ int h = rnfo.h;
+
+ int color = rnfo.color;
+
+ byte* s = rnfo.s;
+ DWORD* dst = rnfo.dst;
+ // xo is the offset (usually negative) we have moved into the image
+ // So if we have passed the switchpoint (?) switch to another colour
+ // (So switchpts stores both colours *and* coordinates?)
+ int gran = min(rnfo.sw[3]+1-rnfo.xo,rnfo.w);
+ int color2 = rnfo.sw[2];
+ while(h--)
+ {
+ for(int wt=0; wt<gran; ++wt)
+ pixmix_sse2(&dst[wt], color, s[wt*2]);
+ for(int wt=gran; wt<rnfo.w; ++wt)
+ pixmix_sse2(&dst[wt], color2, s[wt*2]);
+ s += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_noAlpha_sp_noBody_sse2(RasterizerNfo& rnfo)
+{
+ int h = rnfo.h;
+ int color = rnfo.color;
+
+
+ byte* src = rnfo.src;
+ DWORD* dst = rnfo.dst;
+ int gran = min(rnfo.sw[3]+1-rnfo.xo,rnfo.w);
+ int color2 = rnfo.sw[2];
+ while(h--)
+ {
+ for(int wt=0; wt<gran; ++wt)
+ pixmix_sse2(&dst[wt], color, safe_subtract_sse2(src[wt*2+1], src[wt*2]));
+ for(int wt=gran; wt<rnfo.w; ++wt)
+ pixmix_sse2(&dst[wt], color2, safe_subtract_sse2(src[wt*2+1], src[wt*2]));
+ src += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Alpha_spFF_Body_sse2(RasterizerNfo& rnfo)
+{
+#ifdef _VSMOD // patch m006. moveable vector clip
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+#else
+ byte* am = rnfo.am;
+#endif
+ int h = rnfo.h;
+ int color = rnfo.color;
+
+ byte* s = rnfo.s;
+ DWORD* dst = rnfo.dst;
+ // Both s and am contain 6-bit bitmaps of two different
+ // alpha masks; s is the subtitle shape and am is the
+ // clipping mask.
+ // Multiplying them together yields a 12-bit number.
+ // I think some imprecision is introduced here??
+ while(h--)
+ {
+ for(int wt=0; wt<rnfo.w; ++wt)
+#ifdef _VSMOD // patch m006. moveable vector clip
+ pixmix2_sse2(&dst[wt], color, s[wt*2], mod_vc.GetAlphaValue(wt,h));
+#else
+ pixmix2_sse2(&dst[wt], color, s[wt*2], am[wt]);
+ am += rnfo.spdw;
+#endif
+ s += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Alpha_spFF_noBody_sse2(RasterizerNfo& rnfo)
+{
+#ifdef _VSMOD // patch m006. moveable vector clip
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+#else
+ byte* am = rnfo.am;
+#endif
+ int h = rnfo.h;
+ int color = rnfo.color;
+
+ byte* src = rnfo.src;
+ DWORD* dst = rnfo.dst;
+
+ while(h--)
+ {
+ for(int wt=0; wt<rnfo.w; ++wt)
+#ifdef _VSMOD // patch m006. moveable vector clip
+ pixmix2_sse2(&dst[wt], color, safe_subtract_sse2(src[wt*2+1], src[wt*2]), mod_vc.GetAlphaValue(wt,h));
+#else
+ pixmix2_sse2(&dst[wt], color, safe_subtract_sse2(src[wt*2+1], src[wt*2]), am[wt]);
+ am += rnfo.spdw;
+#endif
+ src += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Alpha_sp_Body_sse2(RasterizerNfo& rnfo)
+{
+#ifdef _VSMOD // patch m006. moveable vector clip
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+#else
+ byte* am = rnfo.am;
+#endif
+ int h = rnfo.h;
+ int color = rnfo.color;
+
+
+ byte* s = rnfo.s;
+ DWORD* dst = rnfo.dst;
+
+ int gran = min(rnfo.sw[3]+1-rnfo.xo,rnfo.w);
+ int color2 = rnfo.sw[2];
+ while(h--)
+ {
+#ifdef _VSMOD // patch m006. moveable vector clip
+ for(int wt=0; wt<gran; ++wt)
+ pixmix2_sse2(&dst[wt], color, s[wt*2], mod_vc.GetAlphaValue(wt,h));
+ for(int wt=gran; wt<rnfo.w; ++wt)
+ pixmix2_sse2(&dst[wt], color2, s[wt*2], mod_vc.GetAlphaValue(wt,h));
+#else
+ for(int wt=0; wt<gran; ++wt)
+ pixmix2_sse2(&dst[wt], color, s[wt*2], am[wt]);
+ for(int wt=gran; wt<rnfo.w; ++wt)
+ pixmix2_sse2(&dst[wt], color2, s[wt*2], am[wt]);
+ am += rnfo.spdw;
+#endif
+ s += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Alpha_sp_noBody_sse2(RasterizerNfo& rnfo)
+{
+#ifdef _VSMOD // patch m006. moveable vector clip
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+#else
+ byte* am = rnfo.am;
+#endif
+ int h = rnfo.h;
+
+ DWORD color = rnfo.color;
+
+ byte* src = rnfo.src;
+ DWORD* dst = rnfo.dst;
+
+ int gran = min(rnfo.sw[3]+1-rnfo.xo,rnfo.w);
+ int color2 = rnfo.sw[2];
+ UNUSED_ALWAYS(color2);
+ while(h--)
+ {
+#ifdef _VSMOD // patch m006. moveable vector clip
+ for(int wt=0; wt<gran; ++wt)
+ pixmix2_sse2(&dst[wt], color, safe_subtract_sse2(src[wt*2+1], src[wt*2]), mod_vc.GetAlphaValue(wt,h));
+ for(int wt=gran; wt<rnfo.w; ++wt)
+ pixmix2_sse2(&dst[wt], color, safe_subtract_sse2(src[wt*2+1], src[wt*2]), mod_vc.GetAlphaValue(wt,h));
+#else
+ for(int wt=0; wt<gran; ++wt)
+ pixmix2_sse2(&dst[wt], color, safe_subtract_sse2(src[wt*2+1], src[wt*2]), am[wt]);
+ for(int wt=gran; wt<rnfo.w; ++wt)
+ pixmix2_sse2(&dst[wt], color, safe_subtract_sse2(src[wt*2+1], src[wt*2]), am[wt]);
+ am += rnfo.spdw;
+#endif
+ src += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+#ifdef _VSMOD // patch m004. gradient colors
+void Rasterizer::Draw_Grad_noAlpha_spFF_Body_0(RasterizerNfo& rnfo)
+{
+ double hfull = (double)rnfo.h;
+ byte* s = rnfo.s;
+ DWORD* dst = rnfo.dst;
+ MOD_GRADIENT mod_grad = rnfo.mod_grad;
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+
+ int typ = rnfo.typ;
+ int h = rnfo.h;
+ int w = rnfo.w;
+ while(h--)
+ {
+ for(int wt=0; wt<w; ++wt)
+ pixmix(&dst[wt], mod_grad.getmixcolor(wt,h,typ), s[wt*2]);
+ s += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}//Draw_noAlpha_spFF_Body_0(w,h,color,spd.pitch,dst,s);
+
+void Rasterizer::Draw_Grad_noAlpha_spFF_noBody_0(RasterizerNfo& rnfo)
+{
+ double hfull = (double)rnfo.h;
+ byte* src = rnfo.src;
+ DWORD* dst = rnfo.dst;
+ MOD_GRADIENT mod_grad = rnfo.mod_grad;
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+
+ int typ = rnfo.typ;
+ int h = rnfo.h;
+ int w = rnfo.w;
+ while(h--)
+ {
+ for(int wt=0; wt<w; ++wt)
+ pixmix(&dst[wt], mod_grad.getmixcolor(wt,h,typ), safe_subtract(src[wt*2+1], src[wt*2]));
+ src += 2*rnfo.overlayp;
+
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Grad_noAlpha_sp_Body_0(RasterizerNfo& rnfo)
+{
+ double hfull = (double)rnfo.h;
+ MOD_GRADIENT mod_grad = rnfo.mod_grad;
+
+ byte* s = rnfo.s;
+ DWORD* dst = rnfo.dst;
+
+ int h = rnfo.h;
+ int w = rnfo.w;
+
+ int gran = max(rnfo.sw[3]+1-rnfo.xo,0);
+ while(h--)
+ {
+ for(int wt=0; wt<gran; ++wt)
+ pixmix(&dst[wt], mod_grad.getmixcolor(wt,h,0), s[wt*2]);
+ for(int wt=gran; wt<w; ++wt)
+ pixmix(&dst[wt], mod_grad.getmixcolor(wt,h,1), s[wt*2]);
+ s += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Grad_noAlpha_sp_noBody_0(RasterizerNfo& rnfo)
+{
+ double hfull = (double)rnfo.h;
+ MOD_GRADIENT mod_grad = rnfo.mod_grad;
+
+ byte* src = rnfo.src;
+ DWORD* dst = rnfo.dst;
+
+ int h = rnfo.h;
+ int w = rnfo.w;
+
+ int gran = min(rnfo.sw[3]+1-rnfo.xo,rnfo.w);
+ while(h--)
+ {
+ for(int wt=0; wt<gran; ++wt)
+ pixmix(&dst[wt], mod_grad.getmixcolor(wt,h,0), safe_subtract(src[wt*2+1], src[wt*2]));
+ for(int wt=gran; wt<w; ++wt)
+ pixmix(&dst[wt], mod_grad.getmixcolor(wt,h,1), safe_subtract(src[wt*2+1], src[wt*2]));
+ src += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Grad_Alpha_spFF_Body_0(RasterizerNfo& rnfo)
+{
+ double hfull = (double)rnfo.h;
+ byte* s = rnfo.s;
+ DWORD* dst = rnfo.dst;
+ MOD_GRADIENT mod_grad = rnfo.mod_grad;
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+
+ int typ = rnfo.typ;
+ int h = rnfo.h;
+ int w = rnfo.w;
+ while(h--)
+ {
+ for(int wt=0; wt<w; ++wt)
+ pixmix2(&dst[wt], mod_grad.getmixcolor(wt,h,typ), s[wt*2], mod_vc.GetAlphaValue(wt,h));
+ s += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Grad_Alpha_spFF_noBody_0(RasterizerNfo& rnfo)
+{
+ double hfull = (double)rnfo.h;
+ byte* src = rnfo.src;
+ DWORD* dst = rnfo.dst;
+ MOD_GRADIENT mod_grad = rnfo.mod_grad;
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+
+ int typ = rnfo.typ;
+ int h = rnfo.h;
+ int w = rnfo.w;
+ while(h--)
+ {
+ for(int wt=0; wt<w; ++wt)
+ pixmix2(&dst[wt], mod_grad.getmixcolor(wt,h,typ), safe_subtract(src[wt*2+1], src[wt*2]), mod_vc.GetAlphaValue(wt,h));
+ src += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Grad_Alpha_sp_Body_0(RasterizerNfo& rnfo)
+{
+ double hfull = (double)rnfo.h;
+ MOD_GRADIENT mod_grad = rnfo.mod_grad;
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+
+ int gran = min(rnfo.sw[3]+1-rnfo.xo,rnfo.w);
+ byte* s = rnfo.s;
+ DWORD* dst = rnfo.dst;
+
+ int h = rnfo.h;
+ int w = rnfo.w;
+
+ while(h--)
+ {
+ for(int wt=0; wt<gran; ++wt)
+ pixmix2(&dst[wt], mod_grad.getmixcolor(wt,h,0), s[wt*2], mod_vc.GetAlphaValue(wt,h));
+ for(int wt=gran; wt<w; ++wt)
+ pixmix2(&dst[wt], mod_grad.getmixcolor(wt,h,1), s[wt*2], mod_vc.GetAlphaValue(wt,h));
+ s += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Grad_Alpha_sp_noBody_0(RasterizerNfo& rnfo)
+{
+ double hfull = (double)rnfo.h;
+ MOD_GRADIENT mod_grad = rnfo.mod_grad;
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+
+ int gran = min(rnfo.sw[3]+1-rnfo.xo,rnfo.w);
+ byte* src = rnfo.src;
+ DWORD* dst = rnfo.dst;
+
+ int h = rnfo.h;
+ int w = rnfo.w;
+
+ while(h--)
+ {
+ for(int wt=0; wt<gran; ++wt)
+ pixmix2(&dst[wt], mod_grad.getmixcolor(wt,h,0), safe_subtract(src[wt*2+1], src[wt*2]), mod_vc.GetAlphaValue(wt,h));
+ for(int wt=gran; wt<w; ++wt)
+ pixmix2(&dst[wt], mod_grad.getmixcolor(wt,h,1), safe_subtract(src[wt*2+1], src[wt*2]), mod_vc.GetAlphaValue(wt,h));
+ src += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+// == SSE2 func ==
+void Rasterizer::Draw_Grad_noAlpha_spFF_Body_sse2(RasterizerNfo& rnfo)
+{
+ double hfull = (double)rnfo.h;
+ MOD_GRADIENT mod_grad = rnfo.mod_grad;
+ int typ = rnfo.typ;
+
+ byte* s = rnfo.s;
+ DWORD* dst = rnfo.dst;
+
+ int h = rnfo.h;
+ int w = rnfo.w;
+ while(h--)
+ {
+ for(int wt=0; wt<w; ++wt)
+ pixmix_sse2(&dst[wt], mod_grad.getmixcolor(wt,h,typ), s[wt*2]);
+ s += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Grad_noAlpha_spFF_noBody_sse2(RasterizerNfo& rnfo)
+{
+ double hfull = (double)rnfo.h;
+ MOD_GRADIENT mod_grad = rnfo.mod_grad;
+ int typ = rnfo.typ;
+
+ byte* src = rnfo.src;
+ DWORD* dst = rnfo.dst;
+
+ int h = rnfo.h;
+ int w = rnfo.w;
+ while(h--)
+ {
+ for(int wt=0; wt<w; ++wt)
+ pixmix_sse2(&dst[wt], mod_grad.getmixcolor(wt,h,typ), safe_subtract_sse2(src[wt*2+1], src[wt*2]));
+ src += 2*rnfo.overlayp;
+
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Grad_noAlpha_sp_Body_sse2(RasterizerNfo& rnfo)
+{
+ double hfull = (double)rnfo.h;
+ MOD_GRADIENT mod_grad = rnfo.mod_grad;
+
+ byte* s = rnfo.s;
+ DWORD* dst = rnfo.dst;
+
+ int h = rnfo.h;
+ int w = rnfo.w;
+ int gran = min(rnfo.sw[3]+1-rnfo.xo,rnfo.w);
+ while(h--)
+ {
+ for(int wt=0; wt<gran; ++wt)
+ pixmix_sse2(&dst[wt], mod_grad.getmixcolor(wt,h,0), s[wt*2]);
+ for(int wt=gran; wt<w; ++wt)
+ pixmix_sse2(&dst[wt], mod_grad.getmixcolor(wt,h,1), s[wt*2]);
+ s += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Grad_noAlpha_sp_noBody_sse2(RasterizerNfo& rnfo)
+{
+ double hfull = (double)rnfo.h;
+ MOD_GRADIENT mod_grad = rnfo.mod_grad;
+
+ byte* src = rnfo.src;
+ DWORD* dst = rnfo.dst;
+
+ int h = rnfo.h;
+ int w = rnfo.w;
+ int gran = min(rnfo.sw[3]+1-rnfo.xo,rnfo.w);
+ while(h--)
+ {
+ for(int wt=0; wt<gran; ++wt)
+ pixmix_sse2(&dst[wt], mod_grad.getmixcolor(wt,h,0), safe_subtract_sse2(src[wt*2+1], src[wt*2]));
+ for(int wt=gran; wt<w; ++wt)
+ pixmix_sse2(&dst[wt], mod_grad.getmixcolor(wt,h,1), safe_subtract_sse2(src[wt*2+1], src[wt*2]));
+ src += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Grad_Alpha_spFF_Body_sse2(RasterizerNfo& rnfo)
+//(int w, int h, int overlayp, int typ, MOD_GRADIENT& mod_grad, int pitch, DWORD* dst, const byte* s, MOD_MOVEVC& mod_vc)
+{
+ double hfull = (double)rnfo.h;
+ byte* s = rnfo.s;
+ DWORD* dst = rnfo.dst;
+ MOD_GRADIENT mod_grad = rnfo.mod_grad;
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+
+ int typ = rnfo.typ;
+ int h = rnfo.h;
+ int w = rnfo.w;
+ while(h--)
+ {
+ for(int wt=0; wt<w; ++wt)
+ pixmix2_sse2(&dst[wt], mod_grad.getmixcolor(wt,h,typ), s[wt*2], mod_vc.GetAlphaValue(wt,h));
+ s += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Grad_Alpha_spFF_noBody_sse2(RasterizerNfo& rnfo)
+//(int w, int h, int overlayp, int typ, MOD_GRADIENT& mod_grad, int pitch, DWORD* dst, const byte* src, MOD_MOVEVC& mod_vc)
+{
+ double hfull = (double)rnfo.h;
+ byte* src = rnfo.src;
+ DWORD* dst = rnfo.dst;
+ MOD_GRADIENT mod_grad = rnfo.mod_grad;
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+
+ int typ = rnfo.typ;
+ int h = rnfo.h;
+ int w = rnfo.w;
+ while(h--)
+ {
+ for(int wt=0; wt<w; ++wt)
+ pixmix2_sse2(&dst[wt], mod_grad.getmixcolor(wt,h,typ), safe_subtract_sse2(src[wt*2+1], src[wt*2]), mod_vc.GetAlphaValue(wt,h));
+ src += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Grad_Alpha_sp_Body_sse2(RasterizerNfo& rnfo)
+{
+ double hfull = (double)rnfo.h;
+ MOD_GRADIENT mod_grad = rnfo.mod_grad;
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+
+ byte* s = rnfo.s;
+ DWORD* dst = rnfo.dst;
+
+ int h = rnfo.h;
+ int w = rnfo.w;
+ int gran = min(rnfo.sw[3]+1-rnfo.xo,rnfo.w);
+ while(h--)
+ {
+ for(int wt=0; wt<gran; ++wt)
+ pixmix2_sse2(&dst[wt], mod_grad.getmixcolor(wt,h,0), s[wt*2], mod_vc.GetAlphaValue(wt,h));
+ for(int wt=gran; wt<w; ++wt)
+ pixmix2_sse2(&dst[wt], mod_grad.getmixcolor(wt,h,1), s[wt*2], mod_vc.GetAlphaValue(wt,h));
+ s += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+
+void Rasterizer::Draw_Grad_Alpha_sp_noBody_sse2(RasterizerNfo& rnfo)
+{
+ double hfull = (double)rnfo.h;
+ MOD_GRADIENT mod_grad = rnfo.mod_grad;
+ MOD_MOVEVC mod_vc = rnfo.mod_vc;
+
+ byte* src = rnfo.src;
+ DWORD* dst = rnfo.dst;
+
+ int h = rnfo.h;
+ int w = rnfo.w;
+ int gran = min(rnfo.sw[3]+1-rnfo.xo,rnfo.w);
+ while(h--)
+ {
+ for(int wt=0; wt<gran; ++wt)
+ pixmix2_sse2(&dst[wt], mod_grad.getmixcolor(wt,h,0), safe_subtract_sse2(src[wt*2+1], src[wt*2]), mod_vc.GetAlphaValue(wt,h));
+ for(int wt=gran; wt<w; ++wt)
+ pixmix2_sse2(&dst[wt], mod_grad.getmixcolor(wt,h,1), safe_subtract_sse2(src[wt*2+1], src[wt*2]), mod_vc.GetAlphaValue(wt,h));
+ src += 2*rnfo.overlayp;
+ dst = (DWORD*)((char *)dst + rnfo.pitch);
+ }
+}
+#endif
+// Render a subpicture onto a surface.
+// spd is the surface to render on.
+// clipRect is a rectangular clip region to render inside.
+// pAlphaMask is an alpha clipping mask.
+// xsub and ysub ???
+// switchpts seems to be an array of fill colours interlaced with coordinates.
+// switchpts[i*2] contains a colour and switchpts[i*2+1] contains the coordinate to use that colour from
+// fBody tells whether to render the body of the subs.
+// fBorder tells whether to render the border of the subs.
+#ifdef _VSMOD // patch m004. gradient colors
+CRect Rasterizer::Draw(SubPicDesc& spd, CRect& clipRect, byte* pAlphaMask, int xsub, int ysub,
+ const DWORD* switchpts, bool fBody, bool fBorder, int typ, MOD_GRADIENT& mod_grad, MOD_MOVEVC& mod_vc)
+#else
+CRect Rasterizer::Draw(SubPicDesc& spd, CRect& clipRect, byte* pAlphaMask, int xsub, int ysub,
+ const DWORD* switchpts, bool fBody, bool fBorder)
+#endif
+{
+ CRect bbox(0, 0, 0, 0);
+
+ if(!switchpts || !fBody && !fBorder) return(bbox);
+
+ // Limit drawn area to intersection of rendering surface and rectangular clip area
+ CRect r(0, 0, spd.w, spd.h);
+ r &= clipRect;
+
+ // Remember that all subtitle coordinates are specified in 1/8 pixels
+ // (x+4)>>3 rounds to nearest whole pixel.
+ // ??? What is xsub, ysub, mOffsetX and mOffsetY ?
+ int x = (xsub + mOffsetX + 4)>>3;
+ int y = (ysub + mOffsetY + 4)>>3;
+ int w = mOverlayWidth;
+ int h = mOverlayHeight;
+ int xo = 0, yo = 0;
+
+ // Again, limiting?
+ if(x < r.left) {xo = r.left-x; w -= r.left-x; x = r.left;}
+ if(y < r.top) {yo = r.top-y; h -= r.top-y; y = r.top;}
+ if(x+w > r.right) w = r.right-x;
+ if(y+h > r.bottom) h = r.bottom-y;
+
+ // Check if there's actually anything to render
+ if(w <= 0 || h <= 0) return(bbox);
+
+ bbox.SetRect(x, y, x+w, y+h);
+ bbox &= CRect(0, 0, spd.w, spd.h);
+
+ // CPUID from VDub
+ bool fSSE2 = !!(g_cpuid.m_flags & CCpuID::sse2);
+
+#ifdef _VSMOD // patch m006. moveable vector clip
+ mod_vc.hfull = h;
+ mod_vc.curpos = CPoint(x,y);
+ mod_vc.alphamask = pAlphaMask + spd.w * y + x;
+#endif
+
+ // fill rasterize info
+ RasterizerNfo rnfo;
+ // Grab the first colour
+ rnfo.color = switchpts[0];
+ // How would this differ from src?
+ rnfo.dst = (DWORD*)((char *)spd.bits + (spd.pitch * y)) + x;
+ rnfo.sw = switchpts;
+
+ rnfo.w = w;
+ rnfo.h = h;
+ rnfo.xo = xo;
+ rnfo.yo = yo;
+ rnfo.overlayp = mOverlayWidth;
+ rnfo.pitch = spd.pitch;
+ rnfo.spdw = spd.w;
+ // The alpha bitmap of the subtitles?
+ rnfo.src = mpOverlayBuffer + 2*(mOverlayWidth * yo + xo);
+ // s points to what the "body" to use is
+ // If we're rendering body fill and border, src+1 points to the array of
+ // widened regions which contain both border and fill in one.
+ rnfo.s = fBorder ? (rnfo.src+1) : rnfo.src;
+#ifdef _VSMOD // patch m006. moveable vector clip
+ rnfo.mod_vc = mod_vc;
+ rnfo.mod_grad = mod_grad;
+ rnfo.mod_grad.width = mOverlayWidth;
+ rnfo.mod_grad.height = mOverlayHeight;
+ rnfo.mod_grad.xoffset = xo;
+ rnfo.mod_grad.yoffset = yo;
+ rnfo.typ = typ;
+#else
+ // The complex "vector clip mask" I think.
+ rnfo.am = pAlphaMask + spd.w * y + x;
+#endif
+ // Every remaining line in the bitmap to be rendered...
+ // Basic case of no complex clipping mask
+#ifdef _VSMOD // patch m004. gradient colors
+ if(((typ==0)&&((mod_grad.mode[0]==0)&&(mod_grad.mode[1]==0)))||(mod_grad.mode[typ]==0))
+ // No gradient
+#endif
+ if(!pAlphaMask)
+ {
+ // If the first colour switching coordinate is at "infinite" we're
+ // never switching and can use some simpler code.
+ // ??? Is this optimisation really worth the extra readability issues it adds?
+ if(switchpts[1] == 0xFFFFFFFF)
+ {
+ // fBody is true if we're rendering a fill or a shadow.
+ if(fBody)
+ {
+ if(fSSE2)
+ { Draw_noAlpha_spFF_Body_sse2(rnfo); }
+ else
+ { Draw_noAlpha_spFF_Body_0(rnfo); }
+ }
+ // Not painting body, ie. painting border without fill in it
+ else
+ {
+ if(fSSE2)
+ { Draw_noAlpha_spFF_noBody_sse2(rnfo); }
+ else
+ { Draw_noAlpha_spFF_noBody_0(rnfo); }
+ }
+ }
+ // not (switchpts[1] == 0xFFFFFFFF)
+ else
+ {
+ // switchpts plays an important rule here
+ //const long *sw = switchpts;
+
+ if(fBody)
+ {
+ if(fSSE2)
+ { Draw_noAlpha_sp_Body_sse2(rnfo); }
+ else
+ { Draw_noAlpha_sp_Body_0(rnfo); }
+ }
+ // Not body
+ else
+ {
+ if(fSSE2)
+ { Draw_noAlpha_sp_noBody_sse2(rnfo); }
+ else
+ { Draw_noAlpha_sp_noBody_0(rnfo); }
+ }
+ }
+ }
+ // Here we *do* have an alpha mask
+ else
+ {
+ if(switchpts[1] == 0xFFFFFFFF)
+ {
+ if(fBody)
+ {
+ if(fSSE2)
+ { Draw_Alpha_spFF_Body_sse2(rnfo); }
+ else
+ { Draw_Alpha_spFF_Body_0(rnfo); }
+ }
+ else
+ {
+ if(fSSE2)
+ { Draw_Alpha_spFF_noBody_sse2(rnfo); }
+ else
+ { Draw_Alpha_spFF_noBody_0(rnfo); }
+ }
+ }
+ else
+ {
+ //const long *sw = switchpts;
+
+ if(fBody)
+ {
+ if(fSSE2)
+ { Draw_Alpha_sp_Body_sse2(rnfo); }
+ else
+ { Draw_Alpha_sp_Body_0(rnfo); }
+ }
+ else
+ {
+ if(fSSE2)
+ { Draw_Alpha_sp_noBody_sse2(rnfo); }
+ else
+ { Draw_Alpha_sp_noBody_0(rnfo); }
+ }
+ }
+ }
+#ifdef _VSMOD // patch m004. gradient colors
+ else
+ {
+ if(!pAlphaMask)
+ {
+ // If the first colour switching coordinate is at "infinite" we're
+ // never switching and can use some simpler code.
+ // ??? Is this optimisation really worth the extra readability issues it adds?
+ if(switchpts[1] == 0xFFFFFFFF)
+ {
+ // fBody is true if we're rendering a fill or a shadow.
+ if(fBody)
+ {
+ if(fSSE2)
+ { Draw_Grad_noAlpha_spFF_Body_sse2(rnfo); }
+ else
+ { Draw_Grad_noAlpha_spFF_Body_0(rnfo); }
+ }
+ // Not painting body, ie. painting border without fill in it
+ else
+ {
+ if(fSSE2)
+ { Draw_Grad_noAlpha_spFF_noBody_sse2(rnfo); }
+ else
+ { Draw_Grad_noAlpha_spFF_noBody_0(rnfo); }
+ }
+ }
+ // not (switchpts[1] == 0xFFFFFFFF)
+ else
+ {
+ // switchpts plays an important rule here
+ //const long *sw = switchpts;
+
+ if(fBody)
+ {
+ if(fSSE2)
+ { Draw_Grad_noAlpha_sp_Body_sse2(rnfo); }
+ else
+ { Draw_Grad_noAlpha_sp_Body_0(rnfo); }
+ }
+ // Not body
+ else
+ {
+ if(fSSE2)
+ { Draw_Grad_noAlpha_sp_noBody_sse2(rnfo); }
+ else
+ { Draw_Grad_noAlpha_sp_noBody_0(rnfo); }
+ }
+ }
+ }
+ // Here we *do* have an alpha mask
+ else
+ {
+ if(switchpts[1] == 0xFFFFFFFF)
+ {
+ if(fBody)
+ {
+ if(fSSE2)
+ { Draw_Grad_Alpha_spFF_Body_sse2(rnfo); }
+ else
+ { Draw_Grad_Alpha_spFF_Body_0(rnfo); }
+ }
+ else
+ {
+ if(fSSE2)
+ { Draw_Grad_Alpha_spFF_noBody_sse2(rnfo); }
+ else
+ { Draw_Grad_Alpha_spFF_noBody_0(rnfo); }
+ }
+ }
+ else
+ {
+ //const long *sw = switchpts;
+
+ if(fBody)
+ {
+ if(fSSE2)
+ { Draw_Grad_Alpha_sp_Body_sse2(rnfo); }
+ else
+ { Draw_Grad_Alpha_sp_Body_0(rnfo); }
+ }
+ else
+ {
+ if(fSSE2)
+ { Draw_Grad_Alpha_sp_noBody_sse2(rnfo); }
+ else
+ { Draw_Grad_Alpha_sp_noBody_0(rnfo); }
+ }
+ }
+ }
+ }
+
+ // patch m010. png background
+ //if(typ==0) rnfo.mod_grad.b_images[1].freeImage();
+ //rnfo.mod_grad.b_images[typ].freeImage();
+#endif
+ // Remember to EMMS!
+ // Rendering fails in funny ways if we don't do this.
+ _mm_empty();
+
+ return bbox;
+}
+
+
+#if 0
+void Rasterizer::FillSolidRect(SubPicDesc& spd, int x, int y, int nWidth, int nHeight, DWORD lColor)
+{
+ DWORD alpha = (lColor >> 24) & 0xff;
+ lColor = (255 - alpha) << 24 | lColor & 0xffffff;
+ // Warning : lColor is AARRGGBB (same format as DirectX D3DCOLOR_ARGB)
+ for (int wy=y; wy<y+nHeight; wy++)
+ {
+ DWORD* dst = (DWORD*)((BYTE*)spd.bits + spd.pitch * wy) + x;
+
+ memsetd(dst, lColor, nWidth*4);
+ }
+}
+#else
+void Rasterizer::FillSolidRect(SubPicDesc& spd, int x, int y, int nWidth, int nHeight, DWORD lColor)
+{
+ bool fSSE2 = !!(g_cpuid.m_flags & CCpuID::sse2);
+
+ if(fSSE2)
+ {
+ for (int wy=y; wy<y+nHeight; wy++)
+ {
+ DWORD* dst = (DWORD*)((BYTE*)spd.bits + spd.pitch * wy) + x;
+ for(int wt=0; wt<nWidth; ++wt)
+ pixmix_sse2(&dst[wt], lColor, 0x40); // 0x40 because >> 6 in pixmix (to preserve tranparency)
+ }
+ }
+ else
+ {
+ for (int wy=y; wy<y+nHeight; wy++)
+ {
+ DWORD* dst = (DWORD*)((BYTE*)spd.bits + spd.pitch * wy) + x;
+ for(int wt=0; wt<nWidth; ++wt)
+ pixmix(&dst[wt], lColor, 0x40);
+ }
+ }
+}
+#endif
+
+#ifdef _VSMOD // patch m006. movable vector clip
+MOD_MOVEVC::MOD_MOVEVC()
+{
+ clear();
+}
+
+void MOD_MOVEVC::clear()
+{
+ enable = false;
+ size = CSize(0,0);
+ pos = CPoint(0,0);
+// canvas = CSize(0,0);
+ spd = CSize(0,0);
+ curpos = CPoint(0,0);
+ hfull = 0;
+ alphamask = NULL;
+}
+
+byte MOD_MOVEVC::GetAlphaValue(int wx,int wy)
+{
+ byte alpham;
+ if(!enable)
+ {
+// return 0xFF;
+ return alphamask[wx+((hfull-wy)*spd.cx)];
+ }
+ if ((wx-pos.x)<-curpos.x+1) alpham=0;
+ else if ((wx-pos.x)>spd.cx-1) alpham=0;//canvas.cx
+ else if ((hfull-wy-pos.y)<-curpos.y+1) alpham=0;
+ else if ((hfull-wy-pos.y)>spd.cy-1) alpham=0;
+ else alpham=alphamask[wx-pos.x - pos.y*spd.cx+((hfull-wy)*spd.cx)];
+
+ return alpham;
+}
+#endif
+
+RasterizerNfo::RasterizerNfo()
+{
+ /*
+ w = 0;
+ h = 0;
+ spdw = 0;
+ overlayp = 0;
+ typ = 0;
+ pitch = 0;
+ color = 0;
+
+ xo = 0;
+
+ sw = NULL;
+ s = NULL;
+ src = NULL;
+ dst = NULL;
+
+#ifdef _VSMOD
+ mod_grad.clear();
+ mod_vc.clear();
+#else
+ am = NULL;
+#endif
+ */
+} \ No newline at end of file
diff --git a/src/Subtitles/Rasterizer.h b/src/Subtitles/Rasterizer.h
new file mode 100644
index 000000000..6a261fbb6
--- /dev/null
+++ b/src/Subtitles/Rasterizer.h
@@ -0,0 +1,186 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <vector>
+#include "../SubPic/ISubPic.h"
+#ifdef _VSMOD // patch m004. gradient colors
+#include "STS.h"
+#endif
+
+#ifdef _VSMOD // patch m006. moveable vector clip
+class MOD_MOVEVC
+{
+public:
+ // movevc
+ bool enable;
+ CSize size;
+ CPoint pos;
+
+ //CSize canvas; // canvas size
+ CSize spd; // output canvas size
+ CPoint curpos; // output origin point
+ int hfull; // full height
+ byte* alphamask;
+
+ MOD_MOVEVC();
+
+ byte GetAlphaValue(int wx,int wy);
+ void clear();
+};
+#endif
+
+#define PT_MOVETONC 0xfe
+#define PT_BSPLINETO 0xfc
+#define PT_BSPLINEPATCHTO 0xfa
+
+class RasterizerNfo
+{
+public:
+ int w;
+ int h;
+ int spdw;
+ int overlayp;
+ int pitch;
+ DWORD color;
+
+ int xo;
+ int yo;
+
+ const DWORD* sw;
+ byte* s;
+ byte* src;
+ DWORD* dst;
+
+#ifdef _VSMOD
+ int typ;
+ MOD_GRADIENT mod_grad;
+ MOD_MOVEVC mod_vc;
+#else
+ byte* am;
+#endif
+
+ RasterizerNfo();
+};
+
+class Rasterizer
+{
+ bool fFirstSet;
+ CPoint firstp, lastp;
+
+protected:
+ BYTE* mpPathTypes;
+ POINT* mpPathPoints;
+ size_t mPathPoints;
+
+private:
+ int mWidth, mHeight;
+
+ typedef std::pair<unsigned __int64, unsigned __int64> tSpan;
+ typedef std::vector<tSpan> tSpanBuffer;
+
+ tSpanBuffer mOutline;
+ tSpanBuffer mWideOutline;
+ int mWideBorder;
+
+ struct Edge {
+ int next;
+ int posandflag;
+ } *mpEdgeBuffer;
+ size_t mEdgeHeapSize;
+ size_t mEdgeNext;
+
+ size_t* mpScanBuffer;
+
+ typedef unsigned char byte;
+
+protected:
+ int mPathOffsetX, mPathOffsetY;
+ int mOffsetX, mOffsetY;
+ int mOverlayWidth, mOverlayHeight;
+ byte *mpOverlayBuffer;
+
+private:
+ void _TrashPath();
+ void _TrashOverlay();
+ void _ReallocEdgeBuffer(int edges);
+ void _EvaluateBezier(int ptbase, bool fBSpline);
+ void _EvaluateLine(int pt1idx, int pt2idx);
+ void _EvaluateLine(int x0, int y0, int x1, int y1);
+ static void _OverlapRegion(tSpanBuffer& dst, tSpanBuffer& src, int dx, int dy);
+ // helpers
+ void Draw_noAlpha_spFF_Body_0(RasterizerNfo& rnfo);
+ void Draw_noAlpha_spFF_noBody_0(RasterizerNfo& rnfo);
+ void Draw_noAlpha_sp_Body_0(RasterizerNfo& rnfo);
+ void Draw_noAlpha_sp_noBody_0(RasterizerNfo& rnfo);
+ void Draw_noAlpha_spFF_Body_sse2(RasterizerNfo& rnfo);
+ void Draw_noAlpha_spFF_noBody_sse2(RasterizerNfo& rnfo);
+ void Draw_noAlpha_sp_Body_sse2(RasterizerNfo& rnfo);
+ void Draw_noAlpha_sp_noBody_sse2(RasterizerNfo& rnfo);
+ void Draw_Alpha_spFF_Body_0(RasterizerNfo& rnfo);
+ void Draw_Alpha_spFF_noBody_0(RasterizerNfo& rnfo);
+ void Draw_Alpha_sp_Body_0(RasterizerNfo& rnfo);
+ void Draw_Alpha_sp_noBody_0(RasterizerNfo& rnfo);
+ void Draw_Alpha_spFF_Body_sse2(RasterizerNfo& rnfo);
+ void Draw_Alpha_spFF_noBody_sse2(RasterizerNfo& rnfo);
+ void Draw_Alpha_sp_Body_sse2(RasterizerNfo& rnfo);
+ void Draw_Alpha_sp_noBody_sse2(RasterizerNfo& rnfo);
+
+#ifdef _VSMOD // patch m004. gradient colors
+ void Draw_Grad_noAlpha_spFF_Body_0(RasterizerNfo& rnfo);
+ void Draw_Grad_noAlpha_spFF_noBody_0(RasterizerNfo& rnfo);
+ void Draw_Grad_noAlpha_sp_Body_0(RasterizerNfo& rnfo);
+ void Draw_Grad_noAlpha_sp_noBody_0(RasterizerNfo& rnfo);
+ void Draw_Grad_Alpha_spFF_Body_0(RasterizerNfo& rnfo);
+ void Draw_Grad_Alpha_spFF_noBody_0(RasterizerNfo& rnfo);
+ void Draw_Grad_Alpha_sp_Body_0(RasterizerNfo& rnfo);
+ void Draw_Grad_Alpha_sp_noBody_0(RasterizerNfo& rnfo);
+ void Draw_Grad_noAlpha_spFF_Body_sse2(RasterizerNfo& rnfo);
+ void Draw_Grad_noAlpha_spFF_noBody_sse2(RasterizerNfo& rnfo);
+ void Draw_Grad_noAlpha_sp_Body_sse2(RasterizerNfo& rnfo);
+ void Draw_Grad_noAlpha_sp_noBody_sse2(RasterizerNfo& rnfo);
+ void Draw_Grad_Alpha_spFF_Body_sse2(RasterizerNfo& rnfo);
+ void Draw_Grad_Alpha_spFF_noBody_sse2(RasterizerNfo& rnfo);
+ void Draw_Grad_Alpha_sp_Body_sse2(RasterizerNfo& rnfo);
+ void Draw_Grad_Alpha_sp_noBody_sse2(RasterizerNfo& rnfo);
+#endif
+public:
+ Rasterizer();
+ virtual ~Rasterizer();
+
+ bool BeginPath(HDC hdc);
+ bool EndPath(HDC hdc);
+ bool PartialBeginPath(HDC hdc, bool bClearPath);
+ bool PartialEndPath(HDC hdc, long dx, long dy);
+ bool ScanConvert();
+ bool CreateWidenedRegion(int borderX, int borderY);
+ void DeleteOutlines();
+ bool Rasterize(int xsub, int ysub, int fBlur, double fGaussianBlur);
+ int getOverlayWidth();
+#ifdef _VSMOD // patch m004. gradient colors
+ CRect Draw(SubPicDesc& spd, CRect& clipRect, byte* pAlphaMask, int xsub, int ysub, const DWORD* switchpts, bool fBody, bool fBorder, int typ, MOD_GRADIENT& mod_grad, MOD_MOVEVC& mod_vc);
+#else
+ CRect Draw(SubPicDesc& spd, CRect& clipRect, byte* pAlphaMask, int xsub, int ysub, const DWORD* switchpts, bool fBody, bool fBorder);
+#endif
+ void FillSolidRect(SubPicDesc& spd, int x, int y, int nWidth, int nHeight, DWORD lColor);
+};
+
diff --git a/src/Subtitles/RealTextParser.cpp b/src/Subtitles/RealTextParser.cpp
new file mode 100644
index 000000000..35bce0571
--- /dev/null
+++ b/src/Subtitles/RealTextParser.cpp
@@ -0,0 +1,635 @@
+#include "stdafx.h"
+#include "RealTextParser.h"
+
+CRealTextParser::CRealTextParser(void):
+ m_bIgnoreFont(false),
+ m_bIgnoreFontSize(false),
+ m_bIgnoreFontColor(false),
+ m_bIgnoreFontWeight(false),
+ m_bIgnoreFontFace(false),
+ m_iMinFontSize(14),
+ m_iMaxFontSize(25),
+ m_iDefaultSubtitleDurationInMillisecs(4000),
+ m_bTryToIgnoreErrors(true)
+{
+}
+
+CRealTextParser::~CRealTextParser(void)
+{
+}
+
+bool CRealTextParser::ParseRealText(wstring p_szFile)
+{
+ vector<int> vStartTimecodes;
+ vector<int> vEndTimecodes;
+ bool bPrevEndTimeMissing(false);
+ list<Tag> listTags;
+ list<Tag> listPreviousOpenTags;
+
+ while (p_szFile.length() > 0)
+ {
+ if (p_szFile.at(0) == '<')
+ {
+ Tag oTag;
+ if (!ExtractTag(p_szFile, oTag))
+ return false;
+
+ if (oTag.m_bComment)
+ continue;
+
+ if (oTag.m_szName == L"time")
+ {
+ int iStartTimecode = GetTimecode(oTag.m_mapAttributes[L"begin"]);
+ int iEndTimecode = GetTimecode(oTag.m_mapAttributes[L"end"]);
+
+// FilterReduntantTags(listTags);
+ wstring szLine = RenderTags(listTags);
+
+ if (bPrevEndTimeMissing)
+ {
+ pair<int, int> pairTimecodes(vStartTimecodes.back(), iStartTimecode);
+
+ // Fix issues where the next time code isn't valid end time code for the previous subtitle
+ if (pairTimecodes.first >= pairTimecodes.second)
+ {
+ pairTimecodes.second = pairTimecodes.first + m_iDefaultSubtitleDurationInMillisecs;
+ }
+
+ if (szLine.length() > 0)
+ m_RealText.m_mapLines[pairTimecodes] = szLine;
+
+ bPrevEndTimeMissing = false;
+ }
+ else if (vStartTimecodes.size() > 0 && vEndTimecodes.size() > 0)
+ {
+ pair<int, int> pairTimecodes(vStartTimecodes.back(), vEndTimecodes.back());
+
+ if (szLine.length() > 0)
+ m_RealText.m_mapLines[pairTimecodes] = szLine;
+
+ }
+
+ vStartTimecodes.push_back(iStartTimecode);
+ if (iEndTimecode <= 0)
+ {
+ bPrevEndTimeMissing = true;
+ }
+ else
+ {
+ vEndTimecodes.push_back(iEndTimecode);
+ }
+ }
+ else if (oTag.m_szName == L"b" || oTag.m_szName == L"i" || oTag.m_szName == L"font")
+ {
+ if (oTag.m_bOpen)
+ listPreviousOpenTags.push_back(oTag);
+
+ if (oTag.m_bClose)
+ PopTag(listPreviousOpenTags, oTag.m_szName);
+
+ listTags.push_back(oTag);
+ }
+ else if (oTag.m_szName == L"clear")
+ {
+ listTags.clear();
+
+ // set existing tags
+ listTags.insert(listTags.end(), listPreviousOpenTags.begin(), listPreviousOpenTags.end());
+ }
+ else if (oTag.m_szName == L"window")
+ {
+ if (oTag.m_bOpen)
+ m_RealText.m_WindowTag = oTag;
+
+ // Ignore close
+ }
+ else if (oTag.m_szName == L"center")
+ {
+ m_RealText.m_bCenter = true;
+ }
+ else if (oTag.m_szName == L"required")
+ {
+ // Ignore
+ }
+ else if (oTag.m_szName == L"")
+ {
+ // Ignore
+ }
+ else
+ {
+ // assume formating tag (handled later)
+ listTags.push_back(oTag);
+ }
+ }
+ else
+ {
+ Tag oTextTag;
+ if (!ExtractTextTag(p_szFile, oTextTag))
+ return false;
+
+ listTags.push_back(oTextTag);
+ }
+ }
+
+ // Handle final line
+// FilterReduntantTags(listTags);
+ wstring szLine = RenderTags(listTags);
+
+ if (bPrevEndTimeMissing)
+ {
+ pair<int, int> pairTimecodes(vStartTimecodes.back(), vStartTimecodes.back() + m_iDefaultSubtitleDurationInMillisecs);
+
+ if (szLine.length() > 0)
+ m_RealText.m_mapLines[pairTimecodes] = szLine;
+
+ bPrevEndTimeMissing = false;
+ }
+ else if (vStartTimecodes.size() > 0 && vEndTimecodes.size() > 0)
+ {
+ pair<int, int> pairTimecodes(vStartTimecodes.back(), vEndTimecodes.back());
+
+ if (szLine.length() > 0)
+ m_RealText.m_mapLines[pairTimecodes] = szLine;
+
+ }
+
+ return true;
+}
+
+const CRealTextParser::Subtitles& CRealTextParser::GetParsedSubtitles()
+{
+ return m_RealText;
+}
+
+bool CRealTextParser::ExtractTag(wstring& p_rszLine, Tag& p_rTag)
+{
+ if (p_rszLine.length() < 2 || p_rszLine.at(0) != '<')
+ {
+ if (m_bTryToIgnoreErrors)
+ {
+ size_t iTempPos = p_rszLine.find_first_of('<');
+
+ if (iTempPos != wstring::npos)
+ {
+ p_rszLine = p_rszLine.substr(iTempPos);
+
+ if (p_rszLine.length() < 2)
+ return false;
+ }
+
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ unsigned int iPos = 1;
+
+ // skip comments
+ if (p_rszLine.at(iPos) == '!')
+ {
+ p_rTag.m_bComment = true;
+
+ wstring szComment;
+ GetString(p_rszLine, iPos, szComment, L">");
+ p_rTag.m_szName = szComment;
+
+ ++iPos; // Skip >
+ p_rszLine = p_rszLine.substr(iPos);
+ return true;
+ }
+ else
+ {
+ p_rTag.m_bComment = false;
+ }
+
+ if (!SkipSpaces(p_rszLine, iPos))
+ return false;
+
+ if (p_rszLine.at(iPos) == '/')
+ {
+ p_rTag.m_bOpen = false;
+ p_rTag.m_bClose = true;
+ ++iPos;
+ }
+ else
+ {
+ p_rTag.m_bOpen = true;
+ p_rTag.m_bClose = false;
+ }
+
+ if (!GetString(p_rszLine, iPos, p_rTag.m_szName, L"\r\n\t />"))
+ return false;
+
+ p_rTag.m_szName = StringToLower(p_rTag.m_szName);
+
+ if (!GetAttributes(p_rszLine, iPos, p_rTag.m_mapAttributes))
+ return false;
+
+ if (p_rszLine.at(iPos) == '/')
+ {
+ ++iPos;
+ p_rTag.m_bClose = true;
+ }
+
+ if (p_rszLine.at(iPos) == '>')
+ {
+ ++iPos;
+ p_rszLine = p_rszLine.substr(iPos);
+ return true;
+ }
+ else
+ {
+ if (m_bTryToIgnoreErrors)
+ {
+ size_t iTempPos = p_rszLine.find_first_of('>');
+
+ if (iTempPos != wstring::npos)
+ {
+ if (iTempPos - 1 >= p_rszLine.length())
+ return false;
+
+ p_rszLine = p_rszLine.substr(iTempPos + 1);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+
+ }
+ else
+ {
+ return false;
+ }
+ }
+}
+
+bool CRealTextParser::ExtractTextTag(wstring& p_rszLine, Tag& p_rTag)
+{
+ p_rTag.m_bText = true;
+ return ExtractString(p_rszLine, p_rTag.m_szName);
+}
+
+bool CRealTextParser::ExtractString(wstring& p_rszLine, wstring& p_rszString)
+{
+ if (p_rszLine.length() == 0 || p_rszLine.at(0) == '<')
+ {
+ if (m_bTryToIgnoreErrors)
+ {
+ p_rszString = L"";
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ unsigned int iPos = 0;
+
+ if (!SkipSpaces(p_rszLine, iPos))
+ return false;
+
+ if (!GetString(p_rszLine, iPos, p_rszString, L"<"))
+ return false;
+
+ p_rszLine = p_rszLine.substr(iPos);
+ return true;
+}
+
+bool CRealTextParser::SkipSpaces(wstring& p_rszLine, unsigned int& p_riPos)
+{
+ while (p_rszLine.length() > p_riPos && iswspace(p_rszLine.at(p_riPos)))
+ {
+ ++p_riPos;
+ }
+
+ return p_rszLine.length() > p_riPos;
+}
+
+bool CRealTextParser::GetString(wstring& p_rszLine, unsigned int& p_riPos, wstring& p_rszString, const wstring& p_crszEndChars)
+{
+ while (p_rszLine.length() > p_riPos && p_crszEndChars.find(p_rszLine.at(p_riPos)) == wstring::npos)
+ {
+ p_rszString += p_rszLine.at(p_riPos);
+ ++p_riPos;
+ }
+
+ return p_rszLine.length() > p_riPos;
+}
+
+bool CRealTextParser::GetAttributes(wstring& p_rszLine, unsigned int& p_riPos, map<wstring, wstring>& p_rmapAttributes)
+{
+ if (!SkipSpaces(p_rszLine, p_riPos))
+ return false;
+
+ while (p_riPos>p_rszLine.length() && p_rszLine.at(p_riPos) != '/' && p_rszLine.at(p_riPos) != '>')
+ {
+ wstring szName;
+ if (!GetString(p_rszLine, p_riPos, szName, L"\r\n\t ="))
+ return false;
+
+ if (!SkipSpaces(p_rszLine, p_riPos))
+ return false;
+
+ if (p_rszLine.at(p_riPos) != '=')
+ {
+ if (m_bTryToIgnoreErrors)
+ {
+ p_riPos = p_rszLine.find_first_of('=', p_riPos);
+ if (p_riPos == wstring::npos)
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ++p_riPos;
+
+ if (!SkipSpaces(p_rszLine, p_riPos))
+ return false;
+
+ bool bUsesQuotes(false);
+ if (p_rszLine.at(p_riPos) == '\'' || p_rszLine.at(p_riPos) == '\"')
+ {
+ ++p_riPos;
+ bUsesQuotes = true;
+ }
+
+ if (!SkipSpaces(p_rszLine, p_riPos))
+ return false;
+
+ wstring szValue;
+ if (bUsesQuotes)
+ {
+ if (!GetString(p_rszLine, p_riPos, szValue, L"\"\'/>"))
+ return false;
+ }
+ else
+ {
+ if (!GetString(p_rszLine, p_riPos, szValue, L" \t/>"))
+ return false;
+ }
+
+ p_rmapAttributes[StringToLower(szName)] = szValue;
+
+ if (!SkipSpaces(p_rszLine, p_riPos))
+ return false;
+
+ if (p_rszLine.at(p_riPos) == '\'' || p_rszLine.at(p_riPos) == '\"')
+ ++p_riPos;
+
+ if (!SkipSpaces(p_rszLine, p_riPos))
+ return false;
+ }
+
+ return p_rszLine.length() > p_riPos;
+}
+
+int CRealTextParser::GetTimecode(const wstring& p_crszTimecode)
+{
+ int iTimecode(0);
+ int iMultiplier(1);
+
+ // Exception: if the timecode doesn't contain any separators, assume the time code is in seconds (and change multiplier to reflect that)
+ if (p_crszTimecode.find_first_of('.') == wstring::npos && p_crszTimecode.find_first_of(':') == wstring::npos)
+ iMultiplier = 1000;
+
+ wstring szCurrentPart;
+
+ for (int i = p_crszTimecode.length() - 1; i >= 0; --i)
+ {
+ if (p_crszTimecode.at(i) == '.' || p_crszTimecode.at(i) == ':')
+ {
+ if (iMultiplier == 1)
+ {
+ while (szCurrentPart.length() < 3)
+ szCurrentPart += L"0";
+ }
+
+ iTimecode += iMultiplier * ::_wtoi(szCurrentPart.c_str());
+
+ if (iMultiplier == 1)
+ {
+ iMultiplier = 1000;
+ }
+ else
+ {
+ iMultiplier *= 60;
+ }
+
+ szCurrentPart = L"";
+ }
+ else
+ {
+ szCurrentPart = p_crszTimecode.substr(i, 1) + szCurrentPart;
+ }
+ }
+
+ iTimecode += iMultiplier * ::_wtoi(szCurrentPart.c_str());
+
+ return iTimecode;
+}
+
+wstring CRealTextParser::FormatTimecode(int iTimecode,
+ int iMillisecondPrecision/* = 3*/,
+ bool p_bPadZeroes/* = true*/,
+ const wstring& p_crszSeparator/* = ":"*/,
+ const wstring& p_crszMillisecondSeparator/* = "."*/)
+{
+ wostringstream ossTimecode;
+
+ int iHours = iTimecode / 1000 / 60 / 60;
+
+ ossTimecode << iHours;
+
+ int iMinutes = (iTimecode / 1000 / 60) % 60;
+
+ ossTimecode << p_crszSeparator;
+ ossTimecode << iMinutes;
+
+ int iSeconds = (iTimecode / 1000) % 60;
+
+ ossTimecode << p_crszSeparator;
+ ossTimecode << iSeconds;
+
+ int iMilliSeconds = iTimecode % 1000;
+
+ if (iMillisecondPrecision < 3)
+ iMilliSeconds /= 10 * (3 - iMillisecondPrecision);
+
+ ossTimecode << p_crszMillisecondSeparator;
+ ossTimecode << iMilliSeconds;
+
+ return ossTimecode.str();
+}
+
+wstring CRealTextParser::StringToLower(const wstring& p_crszString)
+{
+ wstring szLowercaseString;
+ for(unsigned int i=0; i < p_crszString.length(); ++i)
+ {
+ szLowercaseString += towlower(p_crszString.at(i));
+ }
+ return szLowercaseString;
+}
+
+wstring CRealTextParser::RenderTags(const list<Tag>& p_crlTags)
+{
+ bool bEmpty(true);
+ wstring szString;
+
+ for (list<Tag>::const_iterator iter = p_crlTags.begin(); iter != p_crlTags.end(); ++iter)
+ {
+ Tag oTag(*iter);
+
+ if (oTag.m_szName == L"br")
+ {
+ szString += L"\n";
+ }
+ else if (oTag.m_szName == L"b")
+ {
+ if (!m_bIgnoreFontWeight)
+ {
+ if (oTag.m_bOpen)
+ {
+ szString += L"<b>";
+ }
+ else if (oTag.m_bClose)
+ {
+ szString += L"</b>";
+ }
+ }
+ }
+ else if (oTag.m_szName == L"i")
+ {
+ if (!m_bIgnoreFontWeight)
+ {
+ if (oTag.m_bOpen)
+ {
+ szString += L"<i>";
+ }
+ else if (oTag.m_bClose)
+ {
+ szString += L"</i>";
+ }
+ }
+ }
+ else if (oTag.m_szName == L"font")
+ {
+ if (!m_bIgnoreFont)
+ {
+ if (oTag.m_bOpen)
+ {
+ szString += L"<font";
+ for (map<wstring, wstring>:: iterator i = oTag.m_mapAttributes.begin(); i != oTag.m_mapAttributes.end(); ++i)
+ {
+ if (m_bIgnoreFontSize && i->first == L"size")
+ continue;
+
+ if (m_bIgnoreFontColor && i->first == L"color")
+ continue;
+
+ if (m_bIgnoreFontFace && i->first == L"face")
+ continue;
+
+ if (i->first == L"size" && i->second.length() > 0 && ::iswdigit(i->second.at(0)))
+ {
+ int iSize = ::_wtoi(i->second.c_str());
+
+ if (iSize > 0 && iSize < m_iMinFontSize)
+ continue;
+
+ if (iSize > m_iMaxFontSize)
+ continue;
+ }
+
+ szString += L" ";
+ szString += i->first;
+ szString += L"=\"";
+ szString += i->second;
+ szString += L"\"";
+ }
+ szString += L">";
+ }
+
+ if (oTag.m_bClose)
+ {
+ szString += L"</font>";
+ }
+ }
+ }
+ else if (oTag.m_bText)
+ {
+ szString += oTag.m_szName;
+
+ if (!oTag.m_szName.empty())
+ bEmpty = false;
+ }
+ else
+ {
+// AfxMessageBox(CString(_T("Unknown RealText-tag: ")) + oTag.m_szName.c_str());
+ }
+ }
+
+ if (bEmpty)
+ return L"";
+ else
+ return szString;
+}
+
+bool CRealTextParser::OutputSRT(wostream& p_rOutput)
+{
+ int iCounter(1);
+ for (map<pair<int, int>, wstring>::const_iterator i = m_RealText.m_mapLines.begin();
+ i != m_RealText.m_mapLines.end();
+ ++i)
+ {
+ p_rOutput << iCounter++;
+ p_rOutput << endl;
+
+ p_rOutput << FormatTimecode(i->first.first);
+ p_rOutput << L" --> ";
+ p_rOutput << FormatTimecode(i->first.second);
+ p_rOutput << endl;
+
+ p_rOutput << i->second;
+ p_rOutput << endl;
+ p_rOutput << endl;
+ }
+
+ return true;
+}
+
+void CRealTextParser::PopTag(list<Tag>& p_rlistTags, const wstring& p_crszTagName)
+{
+ for (list<Tag>::reverse_iterator riter = p_rlistTags.rbegin(); riter != p_rlistTags.rend(); ++riter)
+ {
+ if (riter->m_szName == p_crszTagName)
+ {
+ p_rlistTags.erase((++riter).base());
+ return;
+ }
+ }
+}
+
+void CRealTextParser::FilterReduntantTags(list<Tag>& p_rlistTags)
+{
+ list<Tag>::iterator iterPrev;
+ for (list<Tag>::iterator iterCurrent = p_rlistTags.begin(); iterCurrent != p_rlistTags.end(); ++iterCurrent)
+ {
+ if (iterCurrent != p_rlistTags.begin())
+ {
+ if (iterPrev->m_szName == L"font" && iterCurrent->m_szName == L"font" &&
+ iterPrev->m_bOpen && iterCurrent->m_bOpen)
+ {
+ p_rlistTags.erase(iterPrev);
+ }
+ }
+ iterPrev = iterCurrent;
+ }
+}
diff --git a/src/Subtitles/RealTextParser.h b/src/Subtitles/RealTextParser.h
new file mode 100644
index 000000000..06247e430
--- /dev/null
+++ b/src/Subtitles/RealTextParser.h
@@ -0,0 +1,102 @@
+#pragma once
+
+#include <sstream>
+using std::wostream;
+using std::wostringstream;
+using std::endl;
+
+#include <string>
+using std::wstring;
+
+#include <map>
+using std::map;
+using std::pair;
+
+#include <vector>
+using std::vector;
+
+#include <list>
+using std::list;
+
+#include <cwctype>
+using std::towlower;
+
+class CRealTextParser
+{
+public:
+ CRealTextParser();
+ virtual ~CRealTextParser(void);
+
+ struct Tag
+ {
+ Tag(): m_bOpen(false), m_bClose(false), m_bComment(false), m_bText(false) {}
+
+ wstring m_szName;
+
+ bool m_bOpen;
+ bool m_bClose;
+
+ bool m_bComment;
+ bool m_bText;
+
+ map<wstring, wstring> m_mapAttributes;
+ };
+
+ struct Subtitles
+ {
+ Subtitles(): m_WindowTag(), m_FontTag(), m_bCenter(false) {}
+
+ Tag m_WindowTag;
+ Tag m_FontTag;
+
+ bool m_bCenter;
+
+ map<pair<int, int>, wstring> m_mapLines;
+ };
+
+ bool ParseRealText(wstring p_szFile);
+
+ const Subtitles& GetParsedSubtitles();
+
+ bool OutputSRT(wostream& p_rOutput);
+
+private:
+ bool ExtractTag(wstring& p_rszLine, Tag& p_rTag);
+ bool ExtractTextTag(wstring& p_rszLine, Tag& p_rTag);
+ bool ExtractString(wstring& p_rszLine, wstring& p_rszString);
+ bool SkipSpaces(wstring& p_rszLine, unsigned int& p_riPos);
+ bool GetString(wstring& p_rszLine, unsigned int& p_riPos, wstring& p_rszString, const wstring& p_crszEndChars);
+ bool GetAttributes(wstring& p_rszLine, unsigned int& p_riPos, map<wstring, wstring>& p_rmapAttributes);
+
+ int GetTimecode(const wstring& p_crszTimecode);
+ wstring FormatTimecode(int iTimecode,
+ int iMillisecondPrecision = 3,
+ bool p_bPadZeroes = true,
+ const wstring& p_crszSeparator = L":",
+ const wstring& p_crszMillisecondSeparator = L".");
+
+ wstring StringToLower(const wstring& p_crszString);
+
+ wstring RenderTags(const list<Tag>& p_crlTags);
+
+ void PopTag(list<Tag>& p_rlistTags, const wstring& p_crszTagName);
+
+ // Filter out for example multiple font tags opened previously (font tags are not always terminated properly in realtext and can build up)
+ void FilterReduntantTags(list<Tag>& p_rlistTags);
+
+
+ Subtitles m_RealText;
+
+ bool m_bIgnoreFont;
+ bool m_bIgnoreFontSize;
+ bool m_bIgnoreFontColor;
+ bool m_bIgnoreFontWeight;
+ bool m_bIgnoreFontFace;
+
+ int m_iMinFontSize;
+ int m_iMaxFontSize;
+
+ int m_iDefaultSubtitleDurationInMillisecs;
+
+ bool m_bTryToIgnoreErrors;
+};
diff --git a/src/Subtitles/RenderedHdmvSubtitle.cpp b/src/Subtitles/RenderedHdmvSubtitle.cpp
new file mode 100644
index 000000000..a41105654
--- /dev/null
+++ b/src/Subtitles/RenderedHdmvSubtitle.cpp
@@ -0,0 +1,180 @@
+/*
+ * $Id$
+ *
+ * (C) 2006-2010 see AUTHORS
+ *
+ * This file is part of mplayerc.
+ *
+ * Mplayerc 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.
+ *
+ * Mplayerc 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include "stdafx.h"
+#include "HdmvSub.h"
+#include "DVBSub.h"
+#include "RenderedHdmvSubtitle.h"
+
+CRenderedHdmvSubtitle::CRenderedHdmvSubtitle(CCritSec* pLock, SUBTITLE_TYPE nType)
+ : CSubPicProviderImpl(pLock)
+{
+ switch (nType)
+ {
+ case ST_DVB :
+ m_pSub = DNew CDVBSub();
+ m_name = "DVB Embedded Subtitle";
+ break;
+ case ST_HDMV :
+ m_pSub = DNew CHdmvSub();
+ m_name = "HDMV Embedded Subtitle";
+ break;
+ default :
+ ASSERT (FALSE);
+ m_pSub = NULL;
+ }
+ m_rtStart = 0;
+}
+
+CRenderedHdmvSubtitle::~CRenderedHdmvSubtitle(void)
+{
+ delete m_pSub;
+}
+
+
+STDMETHODIMP CRenderedHdmvSubtitle::NonDelegatingQueryInterface(REFIID riid, void** ppv)
+{
+ CheckPointer(ppv, E_POINTER);
+ *ppv = NULL;
+
+ return
+ QI(IPersist)
+ QI(ISubStream)
+ QI(ISubPicProvider)
+ __super::NonDelegatingQueryInterface(riid, ppv);
+}
+
+// ISubPicProvider
+
+STDMETHODIMP_(POSITION) CRenderedHdmvSubtitle::GetStartPosition(REFERENCE_TIME rt, double fps)
+{
+ CAutoLock cAutoLock(&m_csCritSec);
+ return m_pSub->GetStartPosition(rt - m_rtStart, fps);
+}
+
+STDMETHODIMP_(POSITION) CRenderedHdmvSubtitle::GetNext(POSITION pos)
+{
+ CAutoLock cAutoLock(&m_csCritSec);
+ return m_pSub->GetNext (pos);
+}
+
+STDMETHODIMP_(REFERENCE_TIME) CRenderedHdmvSubtitle::GetStart(POSITION pos, double fps)
+{
+ CAutoLock cAutoLock(&m_csCritSec);
+ return m_pSub->GetStart(pos) + m_rtStart;
+}
+
+STDMETHODIMP_(REFERENCE_TIME) CRenderedHdmvSubtitle::GetStop(POSITION pos, double fps)
+{
+ CAutoLock cAutoLock(&m_csCritSec);
+ return m_pSub->GetStop(pos) + m_rtStart;
+}
+
+STDMETHODIMP_(bool) CRenderedHdmvSubtitle::IsAnimated(POSITION pos)
+{
+ return(false);
+}
+
+STDMETHODIMP CRenderedHdmvSubtitle::Render(SubPicDesc& spd, REFERENCE_TIME rt, double fps, RECT& bbox)
+{
+ CAutoLock cAutoLock(&m_csCritSec);
+ m_pSub->Render (spd, rt - m_rtStart, bbox);
+
+ return S_OK;
+}
+
+STDMETHODIMP CRenderedHdmvSubtitle::GetTextureSize (POSITION pos, SIZE& MaxTextureSize, SIZE& VideoSize, POINT& VideoTopLeft)
+{
+ CAutoLock cAutoLock(&m_csCritSec);
+ HRESULT hr = m_pSub->GetTextureSize(pos, MaxTextureSize, VideoSize, VideoTopLeft);
+ return hr;
+};
+
+// IPersist
+
+STDMETHODIMP CRenderedHdmvSubtitle::GetClassID(CLSID* pClassID)
+{
+ return pClassID ? *pClassID = __uuidof(this), S_OK : E_POINTER;
+}
+
+// ISubStream
+
+STDMETHODIMP_(int) CRenderedHdmvSubtitle::GetStreamCount()
+{
+ return (1);
+}
+
+STDMETHODIMP CRenderedHdmvSubtitle::GetStreamInfo(int iStream, WCHAR** ppName, LCID* pLCID)
+{
+ if(iStream != 0) return E_INVALIDARG;
+
+ if(ppName)
+ {
+ *ppName = (WCHAR*)CoTaskMemAlloc((m_name.GetLength()+1)*sizeof(WCHAR));
+ if(!(*ppName))
+ return E_OUTOFMEMORY;
+
+ wcscpy_s (*ppName, m_name.GetLength()+1, CStringW(m_name));
+ }
+
+ if(pLCID)
+ {
+ *pLCID = m_lcid;
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP_(int) CRenderedHdmvSubtitle::GetStream()
+{
+ return(0);
+}
+
+STDMETHODIMP CRenderedHdmvSubtitle::SetStream(int iStream)
+{
+ return iStream == 0 ? S_OK : E_FAIL;
+}
+
+STDMETHODIMP CRenderedHdmvSubtitle::Reload()
+{
+ return S_OK;
+}
+
+HRESULT CRenderedHdmvSubtitle::ParseSample (IMediaSample* pSample)
+{
+ CAutoLock cAutoLock(&m_csCritSec);
+ HRESULT hr;
+
+ hr = m_pSub->ParseSample (pSample);
+ return hr;
+}
+
+HRESULT CRenderedHdmvSubtitle::NewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate)
+{
+ CAutoLock cAutoLock(&m_csCritSec);
+
+ m_pSub->Reset();
+ m_rtStart = tStart;
+ return S_OK;
+}
+
diff --git a/src/Subtitles/RenderedHdmvSubtitle.h b/src/Subtitles/RenderedHdmvSubtitle.h
new file mode 100644
index 000000000..7b7760495
--- /dev/null
+++ b/src/Subtitles/RenderedHdmvSubtitle.h
@@ -0,0 +1,71 @@
+/*
+ * $Id$
+ *
+ * (C) 2006-2010 see AUTHORS
+ *
+ * This file is part of mplayerc.
+ *
+ * Mplayerc 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.
+ *
+ * Mplayerc 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#pragma once
+
+#include "Rasterizer.h"
+#include "../SubPic/SubPicProviderImpl.h"
+#include "HdmvSub.h"
+#include "BaseSub.h"
+
+
+class __declspec(uuid("FCA68599-C83E-4ea5-94A3-C2E1B0E326B9"))
+CRenderedHdmvSubtitle : public CSubPicProviderImpl, public ISubStream
+{
+public:
+ CRenderedHdmvSubtitle(CCritSec* pLock, SUBTITLE_TYPE nType);
+ ~CRenderedHdmvSubtitle(void);
+
+ DECLARE_IUNKNOWN
+ STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void** ppv);
+
+ // ISubPicProvider
+ STDMETHODIMP_(POSITION) GetStartPosition(REFERENCE_TIME rt, double fps);
+ STDMETHODIMP_(POSITION) GetNext(POSITION pos);
+ STDMETHODIMP_(REFERENCE_TIME) GetStart(POSITION pos, double fps);
+ STDMETHODIMP_(REFERENCE_TIME) GetStop(POSITION pos, double fps);
+ STDMETHODIMP_(bool) IsAnimated(POSITION pos);
+ STDMETHODIMP Render(SubPicDesc& spd, REFERENCE_TIME rt, double fps, RECT& bbox);
+ STDMETHODIMP GetTextureSize (POSITION pos, SIZE& MaxTextureSize, SIZE& VirtualSize, POINT& VirtualTopLeft);
+
+ // IPersist
+ STDMETHODIMP GetClassID(CLSID* pClassID);
+
+ // ISubStream
+ STDMETHODIMP_(int) GetStreamCount();
+ STDMETHODIMP GetStreamInfo(int i, WCHAR** ppName, LCID* pLCID);
+ STDMETHODIMP_(int) GetStream();
+ STDMETHODIMP SetStream(int iStream);
+ STDMETHODIMP Reload();
+
+ HRESULT ParseSample (IMediaSample* pSample);
+ HRESULT NewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate);
+
+private :
+ CString m_name;
+ LCID m_lcid;
+ REFERENCE_TIME m_rtStart;
+
+ CBaseSub* m_pSub;
+ CCritSec m_csCritSec;
+};
diff --git a/src/Subtitles/SSF.cpp b/src/Subtitles/SSF.cpp
new file mode 100644
index 000000000..3e5cc062b
--- /dev/null
+++ b/src/Subtitles/SSF.cpp
@@ -0,0 +1,243 @@
+/*
+ * 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
+ *
+ * TODO:
+ * - fill effect
+ * - outline bkg still very slow
+ *
+ */
+
+#include "stdafx.h"
+#include <xmmintrin.h>
+#include <emmintrin.h>
+#include "SSF.h"
+#include "../SubPic/MemSubPic.h"
+
+namespace ssf
+{
+ CRenderer::CRenderer(CCritSec* pLock)
+ : CSubPicProviderImpl(pLock)
+ {
+ }
+
+ CRenderer::~CRenderer()
+ {
+ }
+
+ bool CRenderer::Open(CString fn, CString name)
+ {
+ m_fn.Empty();
+ m_name.Empty();
+ m_file.Free();
+ m_renderer.Free();
+
+ if(name.IsEmpty())
+ {
+ CString str = fn;
+ str.Replace('\\', '/');
+ name = str.Left(str.ReverseFind('.'));
+ name = name.Mid(name.ReverseFind('/')+1);
+ name = name.Mid(name.ReverseFind('.')+1);
+ }
+
+ try
+ {
+ if(Open(FileInputStream(fn), name))
+ {
+ m_fn = fn;
+ return true;
+ }
+ }
+ catch(Exception& e)
+ {
+ UNREFERENCED_PARAMETER(e);
+ TRACE(_T("%s\n"), e.ToString());
+ }
+
+ return false;
+ }
+
+ bool CRenderer::Open(InputStream& s, CString name)
+ {
+ m_fn.Empty();
+ m_name.Empty();
+ m_file.Free();
+ m_renderer.Free();
+
+ try
+ {
+ m_file.Attach(DNew SubtitleFile());
+ m_file->Parse(s);
+ m_renderer.Attach(DNew Renderer());
+ m_name = name;
+ return true;
+ }
+ catch(Exception& e)
+ {
+ UNREFERENCED_PARAMETER(e);
+ TRACE(_T("%s\n"), e.ToString());
+ }
+
+ return false;
+ }
+
+ void CRenderer::Append(REFERENCE_TIME rtStart, REFERENCE_TIME rtStop, LPCWSTR str)
+ {
+ if(!m_file) return;
+
+ try
+ {
+ m_file->Append(ssf::WCharInputStream(str), (float)rtStart / 10000000, (float)rtStop / 10000000);
+ }
+ catch(Exception& e)
+ {
+ UNREFERENCED_PARAMETER(e);
+ TRACE(_T("%s\n"), e.ToString());
+ }
+ }
+
+ STDMETHODIMP CRenderer::NonDelegatingQueryInterface(REFIID riid, void** ppv)
+ {
+ CheckPointer(ppv, E_POINTER);
+ *ppv = NULL;
+
+ return
+ QI(IPersist)
+ QI(ISubStream)
+ QI(ISubPicProvider)
+ __super::NonDelegatingQueryInterface(riid, ppv);
+ }
+
+ // ISubPicProvider
+
+ STDMETHODIMP_(POSITION) CRenderer::GetStartPosition(REFERENCE_TIME rt, double fps)
+ {
+ size_t k;
+ return m_file && m_file->m_segments.Lookup((float)rt/10000000, k) ? (POSITION)(++k) : NULL;
+ }
+
+ STDMETHODIMP_(POSITION) CRenderer::GetNext(POSITION pos)
+ {
+ size_t k = (size_t)pos;
+ return m_file && m_file->m_segments.GetSegment(k) ? (POSITION)(++k) : NULL;
+ }
+
+ STDMETHODIMP_(REFERENCE_TIME) CRenderer::GetStart(POSITION pos, double fps)
+ {
+ size_t k = (size_t)pos-1;
+ const SubtitleFile::Segment* s = m_file ? m_file->m_segments.GetSegment(k) : NULL;
+ return s ? (REFERENCE_TIME)(s->m_start*10000000) : 0;
+ }
+
+ STDMETHODIMP_(REFERENCE_TIME) CRenderer::GetStop(POSITION pos, double fps)
+ {
+ CheckPointer(m_file, 0);
+
+ size_t k = (size_t)pos-1;
+ const SubtitleFile::Segment* s = m_file ? m_file->m_segments.GetSegment(k) : NULL;
+ return s ? (REFERENCE_TIME)(s->m_stop*10000000) : 0;
+ }
+
+ STDMETHODIMP_(bool) CRenderer::IsAnimated(POSITION pos)
+ {
+ return true;
+ }
+
+ STDMETHODIMP CRenderer::Render(SubPicDesc& spd, REFERENCE_TIME rt, double fps, RECT& bbox)
+ {
+ CheckPointer(m_file, E_UNEXPECTED);
+ CheckPointer(m_renderer, E_UNEXPECTED);
+
+ if(spd.type != MSP_RGB32) return E_INVALIDARG;
+
+ CAutoLock csAutoLock(m_pLock);
+
+ CRect bbox2;
+ bbox2.SetRectEmpty();
+
+ CAutoPtrList<Subtitle> subs;
+ m_file->Lookup((float)rt/10000000, subs);
+
+ m_renderer->NextSegment(subs);
+
+ POSITION pos = subs.GetHeadPosition();
+ while(pos)
+ {
+ const Subtitle* s = subs.GetNext(pos);
+ const RenderedSubtitle* rs = m_renderer->Lookup(s, CSize(spd.w, spd.h), spd.vidrect);
+ if(rs) bbox2 |= rs->Draw(spd);
+ }
+
+ bbox = bbox2 & CRect(0, 0, spd.w, spd.h);
+
+ return S_OK;
+ }
+
+ // IPersist
+
+ STDMETHODIMP CRenderer::GetClassID(CLSID* pClassID)
+ {
+ return pClassID ? *pClassID = __uuidof(this), S_OK : E_POINTER;
+ }
+
+ // ISubStream
+
+ STDMETHODIMP_(int) CRenderer::GetStreamCount()
+ {
+ return 1;
+ }
+
+ STDMETHODIMP CRenderer::GetStreamInfo(int iStream, WCHAR** ppName, LCID* pLCID)
+ {
+ if(iStream != 0) return E_INVALIDARG;
+
+ if(ppName)
+ {
+ *ppName = (WCHAR*)CoTaskMemAlloc((m_name.GetLength()+1)*sizeof(WCHAR));
+ if(!(*ppName))
+ return E_OUTOFMEMORY;
+
+ wcscpy(*ppName, CStringW(m_name));
+ }
+
+ if(pLCID)
+ {
+ *pLCID = 0; // TODO
+ }
+
+ return S_OK;
+ }
+
+ STDMETHODIMP_(int) CRenderer::GetStream()
+ {
+ return 0;
+ }
+
+ STDMETHODIMP CRenderer::SetStream(int iStream)
+ {
+ return iStream == 0 ? S_OK : E_FAIL;
+ }
+
+ STDMETHODIMP CRenderer::Reload()
+ {
+ CAutoLock csAutoLock(m_pLock);
+
+ return !m_fn.IsEmpty() && Open(m_fn, m_name) ? S_OK : E_FAIL;
+ }
+}
diff --git a/src/Subtitles/SSF.h b/src/Subtitles/SSF.h
new file mode 100644
index 000000000..cd4cf7c3e
--- /dev/null
+++ b/src/Subtitles/SSF.h
@@ -0,0 +1,70 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include "../SubPic/SubPicProviderImpl.h"
+#include "./libssf/SubtitleFile.h"
+#include "./libssf/Renderer.h"
+
+#pragma once
+
+namespace ssf
+{
+ class __declspec(uuid("E0593632-0AB7-47CA-8BE1-E9D2A6A4825E"))
+ CRenderer : public CSubPicProviderImpl, public ISubStream
+ {
+ CString m_fn, m_name;
+ CAutoPtr<SubtitleFile> m_file;
+ CAutoPtr<Renderer> m_renderer;
+
+ public:
+ CRenderer(CCritSec* pLock);
+ virtual ~CRenderer();
+
+ bool Open(CString fn, CString name = _T(""));
+ bool Open(InputStream& s, CString name);
+
+ void Append(REFERENCE_TIME rtStart, REFERENCE_TIME rtStop, LPCWSTR str);
+
+ DECLARE_IUNKNOWN
+ STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void** ppv);
+
+ // ISubPicProvider
+ STDMETHODIMP_(POSITION) GetStartPosition(REFERENCE_TIME rt, double fps);
+ STDMETHODIMP_(POSITION) GetNext(POSITION pos);
+ STDMETHODIMP_(REFERENCE_TIME) GetStart(POSITION pos, double fps);
+ STDMETHODIMP_(REFERENCE_TIME) GetStop(POSITION pos, double fps);
+ STDMETHODIMP_(bool) IsAnimated(POSITION pos);
+ STDMETHODIMP Render(SubPicDesc& spd, REFERENCE_TIME rt, double fps, RECT& bbox);
+
+ // IPersist
+ STDMETHODIMP GetClassID(CLSID* pClassID);
+
+ // ISubStream
+ STDMETHODIMP_(int) GetStreamCount();
+ STDMETHODIMP GetStreamInfo(int i, WCHAR** ppName, LCID* pLCID);
+ STDMETHODIMP_(int) GetStream();
+ STDMETHODIMP SetStream(int iStream);
+ STDMETHODIMP Reload();
+ };
+
+} \ No newline at end of file
diff --git a/src/Subtitles/STS.cpp b/src/Subtitles/STS.cpp
new file mode 100644
index 000000000..b679856d9
--- /dev/null
+++ b/src/Subtitles/STS.cpp
@@ -0,0 +1,3802 @@
+/*
+ * 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 "STS.h"
+#include <atlbase.h>
+
+#include "RealTextParser.h"
+#include <fstream>
+
+// gathered from http://www.netwave.or.jp/~shikai/shikai/shcolor.htm
+
+struct htmlcolor {TCHAR* name; DWORD color;} hmtlcolors[] =
+{
+ {_T("white"), 0xffffff},
+ {_T("whitesmoke"), 0xf5f5f5},
+ {_T("ghostwhite"), 0xf8f8ff},
+ {_T("snow"), 0xfffafa},
+ {_T("gainsboro"), 0xdcdcdc},
+ {_T("lightgrey"), 0xd3d3d3},
+ {_T("silver"), 0xc0c0c0},
+ {_T("darkgray"), 0xa9a9a9},
+ {_T("gray"), 0x808080},
+ {_T("dimgray"), 0x696969},
+ {_T("lightslategray"), 0x778899},
+ {_T("slategray"), 0x708090},
+ {_T("darkslategray"), 0x2f4f4f},
+ {_T("black"), 0x000000},
+
+ {_T("azure"), 0xf0ffff},
+ {_T("aliceblue"), 0xf0f8ff},
+ {_T("mintcream"), 0xf5fffa},
+ {_T("honeydew"), 0xf0fff0},
+ {_T("lightcyan"), 0xe0ffff},
+ {_T("paleturqoise"), 0xafeeee},
+ {_T("powderblue"), 0xb0e0e6},
+ {_T("lightblue"), 0xadd8ed},
+ {_T("lightsteelblue"), 0xb0c4de},
+ {_T("skyblue"), 0x87ceeb},
+ {_T("lightskyblue"), 0x87cefa},
+ {_T("cyan"), 0x00ffff},
+ {_T("aqua"), 0x00ff80},
+ {_T("deepskyblue"), 0x00bfff},
+ {_T("aquamarine"), 0x7fffd4},
+ {_T("turquoise"), 0x40e0d0},
+ {_T("darkturquoise"), 0x00ced1},
+ {_T("lightseagreen"), 0x20b2aa},
+ {_T("mediumturquoise"), 0x40e0dd},
+ {_T("mediumaquamarine"), 0x66cdaa},
+ {_T("cadetblue"), 0x5f9ea0},
+ {_T("teal"), 0x008080},
+ {_T("darkcyan"), 0x008b8b},
+ {_T("comflowerblue"), 0x6495ed},
+ {_T("dodgerblue"), 0x1e90ff},
+ {_T("steelblue"), 0x4682b4},
+ {_T("royalblue"), 0x4169e1},
+ {_T("blue"), 0x0000ff},
+ {_T("mediumblue"), 0x0000cd},
+ {_T("mediumslateblue"), 0x7b68ee},
+ {_T("slateblue"), 0x6a5acd},
+ {_T("darkslateblue"), 0x483d8b},
+ {_T("darkblue"), 0x00008b},
+ {_T("midnightblue"), 0x191970},
+ {_T("navy"), 0x000080},
+
+ {_T("palegreen"), 0x98fb98},
+ {_T("lightgreen"), 0x90ee90},
+ {_T("mediumspringgreen"), 0x00fa9a},
+ {_T("springgreen"), 0x00ff7f},
+ {_T("chartreuse"), 0x7fff00},
+ {_T("lawngreen"), 0x7cfc00},
+ {_T("lime"), 0x00ff00},
+ {_T("limegreen"), 0x32cd32},
+ {_T("greenyellow"), 0xadff2f},
+ {_T("yellowgreen"), 0x9acd32},
+ {_T("darkseagreen"), 0x8fbc8f},
+ {_T("mediumseagreen"), 0x3cb371},
+ {_T("seagreen"), 0x2e8b57},
+ {_T("olivedrab"), 0x6b8e23},
+ {_T("forestgreen"), 0x228b22},
+ {_T("green"), 0x008000},
+ {_T("darkkhaki"), 0xbdb76b},
+ {_T("olive"), 0x808000},
+ {_T("darkolivegreen"), 0x556b2f},
+ {_T("darkgreen"), 0x006400},
+
+ {_T("floralwhite"), 0xfffaf0},
+ {_T("seashell"), 0xfff5ee},
+ {_T("ivory"), 0xfffff0},
+ {_T("beige"), 0xf5f5dc},
+ {_T("cornsilk"), 0xfff8dc},
+ {_T("lemonchiffon"), 0xfffacd},
+ {_T("lightyellow"), 0xffffe0},
+ {_T("lightgoldenrodyellow"), 0xfafad2},
+ {_T("papayawhip"), 0xffefd5},
+ {_T("blanchedalmond"), 0xffedcd},
+ {_T("palegoldenrod"), 0xeee8aa},
+ {_T("khaki"), 0xf0eb8c},
+ {_T("bisque"), 0xffe4c4},
+ {_T("moccasin"), 0xffe4b5},
+ {_T("navajowhite"), 0xffdead},
+ {_T("peachpuff"), 0xffdab9},
+ {_T("yellow"), 0xffff00},
+ {_T("gold"), 0xffd700},
+ {_T("wheat"), 0xf5deb3},
+ {_T("orange"), 0xffa500},
+ {_T("darkorange"), 0xff8c00},
+
+ {_T("oldlace"), 0xfdf5e6},
+ {_T("linen"), 0xfaf0e6},
+ {_T("antiquewhite"), 0xfaebd7},
+ {_T("lightsalmon"), 0xffa07a},
+ {_T("darksalmon"), 0xe9967a},
+ {_T("salmon"), 0xfa8072},
+ {_T("lightcoral"), 0xf08080},
+ {_T("indianred"), 0xcd5c5c},
+ {_T("coral"), 0xff7f50},
+ {_T("tomato"), 0xff6347},
+ {_T("orangered"), 0xff4500},
+ {_T("red"), 0xff0000},
+ {_T("crimson"), 0xdc143c},
+ {_T("firebrick"), 0xb22222},
+ {_T("maroon"), 0x800000},
+ {_T("darkred"), 0x8b0000},
+
+ {_T("lavender"), 0xe6e6fe},
+ {_T("lavenderblush"), 0xfff0f5},
+ {_T("mistyrose"), 0xffe4e1},
+ {_T("thistle"), 0xd8bfd8},
+ {_T("pink"), 0xffc0cb},
+ {_T("lightpink"), 0xffb6c1},
+ {_T("palevioletred"), 0xdb7093},
+ {_T("hotpink"), 0xff69b4},
+ {_T("fuchsia"), 0xff00ee},
+ {_T("magenta"), 0xff00ff},
+ {_T("mediumvioletred"), 0xc71585},
+ {_T("deeppink"), 0xff1493},
+ {_T("plum"), 0xdda0dd},
+ {_T("violet"), 0xee82ee},
+ {_T("orchid"), 0xda70d6},
+ {_T("mediumorchid"), 0xba55d3},
+ {_T("mediumpurple"), 0x9370db},
+ {_T("purple"), 0x9370db},
+ {_T("blueviolet"), 0x8a2be2},
+ {_T("darkviolet"), 0x9400d3},
+ {_T("darkorchid"), 0x9932cc},
+
+ {_T("tan"), 0xd2b48c},
+ {_T("burlywood"), 0xdeb887},
+ {_T("sandybrown"), 0xf4a460},
+ {_T("peru"), 0xcd853f},
+ {_T("goldenrod"), 0xdaa520},
+ {_T("darkgoldenrod"), 0xb8860b},
+ {_T("chocolate"), 0xd2691e},
+ {_T("rosybrown"), 0xbc8f8f},
+ {_T("sienna"), 0xa0522d},
+ {_T("saddlebrown"), 0x8b4513},
+ {_T("brown"), 0xa52a2a},
+};
+
+CHtmlColorMap::CHtmlColorMap()
+{
+ for(ptrdiff_t i = 0; i < countof(hmtlcolors); i++)
+ SetAt(hmtlcolors[i].name, hmtlcolors[i].color);
+}
+
+CHtmlColorMap g_colors;
+
+//
+
+BYTE CharSetList[] =
+{
+ ANSI_CHARSET,
+ DEFAULT_CHARSET,
+ SYMBOL_CHARSET,
+ SHIFTJIS_CHARSET,
+ HANGEUL_CHARSET,
+ HANGUL_CHARSET,
+ GB2312_CHARSET,
+ CHINESEBIG5_CHARSET,
+ OEM_CHARSET,
+ JOHAB_CHARSET,
+ HEBREW_CHARSET,
+ ARABIC_CHARSET,
+ GREEK_CHARSET,
+ TURKISH_CHARSET,
+ VIETNAMESE_CHARSET,
+ THAI_CHARSET,
+ EASTEUROPE_CHARSET,
+ RUSSIAN_CHARSET,
+ MAC_CHARSET,
+ BALTIC_CHARSET
+};
+
+TCHAR* CharSetNames[] =
+{
+ _T("ANSI"),
+ _T("DEFAULT"),
+ _T("SYMBOL"),
+ _T("SHIFTJIS"),
+ _T("HANGEUL"),
+ _T("HANGUL"),
+ _T("GB2312"),
+ _T("CHINESEBIG5"),
+ _T("OEM"),
+ _T("JOHAB"),
+ _T("HEBREW"),
+ _T("ARABIC"),
+ _T("GREEK"),
+ _T("TURKISH"),
+ _T("VIETNAMESE"),
+ _T("THAI"),
+ _T("EASTEUROPE"),
+ _T("RUSSIAN"),
+ _T("MAC"),
+ _T("BALTIC"),
+};
+
+int CharSetLen = countof(CharSetList);
+
+//
+
+static DWORD CharSetToCodePage(DWORD dwCharSet)
+{
+ CHARSETINFO cs={0};
+ ::TranslateCharsetInfo((DWORD *)dwCharSet, &cs, TCI_SRCCHARSET);
+ return cs.ciACP;
+}
+
+int FindChar(CStringW str, WCHAR c, int pos, bool fUnicode, int CharSet)
+{
+ if(fUnicode) return(str.Find(c, pos));
+
+ int fStyleMod = 0;
+
+ DWORD cp = CharSetToCodePage(CharSet);
+ int OrgCharSet = CharSet;
+
+ for(size_t i = 0, j = str.GetLength(), k; i < j; i++)
+ {
+ WCHAR c2 = str[i];
+
+ if(IsDBCSLeadByteEx(cp, (BYTE)c2)) i++;
+ else if(i >= pos)
+ {
+ if(c2 == c) return(i);
+ }
+
+ if(c2 == '{') fStyleMod++;
+ else if(fStyleMod > 0)
+ {
+ if(c2 == '}') fStyleMod--;
+ else if(c2 == 'e' && i >= 3 && i < j-1 && str.Mid(i-2, 3) == L"\\fe")
+ {
+ CharSet = 0;
+ for(k = i+1; _istdigit(str[k]); k++) CharSet = CharSet*10 + (str[k] - '0');
+ if(k == i+1) CharSet = OrgCharSet;
+
+ cp = CharSetToCodePage(CharSet);
+ }
+ }
+ }
+
+ return(-1);
+}
+/*
+int FindChar(CStringA str, char c, int pos, bool fUnicode, int CharSet)
+{
+ ASSERT(!fUnicode);
+
+ return(FindChar(AToW(str), c, pos, false, CharSet));
+}
+*/
+static CStringW ToMBCS(CStringW str, DWORD CharSet)
+{
+ CStringW ret;
+
+ DWORD cp = CharSetToCodePage(CharSet);
+
+ for(ptrdiff_t i = 0, j = str.GetLength(); i < j; i++)
+ {
+ WCHAR wc = str.GetAt(i);
+ char c[8];
+
+ int len;
+ if((len = WideCharToMultiByte(cp, 0, &wc, 1, c, 8, NULL, NULL)) > 0)
+ {
+ for(ptrdiff_t k = 0; k < len; k++)
+ ret += (WCHAR)(BYTE)c[k];
+ }
+ else
+ {
+ ret += '?';
+ }
+ }
+
+ return(ret);
+}
+
+static CStringW UnicodeSSAToMBCS(CStringW str, DWORD CharSet)
+{
+ CStringW ret;
+
+ int OrgCharSet = CharSet;
+
+ for(ptrdiff_t j = 0; j < str.GetLength(); )
+ {
+ j = str.Find('{', j);
+ if(j >= 0)
+ {
+ ret += ToMBCS(str.Left(j), CharSet);
+ str = str.Mid(j);
+
+ j = str.Find('}');
+ if(j < 0)
+ {
+ ret += ToMBCS(str, CharSet);
+ break;
+ }
+ else
+ {
+ int k = str.Find(L"\\fe");
+ if(k >= 0 && k < j)
+ {
+ CharSet = 0;
+ int l = k+3;
+ for(; _istdigit(str[l]); l++) CharSet = CharSet*10 + (str[l] - '0');
+ if(l == k+3) CharSet = OrgCharSet;
+ }
+
+ j++;
+
+ ret += ToMBCS(str.Left(j), OrgCharSet);
+ str = str.Mid(j);
+ j = 0;
+ }
+ }
+ else
+ {
+ ret += ToMBCS(str, CharSet);
+ break;
+ }
+ }
+
+ return(ret);
+}
+
+static CStringW ToUnicode(CStringW str, DWORD CharSet)
+{
+ CStringW ret;
+
+ DWORD cp = CharSetToCodePage(CharSet);
+
+ for(ptrdiff_t i = 0, j = str.GetLength(); i < j; i++)
+ {
+ WCHAR wc = str.GetAt(i);
+ char c = wc&0xff;
+
+ if(IsDBCSLeadByteEx(cp, (BYTE)wc))
+ {
+ i++;
+
+ if(i < j)
+ {
+ char cc[2];
+ cc[0] = c;
+ cc[1] = (char)str.GetAt(i);
+
+ MultiByteToWideChar(cp, 0, cc, 2, &wc, 1);
+ }
+ }
+ else
+ {
+ MultiByteToWideChar(cp, 0, &c, 1, &wc, 1);
+ }
+
+ ret += wc;
+ }
+
+ return(ret);
+}
+
+static CStringW MBCSSSAToUnicode(CStringW str, int CharSet)
+{
+ CStringW ret;
+
+ int OrgCharSet = CharSet;
+
+ for(ptrdiff_t j = 0; j < str.GetLength(); )
+ {
+ j = FindChar(str, '{', 0, false, CharSet);
+
+ if(j >= 0)
+ {
+ ret += ToUnicode(str.Left(j), CharSet);
+ str = str.Mid(j);
+
+ j = FindChar(str, '}', 0, false, CharSet);
+
+ if(j < 0)
+ {
+ ret += ToUnicode(str, CharSet);
+ break;
+ }
+ else
+ {
+ int k = str.Find(L"\\fe");
+ if(k >= 0 && k < j)
+ {
+ CharSet = 0;
+ int l = k+3;
+ for(; _istdigit(str[l]); l++) CharSet = CharSet*10 + (str[l] - '0');
+ if(l == k+3) CharSet = OrgCharSet;
+ }
+
+ j++;
+
+ ret += ToUnicode(str.Left(j), OrgCharSet);
+ str = str.Mid(j);
+ j = 0;
+ }
+ }
+ else
+ {
+ ret += ToUnicode(str, CharSet);
+ break;
+ }
+ }
+
+ return(ret);
+}
+
+CStringW RemoveSSATags(CStringW str, bool fUnicode, int CharSet)
+{
+ str.Replace (L"{\\i1}", L"<i>");
+ str.Replace (L"{\\i}", L"</i>");
+
+ for(ptrdiff_t i = 0, j; i < str.GetLength(); )
+ {
+ if((i = FindChar(str, '{', i, fUnicode, CharSet)) < 0) break;
+ if((j = FindChar(str, '}', i, fUnicode, CharSet)) < 0) break;
+ str.Delete(i, j-i+1);
+ }
+
+ str.Replace(L"\\N", L"\n");
+ str.Replace(L"\\n", L"\n");
+ str.Replace(L"\\h", L" ");
+
+ return(str);
+}
+
+//
+
+static CStringW SubRipper2SSA(CStringW str, int CharSet)
+{
+ str.Replace(L"<i>", L"{\\i1}");
+ str.Replace(L"</i>", L"{\\i}");
+ str.Replace(L"<b>", L"{\\b1}");
+ str.Replace(L"</b>", L"{\\b}");
+ str.Replace(L"<u>", L"{\\u1}");
+ str.Replace(L"</u>", L"{\\u}");
+
+ return(str);
+}
+
+static bool OpenSubRipper(CTextFile* file, CSimpleTextSubtitle& ret, int CharSet)
+{
+ int num = 0;
+
+ CStringW buff;
+ while(file->ReadString(buff))
+ {
+ buff.Trim();
+ if(buff.IsEmpty()) continue;
+
+ WCHAR sep;
+ int hh1, mm1, ss1, ms1, hh2, mm2, ss2, ms2;
+ int c = swscanf(buff, L"%d%c%d%c%d%c%d --> %d%c%d%c%d%c%d\n",
+ &hh1, &sep, &mm1, &sep, &ss1, &sep, &ms1,
+ &hh2, &sep, &mm2, &sep, &ss2, &sep, &ms2);
+
+ if(c == 1) // numbering
+ {
+ num = hh1;
+ }
+ else if(c == 14) // time info
+ {
+ CStringW str, tmp;
+
+ bool fFoundEmpty = false;
+
+ while(file->ReadString(tmp))
+ {
+ tmp.Trim();
+ if(tmp.IsEmpty()) fFoundEmpty = true;
+
+ int num2;
+ WCHAR c;
+ if(swscanf(tmp, L"%d%c", &num2, &c) == 1 && fFoundEmpty)
+ {
+ num = num2;
+ break;
+ }
+
+ str += tmp + '\n';
+ }
+
+ ret.Add(
+ SubRipper2SSA(str, CharSet),
+ file->IsUnicode(),
+ (((hh1*60 + mm1)*60) + ss1)*1000 + ms1,
+ (((hh2*60 + mm2)*60) + ss2)*1000 + ms2);
+ }
+ else if(c != EOF) // might be another format
+ {
+ return(false);
+ }
+ }
+
+ return(ret.GetCount() > 0);
+}
+
+static bool OpenOldSubRipper(CTextFile* file, CSimpleTextSubtitle& ret, int CharSet)
+{
+ CStringW buff;
+ while(file->ReadString(buff))
+ {
+ buff.Trim();
+ if(buff.IsEmpty()) continue;
+
+ for(ptrdiff_t i = 0; i < buff.GetLength(); i++)
+ {
+ if((i = FindChar(buff, '|', i, file->IsUnicode(), CharSet)) < 0) break;
+ buff.SetAt(i, '\n');
+ }
+
+ int hh1, mm1, ss1, hh2, mm2, ss2;
+ int c = swscanf(buff, L"{%d:%d:%d}{%d:%d:%d}", &hh1, &mm1, &ss1, &hh2, &mm2, &ss2);
+
+ if(c == 6)
+ {
+ ret.Add(
+ buff.Mid(buff.Find('}', buff.Find('}')+1)+1),
+ file->IsUnicode(),
+ (((hh1*60 + mm1)*60) + ss1)*1000,
+ (((hh2*60 + mm2)*60) + ss2)*1000);
+ }
+ else if(c != EOF) // might be another format
+ {
+ return(false);
+ }
+ }
+
+ return(ret.GetCount() > 0);
+}
+
+static bool OpenSubViewer(CTextFile* file, CSimpleTextSubtitle& ret, int CharSet)
+{
+ STSStyle def;
+ CStringW font, color, size;
+ bool fBold = false;
+ bool fItalic = false;
+ bool fStriked = false;
+ bool fUnderline = false;
+
+ CStringW buff;
+ while(file->ReadString(buff))
+ {
+ buff.Trim();
+ if(buff.IsEmpty()) continue;
+
+ if(buff[0] == '[')
+ {
+ for(size_t i = 0; i < buff.GetLength() && buff[i]== '['; )
+ {
+ int j = buff.Find(']', ++i);
+ if(j < i) break;
+
+ CStringW tag = buff.Mid(i,j-i);
+ tag.Trim();
+ tag.MakeLower();
+
+ i += j-i;
+
+ j = buff.Find('[', ++i);
+ if(j < 0) j = buff.GetLength();
+
+ CStringW param = buff.Mid(i,j-i);
+ param.Trim(L" \\t,");
+
+ i = j;
+
+ if(tag == L"font")
+ font = def.fontName.CompareNoCase(WToT(param)) ? param : L"";
+ else if(tag == L"colf")
+ color = def.colors[0] != (DWORD)wcstol(((LPCWSTR)param)+2, 0, 16) ? param : L"";
+ else if(tag == L"size")
+ size = def.fontSize != (double)wcstol(param, 0, 10) ? param : L"";
+ else if(tag == L"style")
+ {
+ if(param.Find(L"no") >= 0)
+ {
+ fBold = fItalic = fStriked = fUnderline = false;
+ }
+ else
+ {
+ fBold = def.fontWeight < FW_BOLD && param.Find(L"bd") >= 0;
+ fItalic = def.fItalic && param.Find(L"it") >= 0;
+ fStriked = def.fStrikeOut && param.Find(L"st") >= 0;
+ fUnderline = def.fUnderline && param.Find(L"ud") >= 0;
+ }
+ }
+ }
+
+ continue;
+ }
+
+ WCHAR sep;
+ int hh1, mm1, ss1, hs1, hh2, mm2, ss2, hs2;
+ int c = swscanf(buff, L"%d:%d:%d%c%d,%d:%d:%d%c%d\n",
+ &hh1, &mm1, &ss1, &sep, &hs1, &hh2, &mm2, &ss2, &sep, &hs2);
+
+ if(c == 10)
+ {
+ CStringW str;
+ file->ReadString(str);
+
+ str.Replace(L"[br]", L"\\N");
+
+ CStringW prefix;
+ if(!font.IsEmpty()) prefix += L"\\fn" + font;
+ if(!color.IsEmpty()) prefix += L"\\c" + color;
+ if(!size.IsEmpty()) prefix += L"\\fs" + size;
+ if(fBold) prefix += L"\\b1";
+ if(fItalic) prefix += L"\\i1";
+ if(fStriked) prefix += L"\\s1";
+ if(fUnderline) prefix += L"\\u1";
+ if(!prefix.IsEmpty()) str = L"{" + prefix + L"}" + str;
+
+ ret.Add(str,
+ file->IsUnicode(),
+ (((hh1*60 + mm1)*60) + ss1)*1000 + hs1*10,
+ (((hh2*60 + mm2)*60) + ss2)*1000 + hs2*10);
+ }
+ else if(c != EOF) // might be another format
+ {
+ return(false);
+ }
+ }
+
+ return(ret.GetCount() > 0);
+}
+
+static STSStyle* GetMicroDVDStyle(CString str, int CharSet)
+{
+ STSStyle* ret = DNew STSStyle();
+ if(!ret) return(NULL);
+
+ for(ptrdiff_t i = 0, len = str.GetLength(); i < len; i++)
+ {
+ int j = str.Find('{', i);
+ if(j < 0) j = len;
+
+ if(j >= len) break;
+
+ int k = str.Find('}', j);
+ if(k < 0) k = len;
+
+ CString code = str.Mid(j, k-j);
+ if(code.GetLength() > 2) code.SetAt(1, (TCHAR)towlower(code[1]));
+
+ if(!_tcsnicmp(code, _T("{c:$"), 4))
+ {
+ _stscanf(code, _T("{c:$%x"), &ret->colors[0]);
+ }
+ else if(!_tcsnicmp(code, _T("{f:"), 3))
+ {
+ ret->fontName = code.Mid(3);
+ }
+ else if(!_tcsnicmp(code, _T("{s:"), 3))
+ {
+ float f;
+ if(1 == _stscanf(code, _T("{s:%f"), &f))
+ ret->fontSize = f;
+ }
+ else if(!_tcsnicmp(code, _T("{h:"), 3))
+ {
+ _stscanf(code, _T("{h:%d"), &ret->charSet);
+ }
+ else if(!_tcsnicmp(code, _T("{y:"), 3))
+ {
+ code.MakeLower();
+ if(code.Find('b') >= 0) ret->fontWeight = FW_BOLD;
+ if(code.Find('i') >= 0) ret->fItalic = true;
+ if(code.Find('u') >= 0) ret->fUnderline = true;
+ if(code.Find('s') >= 0) ret->fStrikeOut = true;
+ }
+ else if(!_tcsnicmp(code, _T("{p:"), 3))
+ {
+ int p;
+ _stscanf(code, _T("{p:%d"), &p);
+ ret->scrAlignment = (p == 0) ? 8 : 2;
+ }
+
+ i = k;
+ }
+
+ return(ret);
+}
+
+static CStringW MicroDVD2SSA(CStringW str, bool fUnicode, int CharSet)
+{
+ CStringW ret;
+
+ enum {COLOR=0, FONTNAME, FONTSIZE, FONTCHARSET, BOLD, ITALIC, UNDERLINE, STRIKEOUT};
+ bool fRestore[8];
+ int fRestoreLen = 8;
+ memset(fRestore, 0, sizeof(bool)*fRestoreLen);
+
+ for(ptrdiff_t pos = 0, eol; pos < str.GetLength(); pos++)
+ {
+ if((eol = FindChar(str, '|', pos, fUnicode, CharSet)) < 0) eol = str.GetLength();
+
+ CStringW line = str.Mid(pos, eol-pos);
+
+ pos = eol;
+
+ for(ptrdiff_t i = 0, j, k, len = line.GetLength(); i < len; i++)
+ {
+ if((j = FindChar(line, '{', i, fUnicode, CharSet)) < 0) j = str.GetLength();
+
+ ret += line.Mid(i, j-i);
+
+ if(j >= len) break;
+
+ if((k = FindChar(line, '}', j, fUnicode, CharSet)) < 0) k = len;
+
+ {
+ CStringW code = line.Mid(j, k-j);
+
+ if(!wcsnicmp(code, L"{c:$", 4))
+ {
+ fRestore[COLOR] = (iswupper(code[1]) == 0);
+ code.MakeLower();
+
+ int color;
+ swscanf(code, L"{c:$%x", &color);
+ code.Format(L"{\\c&H%x&}", color);
+ ret += code;
+ }
+ else if(!wcsnicmp(code, L"{f:", 3))
+ {
+ fRestore[FONTNAME] = (iswupper(code[1]) == 0);
+
+ code.Format(L"{\\fn%s}", code.Mid(3));
+ ret += code;
+ }
+ else if(!wcsnicmp(code, L"{s:", 3))
+ {
+ fRestore[FONTSIZE] = (iswupper(code[1]) == 0);
+ code.MakeLower();
+
+ float size;
+ swscanf(code, L"{s:%f", &size);
+ code.Format(L"{\\fs%f}", size);
+ ret += code;
+ }
+ else if(!wcsnicmp(code, L"{h:", 3))
+ {
+ fRestore[COLOR] = (_istupper(code[1]) == 0);
+ code.MakeLower();
+
+ int CharSet;
+ swscanf(code, L"{h:%d", &CharSet);
+ code.Format(L"{\\fe%d}", CharSet);
+ ret += code;
+ }
+ else if(!wcsnicmp(code, L"{y:", 3))
+ {
+ bool f = (_istupper(code[1]) == 0);
+
+ code.MakeLower();
+
+ ret += '{';
+ if(code.Find('b') >= 0) {ret += L"\\b1"; fRestore[BOLD] = f;}
+ if(code.Find('i') >= 0) {ret += L"\\i1"; fRestore[ITALIC] = f;}
+ if(code.Find('u') >= 0) {ret += L"\\u1"; fRestore[UNDERLINE] = f;}
+ if(code.Find('s') >= 0) {ret += L"\\s1"; fRestore[STRIKEOUT] = f;}
+ ret += '}';
+ }
+ else if(!wcsnicmp(code, L"{o:", 3))
+ {
+ code.MakeLower();
+
+ int x, y;
+ TCHAR c;
+ swscanf(code, L"{o:%d%c%d", &x, &c, &y);
+ code.Format(L"{\\move(%d,%d,0,0,0,0)}", x, y);
+ ret += code;
+ }
+ else ret += code;
+ }
+
+ i = k;
+ }
+
+ if(pos >= str.GetLength()) break;
+
+ for(ptrdiff_t i = 0; i < fRestoreLen; i++)
+ {
+ if(fRestore[i])
+ {
+ switch(i)
+ {
+ case COLOR: ret += L"{\\c}"; break;
+ case FONTNAME: ret += L"{\\fn}"; break;
+ case FONTSIZE: ret += L"{\\fs}"; break;
+ case FONTCHARSET: ret += L"{\\fe}"; break;
+ case BOLD: ret += L"{\\b}"; break;
+ case ITALIC: ret += L"{\\i}"; break;
+ case UNDERLINE: ret += L"{\\u}"; break;
+ case STRIKEOUT: ret += L"{\\s}"; break;
+ default: break;
+ }
+ }
+ }
+
+ memset(fRestore, 0, sizeof(bool)*fRestoreLen);
+
+ ret += L"\\N";
+ }
+
+ return(ret);
+}
+
+static bool OpenMicroDVD(CTextFile* file, CSimpleTextSubtitle& ret, int CharSet)
+{
+ bool fCheck = false, fCheck2 = false;
+
+ CString style(_T("Default"));
+
+ CStringW buff;
+ while(file->ReadString(buff))
+ {
+ buff.Trim();
+ if(buff.IsEmpty()) continue;
+
+ int start, end;
+ int c = swscanf(buff, L"{%d}{%d}", &start, &end);
+
+ if(c != 2)
+ {
+ c = swscanf(buff, L"{%d}{}", &start) + 1;
+ end = start + 60;
+ fCheck = true;
+ }
+
+ if(c != 2)
+ {
+ int i;
+ if(buff.Find('{') == 0 && (i = buff.Find('}')) > 1 && i < buff.GetLength())
+ {
+ if(STSStyle* s = GetMicroDVDStyle(WToT(buff.Mid(i+1)), CharSet))
+ {
+ style = buff.Mid(1, i-1);
+ style.MakeUpper();
+ if(style.GetLength())
+ {
+ CString str = style.Mid(1);
+ str.MakeLower();
+ style = style.Left(1) + str;
+ }
+ ret.AddStyle(style, s);
+ CharSet = s->charSet;
+ continue;
+ }
+ }
+ }
+
+ if(c == 2)
+ {
+ if(fCheck2 && ret.GetCount())
+ {
+ STSEntry& stse = ret[ret.GetCount()-1];
+ stse.end = min(stse.end, start);
+ fCheck2 = false;
+ }
+
+ ret.Add(
+ MicroDVD2SSA(buff.Mid(buff.Find('}', buff.Find('}')+1)+1), file->IsUnicode(), CharSet),
+ file->IsUnicode(),
+ start, end,
+ style);
+
+ if(fCheck)
+ {
+ fCheck = false;
+ fCheck2 = true;
+ }
+ }
+ else if(c != EOF) // might be another format
+ {
+ return(false);
+ }
+ }
+
+ return(ret.GetCount() > 0);
+}
+
+static void ReplaceNoCase(CStringW& str, CStringW from, CStringW to)
+{
+ CStringW lstr = str;
+ lstr.MakeLower();
+
+ int i, j, k;
+
+ for(i = 0, j = str.GetLength(); i < j; )
+ {
+ if((k = lstr.Find(from, i)) >= 0)
+ {
+ str.Delete(k, from.GetLength());
+ lstr.Delete(k, from.GetLength());
+ str.Insert(k, to);
+ lstr.Insert(k, to);
+ i = k + to.GetLength();
+ j = str.GetLength();
+ }
+ else break;
+ }
+}
+
+static CStringW SMI2SSA(CStringW str, int CharSet)
+{
+ ReplaceNoCase(str, L"&nbsp;", L" ");
+ ReplaceNoCase(str, L"&quot;", L"\"");
+ ReplaceNoCase(str, L"<br>", L"\\N");
+ ReplaceNoCase(str, L"<i>", L"{\\i1}");
+ ReplaceNoCase(str, L"</i>", L"{\\i}");
+ ReplaceNoCase(str, L"<b>", L"{\\b1}");
+ ReplaceNoCase(str, L"</b>", L"{\\b}");
+
+ CStringW lstr = str;
+ lstr.MakeLower();
+
+ // maven@maven.de
+ // now parse line
+ for(ptrdiff_t i = 0, j = str.GetLength(); i < j; )
+ {
+ int k;
+ if((k = lstr.Find('<', i)) < 0) break;
+
+ int chars_inserted = 0;
+
+ int l = 1;
+ for(; k+l < j && lstr[k+l] != '>'; l++);
+ l++;
+
+// Modified by Cookie Monster
+ if (lstr.Find(L"<font ", k) == k)
+ {
+ CStringW args = lstr.Mid(k+6, l-6); // delete "<font "
+ CStringW arg ;
+
+ args.Remove('\"');
+ args.Remove('#'); // may include 2 * " + #
+ arg.TrimLeft();
+ arg.TrimRight(L" >");
+
+ for (;;)
+ {
+ args.TrimLeft();
+ arg = args.SpanExcluding(L" \t>");
+ args = args.Mid(arg.GetLength());
+
+ if(arg.IsEmpty())
+ break;
+ if (arg.Find(L"color=") == 0 )
+ {
+ DWORD color;
+
+ arg = arg.Mid(6); // delete "color="
+ if ( arg.IsEmpty())
+ continue;
+
+ DWORD val;
+ if(g_colors.Lookup(CString(arg), val))
+ color = (DWORD)val;
+ else if((color = wcstol(arg, NULL, 16) ) == 0)
+ color = 0x00ffffff; // default is white
+
+ arg.Format(L"%02x%02x%02x", color&0xff, (color>>8)&0xff, (color>>16)&0xff);
+ lstr.Insert(k + l + chars_inserted, CStringW(L"{\\c&H") + arg + L"&}");
+ str.Insert(k + l + chars_inserted, CStringW(L"{\\c&H") + arg + L"&}");
+ chars_inserted += 5 + arg.GetLength() + 2;
+ }
+/*
+ else if (arg.Find(_T("size=" )) == 0 )
+ {
+ uint fsize;
+
+ arg = arg.Mid(5); // delete "size="
+ if ( arg.GetLength() == 0)
+ continue;
+
+ if ( fsize = _tcstol(arg, &tmp, 10) == 0 )
+ continue;
+
+ lstr.Insert(k + l + chars_inserted, CString(_T("{\\fs")) + arg + _T("&}"));
+ str.Insert(k + l + chars_inserted, CString(_T("{\\fs")) + arg + _T("&}"));
+ chars_inserted += 4 + arg.GetLength() + 2;
+ }
+*/
+ }
+ }
+
+// Original Code
+/*
+ if (lstr.Find(L"<font color=", k) == k)
+ {
+ CStringW arg = lstr.Mid(k+12, l-12); // may include 2 * " + #
+
+ arg.Remove('\"');
+ arg.Remove('#');
+ arg.TrimLeft(); arg.TrimRight(L" >");
+
+ if(arg.GetLength() > 0)
+ {
+ DWORD color;
+
+ CString key = WToT(arg);
+ void* val;
+ if(g_colors.Lookup(key, val)) color = (DWORD)val;
+ else color = wcstol(arg, NULL, 16);
+
+ arg.Format(L"%02x%02x%02x", color&0xff, (color>>8)&0xff, (color>>16)&0xff);
+ }
+
+ lstr.Insert(k + l + chars_inserted, L"{\\c&H" + arg + L"&}");
+ str.Insert(k + l + chars_inserted, L"{\\c&H" + arg + L"&}");
+ chars_inserted += 5 + arg.GetLength() + 2;
+ }
+*/
+ else if (lstr.Find(L"</font>", k) == k)
+ {
+ lstr.Insert(k + l + chars_inserted, L"{\\c}");
+ str.Insert(k + l + chars_inserted, L"{\\c}");
+ chars_inserted += 4;
+ }
+
+ str.Delete(k, l);
+ lstr.Delete(k, l);
+ i = k + chars_inserted;
+ j = str.GetLength();
+ }
+
+ return(str);
+}
+
+static bool OpenSami(CTextFile* file, CSimpleTextSubtitle& ret, int CharSet)
+{
+ CStringW buff, caption;
+
+ ULONGLONG pos = file->GetPosition();
+
+ bool fSAMI = false;
+
+ while(file->ReadString(buff) && !fSAMI)
+ {
+ if(buff.MakeUpper().Find(L"<SAMI>") >= 0) fSAMI = true;
+ }
+
+ if(!fSAMI) return(false);
+
+ file->Seek(pos, 0);
+
+ bool fComment = false;
+
+ int start_time = 0;
+
+ while(file->ReadString(buff))
+ {
+ buff.Trim();
+ if(buff.IsEmpty()) continue;
+
+ CStringW ubuff = buff;
+ ubuff.MakeUpper();
+
+ if(ubuff.Find(L"<!--") >= 0 || ubuff.Find(L"<TITLE>") >= 0)
+ fComment = true;
+
+ if(!fComment)
+ {
+ int i;
+
+ if((i = ubuff.Find(L"<SYNC START=")) >= 0)
+ {
+ int time = 0;
+
+ for(i = 12; i < ubuff.GetLength(); i++)
+ {
+ if(ubuff[i] != '>' && ubuff[i] != 'M')
+ {
+ if(iswdigit(ubuff[i]))
+ {
+ time *= 10;
+ time += ubuff[i] - 0x30;
+ }
+ }
+ else break;
+ }
+
+ ret.Add(
+ SMI2SSA(caption, CharSet),
+ file->IsUnicode(),
+ start_time, time);
+
+ start_time = time;
+ caption.Empty();
+ }
+
+ caption += buff;
+ }
+
+ if(ubuff.Find(L"-->") >= 0 || ubuff.Find(L"</TITLE>") >= 0)
+ fComment = false;
+ }
+
+ ret.Add(
+ SMI2SSA(caption, CharSet),
+ file->IsUnicode(),
+ start_time, MAXLONG);
+
+ return(true);
+}
+
+static bool OpenVPlayer(CTextFile* file, CSimpleTextSubtitle& ret, int CharSet)
+{
+ CStringW buff;
+ while(file->ReadString(buff))
+ {
+ buff.Trim();
+ if(buff.IsEmpty()) continue;
+
+ for(ptrdiff_t i = 0; i < buff.GetLength(); i++)
+ {
+ if((i = FindChar(buff, '|', i, file->IsUnicode(), CharSet)) < 0) break;
+ buff.SetAt(i, '\n');
+ }
+
+ int hh, mm, ss;
+ int c = swscanf(buff, L"%d:%d:%d:", &hh, &mm, &ss);
+
+ if(c == 3)
+ {
+ CStringW str = buff.Mid(buff.Find(':', buff.Find(':', buff.Find(':')+1)+1)+1);
+ ret.Add(str,
+ file->IsUnicode(),
+ (((hh*60 + mm)*60) + ss)*1000,
+ (((hh*60 + mm)*60) + ss)*1000 + 1000 + 50*str.GetLength());
+ }
+ else if(c != EOF) // might be another format
+ {
+ return(false);
+ }
+ }
+
+ return(ret.GetCount() > 0);
+}
+
+CStringW GetStr(CStringW& buff, char sep = ',') //throw(...)
+{
+ buff.TrimLeft();
+
+ int pos = buff.Find(sep);
+ if(pos < 0)
+ {
+ pos = buff.GetLength();
+ if(pos < 1) throw 1;
+ }
+
+ CStringW ret = buff.Left(pos);
+ if(pos < buff.GetLength()) buff = buff.Mid(pos+1);
+
+ return(ret);
+}
+
+int GetInt(CStringW& buff, char sep = ',') //throw(...)
+{
+ CStringW str;
+
+ str = GetStr(buff, sep);
+ str.MakeLower();
+
+ CStringW fmtstr = str.GetLength() > 2 && (str.Left(2) == L"&h" || str.Left(2) == L"0x")
+ ? str = str.Mid(2), L"%x"
+ : L"%d";
+
+ int ret;
+ if(swscanf(str, fmtstr, &ret) != 1) throw 1;
+
+ return(ret);
+}
+
+double GetFloat(CStringW& buff, char sep = ',') //throw(...)
+{
+ CStringW str;
+
+ str = GetStr(buff, sep);
+ str.MakeLower();
+
+ float ret;
+ if(swscanf(str, L"%f", &ret) != 1) throw 1;
+
+ return((double)ret);
+}
+
+static bool LoadFont(CString& font)
+{
+ int len = font.GetLength();
+
+ CAutoVectorPtr<BYTE> pData;
+ if(len == 0 || (len&3) == 1 || !pData.Allocate(len))
+ return(false);
+
+ const TCHAR* s = font;
+ const TCHAR* e = s + len;
+ for(BYTE* p = pData; s < e; s++, p++) *p = *s - 33;
+
+ for(ptrdiff_t i = 0, j = 0, k = len&~3; i < k; i+=4, j+=3)
+ {
+ pData[j+0] = ((pData[i+0]&63)<<2)|((pData[i+1]>>4)& 3);
+ pData[j+1] = ((pData[i+1]&15)<<4)|((pData[i+2]>>2)&15);
+ pData[j+2] = ((pData[i+2]& 3)<<6)|((pData[i+3]>>0)&63);
+ }
+
+ int datalen = (len&~3)*3/4;
+
+ if((len&3) == 2)
+ {
+ pData[datalen++] = ((pData[(len&~3)+0]&63)<<2)|((pData[(len&~3)+1]>>4)&3);
+ }
+ else if((len&3) == 3)
+ {
+ pData[datalen++] = ((pData[(len&~3)+0]&63)<<2)|((pData[(len&~3)+1]>>4)& 3);
+ pData[datalen++] = ((pData[(len&~3)+1]&15)<<4)|((pData[(len&~3)+2]>>2)&15);
+ }
+
+ HANDLE hFont = INVALID_HANDLE_VALUE;
+
+ if(HMODULE hModule = LoadLibrary(_T("GDI32.DLL")))
+ {
+ typedef HANDLE (WINAPI *PAddFontMemResourceEx)( IN PVOID, IN DWORD, IN PVOID , IN DWORD*);
+ if(PAddFontMemResourceEx f = (PAddFontMemResourceEx)GetProcAddress(hModule, "AddFontMemResourceEx"))
+ {
+ DWORD cFonts;
+ hFont = f(pData, datalen, NULL, &cFonts);
+ }
+
+ FreeLibrary(hModule);
+ }
+
+ if(hFont == INVALID_HANDLE_VALUE)
+ {
+ TCHAR path[_MAX_PATH];
+ GetTempPath(_MAX_PATH, path);
+
+ DWORD chksum = 0;
+ for(ptrdiff_t i = 0, j = datalen>>2; i < j; i++)
+ chksum += ((DWORD*)(BYTE*)pData)[i];
+
+ CString fn;
+ fn.Format(_T("%sfont%08x.ttf"), path, chksum);
+
+ CFileStatus fs;
+ if(!CFileGetStatus(fn, fs))
+ {
+ CFile f;
+ if(f.Open(fn, CFile::modeCreate|CFile::modeWrite|CFile::typeBinary|CFile::shareDenyNone))
+ {
+ f.Write(pData, datalen);
+ f.Close();
+ }
+ }
+
+ AddFontResource(fn);
+ }
+
+ return(true);
+}
+
+static bool LoadUUEFont(CTextFile* file)
+{
+ CString s, font;
+ while(file->ReadString(s))
+ {
+ s.Trim();
+ if(s.IsEmpty()) break;
+ if(s[0] == '[') // check for some standatr blocks
+ {
+ if(s.Find(_T("[Script Info]")) == 0) break;
+ if(s.Find(_T("[V4+ Styles]")) == 0) break;
+ if(s.Find(_T("[V4 Styles]")) == 0) break;
+ if(s.Find(_T("[Events]")) == 0) break;
+ if(s.Find(_T("[Fonts]")) == 0) break;
+ if(s.Find(_T("[Graphics]")) == 0) break;
+ }
+ if(s.Find(_T("fontname:")) == 0)
+ {
+ LoadFont(font);
+ font.Empty();
+ continue;
+ }
+
+ font += s;
+ }
+
+ if(!font.IsEmpty())
+ LoadFont(font);
+
+ return(true);
+}
+
+#ifdef _VSMOD
+bool CSimpleTextSubtitle::LoadEfile(CString& img, CString m_fn)
+{
+ int len = img.GetLength();
+
+ CAutoVectorPtr<BYTE> pData;
+ if(len == 0 || (len&3) == 1 || !pData.Allocate(len))
+ return(false);
+
+ const TCHAR* s = img;
+ const TCHAR* e = s + len;
+ for(BYTE* p = pData; s < e; s++, p++) *p = *s - 33;
+
+ for(ptrdiff_t i = 0, j = 0, k = len&~3; i < k; i+=4, j+=3)
+ {
+ pData[j+0] = ((pData[i+0]&63)<<2)|((pData[i+1]>>4)& 3);
+ pData[j+1] = ((pData[i+1]&15)<<4)|((pData[i+2]>>2)&15);
+ pData[j+2] = ((pData[i+2]& 3)<<6)|((pData[i+3]>>0)&63);
+ }
+
+ int datalen = (len&~3)*3/4;
+
+ if((len&3) == 2)
+ {
+ pData[datalen++] = ((pData[(len&~3)+0]&63)<<2)|((pData[(len&~3)+1]>>4)&3);
+ }
+ else if((len&3) == 3)
+ {
+ pData[datalen++] = ((pData[(len&~3)+0]&63)<<2)|((pData[(len&~3)+1]>>4)& 3);
+ pData[datalen++] = ((pData[(len&~3)+1]&15)<<4)|((pData[(len&~3)+2]>>2)&15);
+ }
+
+ // load png image
+ MOD_PNGIMAGE t_temp;
+ if(t_temp.initImage(pData.m_p,m_fn)) // save path
+ {
+ mod_images.Add(t_temp);
+ }
+ return(true);
+}
+
+
+bool CSimpleTextSubtitle::LoadUUEFile(CTextFile* file, CString m_fn)
+{
+ CString s, img;
+ while(file->ReadString(s))
+ {
+ s.Trim();
+ if(s.IsEmpty()) break;
+ if(s[0] == '[') // check for some standatr blocks
+ {
+ if(s.Find(_T("[Script Info]")) == 0) break;
+ if(s.Find(_T("[V4+ Styles]")) == 0) break;
+ if(s.Find(_T("[V4 Styles]")) == 0) break;
+ if(s.Find(_T("[Events]")) == 0) break;
+ if(s.Find(_T("[Fonts]")) == 0) break;
+ if(s.Find(_T("[Graphics]")) == 0) break;
+ }
+ // next file
+ if(s.Find(_T("filename:")) == 0)
+ {
+ LoadEfile(img, m_fn);
+ m_fn = s.Mid(10);
+ img.Empty();
+ continue;
+ }
+
+ img += s;
+ }
+
+ if(!img.IsEmpty())
+ LoadEfile(img, m_fn);
+
+ return(true);
+}
+#endif
+
+static bool OpenSubStationAlpha(CTextFile* file, CSimpleTextSubtitle& ret, int CharSet)
+{
+ bool fRet = false;
+
+ int version = 3, sver = 3;
+
+ CStringW buff;
+ while(file->ReadString(buff))
+ {
+ buff.Trim();
+ if(buff.IsEmpty() || buff.GetAt(0) == ';') continue;
+
+ CStringW entry;
+
+// try {
+ entry = GetStr(buff, ':');
+// }
+// catch(...) {continue;}
+
+ entry.MakeLower();
+
+ if(entry == L"[script info]")
+ {
+ fRet = true;
+ }
+ else if(entry == L"playresx")
+ {
+ try
+ {
+ ret.m_dstScreenSize.cx = GetInt(buff);
+ }
+ catch(...)
+ {
+ ret.m_dstScreenSize = CSize(0, 0);
+ return(false);
+ }
+
+ if(ret.m_dstScreenSize.cy <= 0)
+ {
+ ret.m_dstScreenSize.cy = (ret.m_dstScreenSize.cx == 1280)
+ ? 1024
+ : ret.m_dstScreenSize.cx * 3 / 4;
+ }
+ }
+ else if(entry == L"playresy")
+ {
+ try
+ {
+ ret.m_dstScreenSize.cy = GetInt(buff);
+ }
+ catch(...)
+ {
+ ret.m_dstScreenSize = CSize(0, 0);
+ return(false);
+ }
+
+ if(ret.m_dstScreenSize.cx <= 0)
+ {
+ ret.m_dstScreenSize.cx = (ret.m_dstScreenSize.cy == 1024)
+ ? 1280
+ : ret.m_dstScreenSize.cy * 4 / 3;
+ }
+ }
+ else if(entry == L"wrapstyle")
+ {
+ try
+ {
+ ret.m_defaultWrapStyle = GetInt(buff);
+ }
+ catch(...)
+ {
+ ret.m_defaultWrapStyle = 1;
+ return(false);
+ }
+ }
+ else if(entry == L"scripttype")
+ {
+ if(buff.GetLength() >= 4 && !buff.Right(4).CompareNoCase(L"4.00")) version = sver = 4;
+ else if(buff.GetLength() >= 5 && !buff.Right(5).CompareNoCase(L"4.00+")) version = sver = 5;
+ else if(buff.GetLength() >= 6 && !buff.Right(6).CompareNoCase(L"4.00++")) version = sver = 6;
+ }
+ else if(entry == L"collisions")
+ {
+ buff = GetStr(buff);
+ buff.MakeLower();
+ ret.m_collisions = buff.Find(L"reverse") >= 0 ? 1 : 0;
+ }
+ else if(entry == L"scaledborderandshadow")
+ {
+ buff = GetStr(buff);
+ buff.MakeLower();
+ ret.m_fScaledBAS = buff.Find(L"yes") >= 0;
+ }
+ else if(entry == L"[v4 styles]")
+ {
+ fRet = true;
+ sver = 4;
+ }
+ else if(entry == L"[v4+ styles]")
+ {
+ fRet = true;
+ sver = 5;
+ }
+ else if(entry == L"[v4++ styles]")
+ {
+ fRet = true;
+ sver = 6;
+ }
+ else if(entry == L"style")
+ {
+ STSStyle* style = DNew STSStyle;
+ if(!style) return(false);
+
+ try
+ {
+ CString StyleName;
+ int alpha = 0;
+
+ StyleName = WToT(GetStr(buff));
+ style->fontName = WToT(GetStr(buff));
+ style->fontSize = GetFloat(buff);
+ for(ptrdiff_t i = 0; i < 4; i++) style->colors[i] = (COLORREF)GetInt(buff);
+ style->fontWeight = !!GetInt(buff) ? FW_BOLD : FW_NORMAL;
+ style->fItalic = !!GetInt(buff);
+ if(sver >= 5) style->fUnderline = !!GetInt(buff);
+ if(sver >= 5) style->fStrikeOut = !!GetInt(buff);
+ if(sver >= 5) style->fontScaleX = GetFloat(buff);
+ if(sver >= 5) style->fontScaleY = GetFloat(buff);
+ if(sver >= 5) style->fontSpacing = GetFloat(buff);
+ if(sver >= 5) style->fontAngleZ = GetFloat(buff);
+ if(sver >= 4) style->borderStyle = GetInt(buff);
+ style->outlineWidthX = style->outlineWidthY = GetFloat(buff);
+ style->shadowDepthX = style->shadowDepthY = GetFloat(buff);
+ style->scrAlignment = GetInt(buff);
+ style->marginRect.left = GetInt(buff);
+ style->marginRect.right = GetInt(buff);
+ style->marginRect.top = style->marginRect.bottom = GetInt(buff);
+ if(sver >= 6) style->marginRect.bottom = GetInt(buff);
+ if(sver <= 4) alpha = GetInt(buff);
+ style->charSet = GetInt(buff);
+ if(sver >= 6) style->relativeTo = GetInt(buff);
+
+ if(sver <= 4) style->colors[2] = style->colors[3]; // style->colors[2] is used for drawing the outline
+ if(sver <= 4) alpha = max(min(alpha, 0xff), 0);
+ if(sver <= 4)
+ {
+ for(ptrdiff_t i = 0; i < 3; i++) style->alpha[i] = alpha;
+ style->alpha[3] = 0x80;
+ }
+ if(sver >= 5) for(ptrdiff_t i = 0; i < 4; i++)
+ {
+ style->alpha[i] = (BYTE)(style->colors[i] >> 24);
+ style->colors[i] &= 0xffffff;
+ }
+ if(sver >= 5) style->fontScaleX = max(style->fontScaleX, 0);
+ if(sver >= 5) style->fontScaleY = max(style->fontScaleY, 0);
+#ifndef _VSMOD // patch f002. negative fontspacing at style
+ if(sver >= 5) style->fontSpacing = max(style->fontSpacing, 0);
+#endif
+ style->fontAngleX = style->fontAngleY = 0;
+ style->borderStyle = style->borderStyle == 1 ? 0 : style->borderStyle == 3 ? 1 : 0;
+ style->outlineWidthX = max(style->outlineWidthX, 0);
+ style->outlineWidthY = max(style->outlineWidthY, 0);
+ style->shadowDepthX = max(style->shadowDepthX, 0);
+ style->shadowDepthY = max(style->shadowDepthY, 0);
+ if(sver <= 4) style->scrAlignment = (style->scrAlignment & 4) ? ((style->scrAlignment & 3) + 6) // top
+ : (style->scrAlignment & 8) ? ((style->scrAlignment & 3) + 3) // mid
+ : (style->scrAlignment & 3); // bottom
+
+ StyleName.TrimLeft('*');
+
+ ret.AddStyle(StyleName, style);
+ }
+ catch(...)
+ {
+ delete style;
+ return(false);
+ }
+ }
+ else if(entry == L"[events]")
+ {
+ fRet = true;
+ }
+ else if(entry == _T("dialogue"))
+ {
+ try
+ {
+ int hh1, mm1, ss1, ms1_div10, hh2, mm2, ss2, ms2_div10, layer = 0;
+ CString Style, Actor, Effect;
+ CRect marginRect;
+
+ if(version <= 4)
+ {
+ GetStr(buff, '='); /* Marked = */
+ GetInt(buff);
+ }
+ if(version >= 5)layer = GetInt(buff);
+ hh1 = GetInt(buff, ':');
+ mm1 = GetInt(buff, ':');
+ ss1 = GetInt(buff, '.');
+ ms1_div10 = GetInt(buff);
+ hh2 = GetInt(buff, ':');
+ mm2 = GetInt(buff, ':');
+ ss2 = GetInt(buff, '.');
+ ms2_div10 = GetInt(buff);
+ Style = WToT(GetStr(buff));
+ Actor = WToT(GetStr(buff));
+ marginRect.left = GetInt(buff);
+ marginRect.right = GetInt(buff);
+ marginRect.top = marginRect.bottom = GetInt(buff);
+ if(version >= 6)marginRect.bottom = GetInt(buff);
+ Effect = WToT(GetStr(buff));
+
+ int len = min(Effect.GetLength(), buff.GetLength());
+ if(Effect.Left(len) == WToT(buff.Left(len))) Effect.Empty();
+
+ Style.TrimLeft('*');
+ if(!Style.CompareNoCase(_T("Default"))) Style = _T("Default");
+
+ ret.Add(buff,
+ file->IsUnicode(),
+ (((hh1*60 + mm1)*60) + ss1)*1000 + ms1_div10*10,
+ (((hh2*60 + mm2)*60) + ss2)*1000 + ms2_div10*10,
+ Style, Actor, Effect,
+ marginRect,
+ layer);
+ }
+ catch(...)
+ {
+ return(false);
+ }
+ }
+ else if(entry == L"fontname")
+ {
+ LoadUUEFont(file);
+ }
+#ifdef _VSMOD // load png graphic from text resources
+ else if(entry == L"filename")
+ {
+ ret.LoadUUEFile(file,GetStr(buff));
+ }
+#endif
+ }
+
+ return(fRet);
+}
+
+static bool OpenXombieSub(CTextFile* file, CSimpleTextSubtitle& ret, int CharSet)
+{
+ float version = 0;
+
+// CMapStringToPtr stylemap;
+
+ CStringW buff;
+ while(file->ReadString(buff))
+ {
+ buff.Trim();
+ if(buff.IsEmpty() || buff.GetAt(0) == ';') continue;
+
+ CStringW entry;
+
+// try {
+ entry = GetStr(buff, '=');
+// }
+// catch(...) {continue;}
+
+ entry.MakeLower();
+
+ if(entry == L"version")
+ {
+ version = (float)GetFloat(buff);
+ }
+ else if(entry == L"screenhorizontal")
+ {
+ try
+ {
+ ret.m_dstScreenSize.cx = GetInt(buff);
+ }
+ catch(...)
+ {
+ ret.m_dstScreenSize = CSize(0, 0);
+ return(false);
+ }
+
+ if(ret.m_dstScreenSize.cy <= 0)
+ {
+ ret.m_dstScreenSize.cy = (ret.m_dstScreenSize.cx == 1280)
+ ? 1024
+ : ret.m_dstScreenSize.cx * 3 / 4;
+ }
+ }
+ else if(entry == L"screenvertical")
+ {
+ try
+ {
+ ret.m_dstScreenSize.cy = GetInt(buff);
+ }
+ catch(...)
+ {
+ ret.m_dstScreenSize = CSize(0, 0);
+ return(false);
+ }
+
+ if(ret.m_dstScreenSize.cx <= 0)
+ {
+ ret.m_dstScreenSize.cx = (ret.m_dstScreenSize.cy == 1024)
+ ? 1280
+ : ret.m_dstScreenSize.cy * 4 / 3;
+ }
+ }
+ else if(entry == L"style")
+ {
+ STSStyle* style = DNew STSStyle;
+ if(!style) return(false);
+
+ try
+ {
+ CString StyleName;
+
+ StyleName = WToT(GetStr(buff)) + _T("_") + WToT(GetStr(buff));
+ style->fontName = WToT(GetStr(buff));
+ style->fontSize = GetFloat(buff);
+ for(ptrdiff_t i = 0; i < 4; i++) style->colors[i] = (COLORREF)GetInt(buff);
+ for(ptrdiff_t i = 0; i < 4; i++) style->alpha[i] = GetInt(buff);
+ style->fontWeight = !!GetInt(buff) ? FW_BOLD : FW_NORMAL;
+ style->fItalic = !!GetInt(buff);
+ style->fUnderline = !!GetInt(buff);
+ style->fStrikeOut = !!GetInt(buff);
+ style->fBlur = !!GetInt(buff);
+ style->fontScaleX = GetFloat(buff);
+ style->fontScaleY = GetFloat(buff);
+ style->fontSpacing = GetFloat(buff);
+ style->fontAngleX = GetFloat(buff);
+ style->fontAngleY = GetFloat(buff);
+ style->fontAngleZ = GetFloat(buff);
+ style->borderStyle = GetInt(buff);
+ style->outlineWidthX = style->outlineWidthY = GetFloat(buff);
+ style->shadowDepthX = style->shadowDepthY = GetFloat(buff);
+ style->scrAlignment = GetInt(buff);
+ style->marginRect.left = GetInt(buff);
+ style->marginRect.right = GetInt(buff);
+ style->marginRect.top = style->marginRect.bottom = GetInt(buff);
+ style->charSet = GetInt(buff);
+
+ style->fontScaleX = max(style->fontScaleX, 0);
+ style->fontScaleY = max(style->fontScaleY, 0);
+ style->fontSpacing = max(style->fontSpacing, 0);
+ style->borderStyle = style->borderStyle == 1 ? 0 : style->borderStyle == 3 ? 1 : 0;
+ style->outlineWidthX = max(style->outlineWidthX, 0);
+ style->outlineWidthY = max(style->outlineWidthY, 0);
+ style->shadowDepthX = max(style->shadowDepthX, 0);
+ style->shadowDepthY = max(style->shadowDepthY, 0);
+
+ ret.AddStyle(StyleName, style);
+ }
+ catch(...)
+ {
+ delete style;
+ return(false);
+ }
+ }
+ else if(entry == L"line")
+ {
+ try
+ {
+ CString id;
+ int hh1, mm1, ss1, ms1, hh2, mm2, ss2, ms2, layer = 0;
+ CString Style, Actor;
+ CRect marginRect;
+
+ if(GetStr(buff) != L"D") continue;
+ id = GetStr(buff);
+ layer = GetInt(buff);
+ hh1 = GetInt(buff, ':');
+ mm1 = GetInt(buff, ':');
+ ss1 = GetInt(buff, '.');
+ ms1 = GetInt(buff);
+ hh2 = GetInt(buff, ':');
+ mm2 = GetInt(buff, ':');
+ ss2 = GetInt(buff, '.');
+ ms2 = GetInt(buff);
+ Style = WToT(GetStr(buff)) + _T("_") + WToT(GetStr(buff));
+ Actor = WToT(GetStr(buff));
+ marginRect.left = GetInt(buff);
+ marginRect.right = GetInt(buff);
+ marginRect.top = marginRect.bottom = GetInt(buff);
+
+ Style.TrimLeft('*');
+ if(!Style.CompareNoCase(_T("Default"))) Style = _T("Default");
+
+ ret.Add(buff,
+ file->IsUnicode(),
+ (((hh1*60 + mm1)*60) + ss1)*1000 + ms1,
+ (((hh2*60 + mm2)*60) + ss2)*1000 + ms2,
+ Style, Actor, _T(""),
+ marginRect,
+ layer);
+ }
+ catch(...)
+ {
+ return(false);
+ }
+ }
+ else if(entry == L"fontname")
+ {
+ LoadUUEFont(file);
+ }
+#ifdef _VSMOD // load png graphic from text resources
+ else if(entry == L"filename")
+ {
+ ret.LoadUUEFile(file,GetStr(buff));
+ }
+#endif
+ }
+
+ return(ret.GetCount() > 0);
+}
+
+#include "USFSubtitles.h"
+
+static bool OpenUSF(CTextFile* file, CSimpleTextSubtitle& ret, int CharSet)
+{
+ CString str;
+ while(file->ReadString(str))
+ {
+ if(str.Find(_T("USFSubtitles")) >= 0)
+ {
+ CUSFSubtitles usf;
+ if(usf.Read(file->GetFilePath()) && usf.ConvertToSTS(ret))
+ return(true);
+
+ break;
+ }
+ }
+
+ return(false);
+}
+
+static CStringW MPL22SSA(CStringW str)
+{
+ CAtlList<CStringW> sl;
+ Explode(str, sl, '|');
+ POSITION pos = sl.GetHeadPosition();
+ while(pos)
+ {
+ CStringW& s = sl.GetNext(pos);
+ if(s[0] == '/')
+ {
+ s = L"{\\i1}" + s.Mid(1) + L"{\\i0}";
+ }
+ }
+ str = Implode(sl, '\n');
+ str.Replace(L"\n", L"\\N");
+ return str;
+}
+
+static bool OpenMPL2(CTextFile* file, CSimpleTextSubtitle& ret, int CharSet)
+{
+ CStringW buff;
+ while(file->ReadString(buff))
+ {
+ buff.Trim();
+ if(buff.IsEmpty()) continue;
+
+ int start, end;
+ int c = swscanf(buff, L"[%d][%d]", &start, &end);
+
+ if(c == 2)
+ {
+ ret.Add(
+ MPL22SSA(buff.Mid(buff.Find(']', buff.Find(']')+1)+1)),
+ file->IsUnicode(),
+ start*100, end*100);
+ }
+ else if(c != EOF) // might be another format
+ {
+ return(false);
+ }
+ }
+
+ return(ret.GetCount() > 0);
+}
+
+typedef bool (*STSOpenFunct)(CTextFile* file, CSimpleTextSubtitle& ret, int CharSet);
+
+static bool OpenRealText(CTextFile* file, CSimpleTextSubtitle& ret, int CharSet);
+
+typedef struct
+{
+ STSOpenFunct open;
+ tmode mode;
+ exttype type;
+} OpenFunctStruct;
+
+static OpenFunctStruct OpenFuncts[] =
+{
+ OpenSubRipper, TIME, EXTSRT,
+ OpenOldSubRipper, TIME, EXTSRT,
+ OpenSubViewer, TIME, EXTSUB,
+ OpenMicroDVD, FRAME, EXTSSA,
+ OpenSami, TIME, EXTSMI,
+ OpenVPlayer, TIME, EXTSRT,
+ OpenSubStationAlpha, TIME, EXTSSA,
+ OpenXombieSub, TIME, EXTXSS,
+ OpenUSF, TIME, EXTUSF,
+ OpenMPL2, TIME, EXTSRT,
+ OpenRealText, TIME, EXTRT,
+};
+
+static int nOpenFuncts = countof(OpenFuncts);
+
+//
+
+CSimpleTextSubtitle::CSimpleTextSubtitle()
+{
+ m_mode = TIME;
+ m_dstScreenSize = CSize(0, 0);
+ m_defaultWrapStyle = 0;
+ m_collisions = 0;
+ m_fScaledBAS = false;
+ m_encoding = CTextFile::ASCII;
+ m_lcid = 0;
+ m_ePARCompensationType = EPCTDisabled;
+ m_dPARCompensation = 1.0;
+
+#ifdef _VSMOD // indexing
+ ind_size = 0;
+#endif
+}
+
+CSimpleTextSubtitle::~CSimpleTextSubtitle()
+{
+ Empty();
+}
+/*
+CSimpleTextSubtitle::CSimpleTextSubtitle(CSimpleTextSubtitle& sts)
+{
+ *this = sts;
+}
+
+CSimpleTextSubtitle& CSimpleTextSubtitle::operator = (CSimpleTextSubtitle& sts)
+{
+ Empty();
+
+ m_name = sts.m_name;
+ m_mode = sts.m_mode;
+ m_dstScreenSize = sts.m_dstScreenSize;
+ m_defaultWrapStyle = sts.m_defaultWrapStyle;
+ m_collisions = sts.m_collisions;
+ m_fScaledBAS = sts.m_fScaledBAS;
+ m_fSSA = sts.m_fSSA;
+ m_fUsingAutoGeneratedDefaultStyle = sts.m_fUsingAutoGeneratedDefaultStyle;
+ CopyStyles(sts.m_styles);
+ m_segments.Copy(sts.m_segments);
+ Copy(sts);
+
+ return(*this);
+}
+*/
+
+void CSimpleTextSubtitle::Copy(CSimpleTextSubtitle& sts)
+{
+ Empty();
+
+ m_name = sts.m_name;
+ m_mode = sts.m_mode;
+ m_path = sts.m_path;
+ m_exttype = sts.m_exttype;
+ m_dstScreenSize = sts.m_dstScreenSize;
+ m_defaultWrapStyle = sts.m_defaultWrapStyle;
+ m_collisions = sts.m_collisions;
+ m_fScaledBAS = sts.m_fScaledBAS;
+ m_encoding = sts.m_encoding;
+ m_fUsingAutoGeneratedDefaultStyle = sts.m_fUsingAutoGeneratedDefaultStyle;
+ CopyStyles(sts.m_styles);
+ m_segments.Copy(sts.m_segments);
+ __super::Copy(sts);
+}
+
+void CSimpleTextSubtitle::Append(CSimpleTextSubtitle& sts, int timeoff)
+{
+ if(timeoff < 0)
+ {
+ timeoff = GetCount() > 0 ? GetAt(GetCount()-1).end : 0;
+ }
+
+ for(ptrdiff_t i = 0, j = GetCount(); i < j; i++)
+ {
+ if(GetAt(i).start > timeoff)
+ {
+ RemoveAt(i, j - i);
+ break;
+ }
+ }
+
+ CopyStyles(sts.m_styles, true);
+
+ for(ptrdiff_t i = 0, j = sts.GetCount(); i < j; i++)
+ {
+ STSEntry stse = sts.GetAt(i);
+ stse.start += timeoff;
+ stse.end += timeoff;
+ stse.readorder += GetCount();
+ __super::Add(stse);
+ }
+
+ CreateSegments();
+}
+
+void CSTSStyleMap::Free()
+{
+ POSITION pos = GetStartPosition();
+ while(pos)
+ {
+ CString key;
+ STSStyle* val;
+ GetNextAssoc(pos, key, val);
+ delete val;
+ }
+
+ RemoveAll();
+}
+
+bool CSimpleTextSubtitle::CopyStyles(const CSTSStyleMap& styles, bool fAppend)
+{
+ if(!fAppend) m_styles.Free();
+
+ POSITION pos = styles.GetStartPosition();
+ while(pos)
+ {
+ CString key;
+ STSStyle* val;
+ styles.GetNextAssoc(pos, key, val);
+
+ STSStyle* s = DNew STSStyle;
+ if(!s) return(false);
+
+ *s = *val;
+
+ AddStyle(key, s);
+ }
+
+ return(true);
+}
+
+void CSimpleTextSubtitle::Empty()
+{
+ m_dstScreenSize = CSize(0, 0);
+ m_styles.Free();
+ m_segments.RemoveAll();
+ RemoveAll();
+
+#ifdef _VSMOD // indexing
+ if(ind_size>0)
+ {
+ delete ind_time;
+ delete ind_pos;
+ }
+#endif
+}
+
+void CSimpleTextSubtitle::Add(CStringW str, bool fUnicode, int start, int end, CString style, CString actor, CString effect, CRect marginRect, int layer, int readorder)
+{
+ if(str.Trim().IsEmpty() || start > end) return;
+
+ str.Remove('\r');
+ str.Replace(L"\n", L"\\N");
+ if(style.IsEmpty()) style = _T("Default");
+ style.TrimLeft('*');
+
+ STSEntry sub;
+ sub.str = str;
+ sub.fUnicode = fUnicode;
+ sub.style = style;
+ sub.actor = actor;
+ sub.effect = effect;
+ sub.marginRect = marginRect;
+ sub.layer = layer;
+ sub.start = start;
+ sub.end = end;
+ sub.readorder = readorder < 0 ? GetCount() : readorder;
+
+ int n = __super::Add(sub);
+
+#ifndef _VSMOD
+ int len = m_segments.GetCount();
+
+ if(len == 0)
+ {
+ STSSegment stss(start, end);
+ stss.subs.Add(n);
+ m_segments.Add(stss);
+ }
+ else if(end <= m_segments[0].start)
+ {
+ STSSegment stss(start, end);
+ stss.subs.Add(n);
+ m_segments.InsertAt(0, stss);
+ }
+ else if(start >= m_segments[len-1].end)
+ {
+ STSSegment stss(start, end);
+ stss.subs.Add(n);
+ m_segments.Add(stss);
+ }
+ else
+ {
+ if(start < m_segments[0].start)
+ {
+ STSSegment stss(start, m_segments[0].start);
+ stss.subs.Add(n);
+ start = m_segments[0].start;
+ m_segments.InsertAt(0, stss);
+ }
+
+ for(ptrdiff_t i = 0; i < m_segments.GetCount(); i++)
+ {
+ STSSegment& s = m_segments[i];
+
+ if(start >= s.end)
+ continue;
+
+ if(end <= s.start)
+ break;
+
+ if(s.start < start && start < s.end)
+ {
+ STSSegment stss(s.start, start);
+ stss.subs.Copy(s.subs);
+ s.start = start;
+ m_segments.InsertAt(i, stss);
+ continue;
+ }
+
+ if(start <= s.start && s.end <= end)
+ {
+ for(ptrdiff_t j = 0, k = s.subs.GetCount(); j <= k; j++)
+ {
+ if(j == k || sub.readorder < GetAt(s.subs[j]).readorder)
+ s.subs.InsertAt(j, n);
+ }
+// s.subs.Add(n);
+ }
+
+ if(s.start < end && end < s.end)
+ {
+ STSSegment stss(s.start, end);
+ stss.subs.Copy(s.subs);
+ for(ptrdiff_t j = 0, k = s.subs.GetCount(); j <= k; j++)
+ {
+ if(j == k || sub.readorder < GetAt(stss.subs[j]).readorder)
+ stss.subs.InsertAt(j, n);
+ }
+// stss.subs.Add(n);
+ s.start = end;
+ m_segments.InsertAt(i, stss);
+ }
+ }
+
+ if(end > m_segments[m_segments.GetCount()-1].end)
+ {
+ STSSegment stss(m_segments[m_segments.GetCount()-1].end, end);
+ stss.subs.Add(n);
+ m_segments.Add(stss);
+ }
+ }
+#endif
+}
+
+
+#ifdef _VSMOD
+void CSimpleTextSubtitle::MakeIndex(int SizeOfSegment)
+{
+ int cnt = m_segments.GetCount();
+ if (SizeOfSegment==0) // autosize
+ {
+ // 100000 lines == 1300 segments
+ // TODO: make gooood =D
+ if(cnt<100)
+ {
+ SizeOfSegment = (cnt==0) ? 1 : cnt;
+ }
+ else if (cnt<1000)
+ {
+ SizeOfSegment = cnt / 50;
+ }
+ else
+ {
+ SizeOfSegment = cnt / 100;
+ }
+ }
+
+ ind_size = cnt / SizeOfSegment;
+
+ ind_time = new DWORD[ind_size];
+ ind_pos = new DWORD[ind_size];
+
+ for(int i = 0; i<ind_size; i++)
+ {
+ int pos = i * SizeOfSegment;
+ ind_time[i] = m_segments[pos].start;
+ ind_pos[i] = pos;
+ }
+}
+#endif
+
+STSStyle* CSimpleTextSubtitle::CreateDefaultStyle(int CharSet)
+{
+ CString def(_T("Default"));
+
+ STSStyle* ret = NULL;
+
+ if(!m_styles.Lookup(def, ret))
+ {
+ STSStyle* style = DNew STSStyle();
+ style->charSet = CharSet;
+ AddStyle(def, style);
+ m_styles.Lookup(def, ret);
+
+ m_fUsingAutoGeneratedDefaultStyle = true;
+ }
+ else
+ {
+ m_fUsingAutoGeneratedDefaultStyle = false;
+ }
+
+ return ret;
+}
+
+void CSimpleTextSubtitle::ChangeUnknownStylesToDefault()
+{
+ CAtlMap<CString, STSStyle*, CStringElementTraits<CString> > unknown;
+ bool fReport = true;
+
+ for(ptrdiff_t i = 0; i < GetCount(); i++)
+ {
+ STSEntry& stse = GetAt(i);
+
+ STSStyle* val;
+ if(!m_styles.Lookup(stse.style, val))
+ {
+ if(!unknown.Lookup(stse.style, val))
+ {
+ if(fReport)
+ {
+ CString msg;
+ msg.Format(_T("Unknown style found: \"%s\", changed to \"Default\"!\n\nPress Cancel to ignore further warnings."), stse.style);
+ if(MessageBox(NULL, msg, _T("Warning"), MB_OKCANCEL|MB_ICONWARNING) != IDOK) fReport = false;
+ }
+
+ unknown[stse.style] = NULL;
+ }
+
+ stse.style = _T("Default");
+ }
+ }
+}
+
+void CSimpleTextSubtitle::AddStyle(CString name, STSStyle* style)
+{
+ int i, j;
+
+ if(name.IsEmpty()) name = _T("Default");
+
+ STSStyle* val;
+ if(m_styles.Lookup(name, val))
+ {
+ if(*val == *style)
+ {
+ delete style;
+ return;
+ }
+
+ int len = name.GetLength();
+
+ for(i = len; i > 0 && _istdigit(name[i-1]); i--);
+
+ int idx = 1;
+
+ CString name2 = name;
+
+ if(i < len && _stscanf(name.Right(len-i), _T("%d"), &idx) == 1)
+ {
+ name2 = name.Left(i);
+ }
+
+ idx++;
+
+ CString name3;
+ do
+ {
+ name3.Format(_T("%s%d"), name2, idx);
+ idx++;
+ }
+ while(m_styles.Lookup(name3));
+
+ m_styles.RemoveKey(name);
+ m_styles[name3] = val;
+
+ for(i = 0, j = GetCount(); i < j; i++)
+ {
+ STSEntry& stse = GetAt(i);
+ if(stse.style == name) stse.style = name3;
+ }
+ }
+
+ m_styles[name] = style;
+}
+
+bool CSimpleTextSubtitle::SetDefaultStyle(STSStyle& s)
+{
+ STSStyle* val;
+ if(!m_styles.Lookup(_T("Default"), val)) return false;
+ *val = s;
+ m_fUsingAutoGeneratedDefaultStyle = false;
+ return true;
+}
+
+bool CSimpleTextSubtitle::GetDefaultStyle(STSStyle& s)
+{
+ STSStyle* val;
+ if(!m_styles.Lookup(_T("Default"), val)) return false;
+ s = *val;
+ return true;
+}
+
+void CSimpleTextSubtitle::ConvertToTimeBased(double fps)
+{
+ if(m_mode == TIME) return;
+
+ for(ptrdiff_t i = 0, j = GetCount(); i < j; i++)
+ {
+ STSEntry& stse = (*this)[i];
+ stse.start = int(1.0 * stse.start * 1000 / fps + 0.5);
+ stse.end = int(1.0 * stse.end * 1000 / fps + 0.5);
+ }
+
+ m_mode = TIME;
+
+ CreateSegments();
+}
+
+void CSimpleTextSubtitle::ConvertToFrameBased(double fps)
+{
+ if(m_mode == FRAME) return;
+
+ for(ptrdiff_t i = 0, j = GetCount(); i < j; i++)
+ {
+ STSEntry& stse = (*this)[i];
+ stse.start = int(1.0 * stse.start * fps / 1000 + 0.5);
+ stse.end = int(1.0 * stse.end * fps / 1000 + 0.5);
+ }
+
+ m_mode = FRAME;
+
+ CreateSegments();
+}
+
+int CSimpleTextSubtitle::SearchSub(int t, double fps)
+{
+ int i = 0, j = GetCount() - 1, ret = -1;
+
+ if(j >= 0 && t >= TranslateStart(j, fps))
+ {
+ return(j);
+ }
+
+ while(i < j)
+ {
+ int mid = (i + j) >> 1;
+
+ int midt = TranslateStart(mid, fps);
+
+ if(t == midt)
+ {
+ while(mid > 0 && t == TranslateStart(mid-1, fps)) mid--;
+ ret = mid;
+ break;
+ }
+ else if(t < midt)
+ {
+ ret = -1;
+ if(j == mid) mid--;
+ j = mid;
+ }
+ else if(t > midt)
+ {
+ ret = mid;
+ if(i == mid) mid++;
+ i = mid;
+ }
+ }
+
+ return(ret);
+}
+
+const STSSegment* CSimpleTextSubtitle::SearchSubs(int t, double fps, /*[out]*/ int* iSegment, int* nSegments)
+{
+ int i = 0, j = m_segments.GetCount() - 1, ret = -1;
+
+ if(nSegments) *nSegments = j+1;
+
+ // last segment
+ if(j >= 0 && t >= TranslateSegmentStart(j, fps) && t < TranslateSegmentEnd(j, fps))
+ {
+ if(iSegment) *iSegment = j;
+ return(&m_segments[j]);
+ }
+
+ // after last segment
+ if(j >= 0 && t >= TranslateSegmentEnd(j, fps))
+ {
+ if(iSegment) *iSegment = j+1;
+ return(NULL);
+ }
+
+ // before first segment
+ if(j > 0 && t < TranslateSegmentStart(i, fps))
+ {
+ if(iSegment) *iSegment = -1;
+ return(NULL);
+ }
+
+#ifdef _VSMOD
+ // find bounds
+ // is this nya?
+ for(ptrdiff_t k = 0; k < ind_size; k++)
+ {
+ if(ind_time[k]>t)
+ {
+ if(k==0) break;
+ i = ind_pos[k-1];
+ j = ind_pos[k];
+ break;
+ }
+ }
+#endif
+
+ while(i < j)
+ {
+ int mid = (i + j) >> 1;
+
+ int midt = TranslateSegmentStart(mid, fps);
+
+ if(t == midt)
+ {
+ ret = mid;
+ break;
+ }
+ else if(t < midt)
+ {
+ ret = -1;
+ if(j == mid) mid--;
+ j = mid;
+ }
+ else if(t > midt)
+ {
+ ret = mid;
+ if(i == mid) mid++;
+ i = mid;
+ }
+ }
+
+ if(0 <= ret && ret < m_segments.GetCount())
+ {
+ if(iSegment) *iSegment = ret;
+ }
+
+ if(0 <= ret && ret < m_segments.GetCount()
+ && m_segments[ret].subs.GetCount() > 0
+ && TranslateSegmentStart(ret, fps) <= t && t < TranslateSegmentEnd(ret, fps))
+ {
+ return(&m_segments[ret]);
+ }
+
+ return(NULL);
+}
+
+int CSimpleTextSubtitle::TranslateStart(int i, double fps)
+{
+ return(i < 0 || GetCount() <= i ? -1 :
+ m_mode == TIME ? GetAt(i).start :
+ m_mode == FRAME ? (int)(GetAt(i).start*1000/fps) :
+ 0);
+}
+
+int CSimpleTextSubtitle::TranslateEnd(int i, double fps)
+{
+ return(i < 0 || GetCount() <= i ? -1 :
+ m_mode == TIME ? GetAt(i).end :
+ m_mode == FRAME ? (int)(GetAt(i).end*1000/fps) :
+ 0);
+}
+
+int CSimpleTextSubtitle::TranslateSegmentStart(int i, double fps)
+{
+ return(i < 0 || m_segments.GetCount() <= i ? -1 :
+ m_mode == TIME ? m_segments[i].start :
+ m_mode == FRAME ? (int)(m_segments[i].start*1000/fps) :
+ 0);
+}
+
+int CSimpleTextSubtitle::TranslateSegmentEnd(int i, double fps)
+{
+ return(i < 0 || m_segments.GetCount() <= i ? -1 :
+ m_mode == TIME ? m_segments[i].end :
+ m_mode == FRAME ? (int)(m_segments[i].end*1000/fps) :
+ 0);
+}
+
+STSStyle* CSimpleTextSubtitle::GetStyle(int i)
+{
+ CString def = _T("Default");
+
+ STSStyle* style = NULL;
+ m_styles.Lookup(GetAt(i).style, style);
+
+ STSStyle* defstyle = NULL;
+ m_styles.Lookup(def, defstyle);
+
+ if(!style)
+ {
+ style = defstyle;
+ }
+
+ ASSERT(style);
+
+ return style;
+}
+
+bool CSimpleTextSubtitle::GetStyle(int i, STSStyle& stss)
+{
+ CString def = _T("Default");
+
+ STSStyle* style = NULL;
+ m_styles.Lookup(GetAt(i).style, style);
+
+ STSStyle* defstyle = NULL;
+ m_styles.Lookup(def, defstyle);
+
+ if(!style)
+ {
+ if(!defstyle)
+ {
+ defstyle = CreateDefaultStyle(DEFAULT_CHARSET);
+ }
+
+ style = defstyle;
+ }
+
+ if(!style)
+ {
+ ASSERT(0);
+ return false;
+ }
+
+ stss = *style;
+ if(stss.relativeTo == 2 && defstyle)
+ stss.relativeTo = defstyle->relativeTo;
+
+ return true;
+}
+
+int CSimpleTextSubtitle::GetCharSet(int i)
+{
+ STSStyle stss;
+ GetStyle(i, stss);
+ return(stss.charSet);
+}
+
+bool CSimpleTextSubtitle::IsEntryUnicode(int i)
+{
+ return(GetAt(i).fUnicode);
+}
+
+void CSimpleTextSubtitle::ConvertUnicode(int i, bool fUnicode)
+{
+ STSEntry& stse = GetAt(i);
+
+ if(stse.fUnicode ^ fUnicode)
+ {
+ int CharSet = GetCharSet(i);
+
+ stse.str = fUnicode
+ ? MBCSSSAToUnicode(stse.str, CharSet)
+ : UnicodeSSAToMBCS(stse.str, CharSet);
+
+ stse.fUnicode = fUnicode;
+ }
+}
+
+CStringA CSimpleTextSubtitle::GetStrA(int i, bool fSSA)
+{
+ return(WToA(GetStrWA(i, fSSA)));
+}
+
+CStringW CSimpleTextSubtitle::GetStrW(int i, bool fSSA)
+{
+ bool fUnicode = IsEntryUnicode(i);
+ int CharSet = GetCharSet(i);
+
+ CStringW str = GetAt(i).str;
+
+ if(!fUnicode)
+ str = MBCSSSAToUnicode(str, CharSet);
+
+ if(!fSSA)
+ str = RemoveSSATags(str, fUnicode, CharSet);
+
+ return(str);
+}
+
+CStringW CSimpleTextSubtitle::GetStrWA(int i, bool fSSA)
+{
+ bool fUnicode = IsEntryUnicode(i);
+ int CharSet = GetCharSet(i);
+
+ CStringW str = GetAt(i).str;
+
+ if(fUnicode)
+ str = UnicodeSSAToMBCS(str, CharSet);
+
+ if(!fSSA)
+ str = RemoveSSATags(str, fUnicode, CharSet);
+
+ return(str);
+}
+
+void CSimpleTextSubtitle::SetStr(int i, CStringA str, bool fUnicode)
+{
+ SetStr(i, AToW(str), false);
+}
+
+void CSimpleTextSubtitle::SetStr(int i, CStringW str, bool fUnicode)
+{
+ STSEntry& stse = GetAt(i);
+
+ str.Replace(L"\n", L"\\N");
+
+ if(stse.fUnicode && !fUnicode) stse.str = MBCSSSAToUnicode(str, GetCharSet(i));
+ else if(!stse.fUnicode && fUnicode) stse.str = UnicodeSSAToMBCS(str, GetCharSet(i));
+ else stse.str = str;
+}
+
+static int comp1(const void* a, const void* b)
+{
+ int ret = ((STSEntry*)a)->start - ((STSEntry*)b)->start;
+ if(ret == 0) ret = ((STSEntry*)a)->layer - ((STSEntry*)b)->layer;
+ if(ret == 0) ret = ((STSEntry*)a)->readorder - ((STSEntry*)b)->readorder;
+ return(ret);
+}
+
+static int comp2(const void* a, const void* b)
+{
+ return(((STSEntry*)a)->readorder - ((STSEntry*)b)->readorder);
+}
+
+void CSimpleTextSubtitle::Sort(bool fRestoreReadorder)
+{
+ qsort(GetData(), GetCount(), sizeof(STSEntry), !fRestoreReadorder ? comp1 : comp2);
+ CreateSegments();
+}
+
+static int intcomp(const void* i1, const void* i2)
+{
+ return(*((int*)i1) - *((int*)i2));
+}
+
+void CSimpleTextSubtitle::CreateSegments()
+{
+ m_segments.RemoveAll();
+
+ int i, j;
+
+ CAtlArray<int> breakpoints;
+
+ for(i = 0; i < GetCount(); i++)
+ {
+ STSEntry& stse = GetAt(i);
+ breakpoints.Add(stse.start);
+ breakpoints.Add(stse.end);
+ }
+
+ qsort(breakpoints.GetData(), breakpoints.GetCount(), sizeof(int), intcomp);
+
+ int* ptr = breakpoints.GetData(), prev = ptr ? *ptr : NULL;
+
+ for(i = breakpoints.GetCount(); i > 0; i--, ptr++)
+ {
+ if(*ptr != prev)
+ {
+ m_segments.Add(STSSegment(prev, *ptr));
+ prev = *ptr;
+ }
+ }
+
+ for(i = 0; i < GetCount(); i++)
+ {
+ STSEntry& stse = GetAt(i);
+ for(j = 0; j < m_segments.GetCount() && m_segments[j].start < stse.start; j++);
+ for(; j < m_segments.GetCount() && m_segments[j].end <= stse.end; j++)
+ m_segments[j].subs.Add(i);
+ }
+
+ OnChanged();
+/*
+ for(i = 0, j = m_segments.GetCount(); i < j; i++)
+ {
+ STSSegment& stss = m_segments[i];
+
+ TRACE(_T("%d - %d"), stss.start, stss.end);
+
+ for(ptrdiff_t k = 0, l = stss.subs.GetCount(); k < l; k++)
+ {
+ TRACE(_T(", %d"), stss.subs[k]);
+ }
+
+ TRACE(_T("\n"));
+ }
+*/
+}
+
+bool CSimpleTextSubtitle::Open(CString fn, int CharSet, CString name)
+{
+ Empty();
+
+ CWebTextFile f;
+ if(!f.Open(fn)) return(false);
+
+ fn.Replace('\\', '/');
+ if(name.IsEmpty())
+ {
+ name = fn.Left(fn.ReverseFind('.'));
+ name = name.Mid(name.ReverseFind('/')+1);
+ int len = name.GetLength();
+ int pos = name.ReverseFind('.') + 1;
+ if ((len - pos) > 1)
+ name = name.Mid(pos);
+ }
+
+ return(Open(&f, CharSet, name));
+}
+
+static int CountLines(CTextFile* f, ULONGLONG from, ULONGLONG to)
+{
+ int n = 0;
+ CString s;
+ f->Seek(from, 0);
+ while(f->ReadString(s) && f->GetPosition() < to) n++;
+ return(n);
+}
+
+bool CSimpleTextSubtitle::Open(CTextFile* f, int CharSet, CString name)
+{
+ Empty();
+
+ ULONGLONG pos = f->GetPosition();
+
+ for(ptrdiff_t i = 0; i < nOpenFuncts; i++)
+ {
+ if(!OpenFuncts[i].open(f, *this, CharSet) /*|| !GetCount()*/)
+ {
+ if(GetCount() > 0)
+ {
+ int n = CountLines(f, pos, f->GetPosition());
+ CString s;
+ s.Format(_T("Syntax error at line %d!\t"), n+1);
+ AfxMessageBox(s, MB_OK|MB_ICONERROR);
+ Empty();
+ break;
+ }
+
+ f->Seek(pos, 0);
+ Empty();
+ continue;
+ }
+
+ m_name = name;
+ m_exttype = OpenFuncts[i].type;
+ m_mode = OpenFuncts[i].mode;
+ m_encoding = f->GetEncoding();
+ m_path = f->GetFilePath();
+
+// Sort();
+ CreateSegments();
+#ifdef _VSMOD // indexing
+ MakeIndex(0);
+#endif
+ CWebTextFile f2;
+ if(f2.Open(f->GetFilePath() + _T(".style")))
+ OpenSubStationAlpha(&f2, *this, CharSet);
+
+ CreateDefaultStyle(CharSet);
+
+ ChangeUnknownStylesToDefault();
+
+ if(m_dstScreenSize == CSize(0, 0)) m_dstScreenSize = CSize(384, 288);
+
+ return(true);
+ }
+
+ return(false);
+}
+
+bool CSimpleTextSubtitle::Open(BYTE* data, int len, int CharSet, CString name)
+{
+ TCHAR path[_MAX_PATH];
+ if(!GetTempPath(_MAX_PATH, path)) return(false);
+
+ TCHAR fn[_MAX_PATH];
+ if(!GetTempFileName(path, _T("vs"), 0, fn)) return(false);
+
+ FILE* tmp = _tfopen(fn, _T("wb"));
+ if(!tmp) return(false);
+
+ int i = 0;
+ for(; i <= (len-1024); i += 1024) fwrite(&data[i], 1024, 1, tmp);
+ if(len > i) fwrite(&data[i], len - i, 1, tmp);
+
+ fclose(tmp);
+
+ bool fRet = Open(fn, CharSet, name);
+
+ _tremove(fn);
+
+ return(fRet);
+}
+
+bool CSimpleTextSubtitle::SaveAs(CString fn, exttype et, double fps, CTextFile::enc e)
+{
+ if(fn.Mid(fn.ReverseFind('.')+1).CompareNoCase(exttypestr[et]))
+ {
+ if(fn[fn.GetLength()-1] != '.') fn += _T(".");
+ fn += exttypestr[et];
+ }
+
+ CTextFile f;
+ if(!f.Save(fn, e))
+ return(false);
+
+ if(et == EXTSMI)
+ {
+ CString str;
+
+ str += _T("<SAMI>\n<HEAD>\n");
+ str += _T("<STYLE TYPE=\"text/css\">\n");
+ str += _T("<!--\n");
+ str += _T("P {margin-left: 16pt; margin-right: 16pt; margin-bottom: 16pt; margin-top: 16pt;\n");
+ str += _T(" text-align: center; font-size: 18pt; font-family: arial; font-weight: bold; color: #f0f0f0;}\n");
+ str += _T(".UNKNOWNCC {Name:Unknown; lang:en-US; SAMIType:CC;}\n");
+ str += _T("-->\n");
+ str += _T("</STYLE>\n");
+ str += _T("</HEAD>\n");
+ str += _T("\n");
+ str += _T("<BODY>\n");
+
+ f.WriteString(str);
+ }
+ else if(et == EXTSSA || et == EXTASS)
+ {
+ CString str;
+
+ str = _T("[Script Info]\n");
+ str += (et == EXTSSA) ? _T("; This is a Sub Station Alpha v4 script.\n") : _T("; This is an Advanced Sub Station Alpha v4+ script.\n");
+ str += _T("; For Sub Station Alpha info and downloads,\n");
+ str += _T("; go to http://www.eswat.demon.co.uk/\n");
+ str += _T("; or email kotus@eswat.demon.co.uk\n");
+ str += _T("; \n");
+ if(et == EXTASS)
+ {
+ str += _T("; Advanced Sub Station Alpha script format developed by #Anime-Fansubs@EfNET\n");
+ str += _T("; http://www.anime-fansubs.org\n");
+ str += _T("; \n");
+ str += _T("; For additional info and downloads go to http://gabest.org/\n");
+ str += _T("; or email gabest@freemail.hu\n");
+ str += _T("; \n");
+ }
+ str += _T("; Note: This file was saved by Subresync.\n");
+ str += _T("; \n");
+ str += (et == EXTSSA) ? _T("ScriptType: v4.00\n") : _T("ScriptType: v4.00+\n");
+ str += (m_collisions == 0) ? _T("Collisions: Normal\n") : _T("Collisions: Reverse\n");
+ if(et == EXTASS && m_fScaledBAS) str += _T("ScaledBorderAndShadow: Yes\n");
+ str += _T("PlayResX: %d\n");
+ str += _T("PlayResY: %d\n");
+ str += _T("Timer: 100.0000\n");
+ str += _T("\n");
+ str += (et == EXTSSA)
+ ? _T("[V4 Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding\n")
+ : _T("[V4+ Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n");
+
+ CString str2;
+ str2.Format(str, m_dstScreenSize.cx, m_dstScreenSize.cy);
+ f.WriteString(str2);
+
+ str = (et == EXTSSA)
+ ? _T("Style: %s,%s,%d,&H%06x,&H%06x,&H%06x,&H%06x,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n")
+ : _T("Style: %s,%s,%d,&H%08x,&H%08x,&H%08x,&H%08x,%d,%d,%d,%d,%d,%d,%d,%.2f,%d,%d,%d,%d,%d,%d,%d,%d\n");
+
+ POSITION pos = m_styles.GetStartPosition();
+ while(pos)
+ {
+ CString key;
+ STSStyle* s;
+ m_styles.GetNextAssoc(pos, key, s);
+
+ if(et == EXTSSA)
+ {
+ CString str2;
+ str2.Format(str, key,
+ s->fontName, (int)s->fontSize,
+ s->colors[0]&0xffffff,
+ s->colors[1]&0xffffff,
+ s->colors[2]&0xffffff,
+ s->colors[3]&0xffffff,
+ s->fontWeight > FW_NORMAL ? -1 : 0, s->fItalic ? -1 : 0,
+ s->borderStyle == 0 ? 1 : s->borderStyle == 1 ? 3 : 0,
+ (int)s->outlineWidthY, (int)s->shadowDepthY,
+ s->scrAlignment <= 3 ? s->scrAlignment : s->scrAlignment <= 6 ? ((s->scrAlignment-3)|8) : s->scrAlignment <= 9 ? ((s->scrAlignment-6)|4) : 2,
+ s->marginRect.left, s->marginRect.right, (s->marginRect.top + s->marginRect.bottom) / 2,
+ s->alpha[0],
+ s->charSet);
+ f.WriteString(str2);
+ }
+ else
+ {
+ CString str2;
+ str2.Format(str, key,
+ s->fontName, (int)s->fontSize,
+ (s->colors[0]&0xffffff) | (s->alpha[0]<<24),
+ (s->colors[1]&0xffffff) | (s->alpha[1]<<24),
+ (s->colors[2]&0xffffff) | (s->alpha[2]<<24),
+ (s->colors[3]&0xffffff) | (s->alpha[3]<<24),
+ s->fontWeight > FW_NORMAL ? -1 : 0,
+ s->fItalic ? -1 : 0, s->fUnderline ? -1 : 0, s->fStrikeOut ? -1 : 0,
+ (int)s->fontScaleX, (int)s->fontScaleY,
+ (int)s->fontSpacing, (float)s->fontAngleZ,
+ s->borderStyle == 0 ? 1 : s->borderStyle == 1 ? 3 : 0,
+ (int)s->outlineWidthY, (int)s->shadowDepthY,
+ s->scrAlignment,
+ s->marginRect.left, s->marginRect.right, (s->marginRect.top + s->marginRect.bottom) / 2,
+ s->charSet);
+ f.WriteString(str2);
+ }
+ }
+
+ if(GetCount() > 0)
+ {
+ str = _T("\n");
+ str += _T("[Events]\n");
+ str += (et == EXTSSA)
+ ? _T("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n")
+ : _T("Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text\n");
+ f.WriteString(str);
+ }
+ }
+
+ CStringW fmt =
+ et == EXTSRT ? L"%d\n%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d\n%s\n\n" :
+ et == EXTSUB ? L"{%d}{%d}%s\n" :
+ et == EXTSMI ? L"<SYNC Start=%d><P Class=UNKNOWNCC>\n%s\n<SYNC Start=%d><P Class=UNKNOWNCC>&nbsp;\n" :
+ et == EXTPSB ? L"{%d:%02d:%02d}{%d:%02d:%02d}%s\n" :
+ et == EXTSSA ? L"Dialogue: Marked=0,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d,%s,%s,%04d,%04d,%04d,%s,%s\n" :
+ et == EXTASS ? L"Dialogue: %d,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d,%s,%s,%04d,%04d,%04d,%s,%s\n" :
+ L"";
+// Sort(true);
+
+ for(ptrdiff_t i = 0, j = GetCount(), k = 0; i < j; i++)
+ {
+ STSEntry& stse = GetAt(i);
+
+ int t1 = TranslateStart(i, fps);
+ if(t1 < 0)
+ {
+ k++;
+ continue;
+ }
+
+ int t2 = TranslateEnd(i, fps);
+
+ int hh1 = (t1/60/60/1000);
+ int mm1 = (t1/60/1000)%60;
+ int ss1 = (t1/1000)%60;
+ int ms1 = (t1)%1000;
+ int hh2 = (t2/60/60/1000);
+ int mm2 = (t2/60/1000)%60;
+ int ss2 = (t2/1000)%60;
+ int ms2 = (t2)%1000;
+
+ CStringW str = f.IsUnicode()
+ ? GetStrW(i, et == EXTSSA || et == EXTASS)
+ : GetStrWA(i, et == EXTSSA || et == EXTASS);
+
+ CStringW str2;
+
+ if(et == EXTSRT)
+ {
+ str2.Format(fmt, i-k+1, hh1, mm1, ss1, ms1, hh2, mm2, ss2, ms2, str);
+ }
+ else if(et == EXTSUB)
+ {
+ str.Replace('\n', '|');
+ str2.Format(fmt, int(t1*fps/1000), int(t2*fps/1000), str);
+ }
+ else if(et == EXTSMI)
+ {
+ str.Replace(L"\n", L"<br>");
+ str2.Format(fmt, t1, str, t2);
+ }
+ else if(et == EXTPSB)
+ {
+ str.Replace('\n', '|');
+ str2.Format(fmt, hh1, mm1, ss1, hh2, mm2, ss2, str);
+ }
+ else if(et == EXTSSA)
+ {
+ str.Replace(L"\n", L"\\N");
+ str2.Format(fmt,
+ hh1, mm1, ss1, ms1/10,
+ hh2, mm2, ss2, ms2/10,
+ TToW(stse.style), TToW(stse.actor),
+ stse.marginRect.left, stse.marginRect.right, (stse.marginRect.top + stse.marginRect.bottom) / 2,
+ TToW(stse.effect), str);
+ }
+ else if(et == EXTASS)
+ {
+ str.Replace(L"\n", L"\\N");
+ str2.Format(fmt,
+ stse.layer,
+ hh1, mm1, ss1, ms1/10,
+ hh2, mm2, ss2, ms2/10,
+ TToW(stse.style), TToW(stse.actor),
+ stse.marginRect.left, stse.marginRect.right, (stse.marginRect.top + stse.marginRect.bottom) / 2,
+ TToW(stse.effect), str);
+ }
+
+ f.WriteString(str2);
+ }
+
+// Sort();
+
+ if(et == EXTSMI)
+ {
+ f.WriteString(_T("</BODY>\n</SAMI>\n"));
+ }
+
+ STSStyle* s;
+ if(!m_fUsingAutoGeneratedDefaultStyle && m_styles.Lookup(_T("Default"), s) && et != EXTSSA && et != EXTASS)
+ {
+ CTextFile f;
+ if(!f.Save(fn + _T(".style"), e))
+ return(false);
+
+ CString str, str2;
+
+ str += _T("ScriptType: v4.00+\n");
+ str += _T("PlayResX: %d\n");
+ str += _T("PlayResY: %d\n");
+ str += _T("\n");
+ str += _T("[V4+ Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n");
+ str2.Format(str, m_dstScreenSize.cx, m_dstScreenSize.cy);
+ f.WriteString(str2);
+
+ str = _T("Style: Default,%s,%d,&H%08x,&H%08x,&H%08x,&H%08x,%d,%d,%d,%d,%d,%d,%d,%.2f,%d,%d,%d,%d,%d,%d,%d,%d\n");
+ str2.Format(str,
+ s->fontName, (int)s->fontSize,
+ (s->colors[0]&0xffffff) | (s->alpha[0]<<24),
+ (s->colors[1]&0xffffff) | (s->alpha[1]<<24),
+ (s->colors[2]&0xffffff) | (s->alpha[2]<<24),
+ (s->colors[3]&0xffffff) | (s->alpha[3]<<24),
+ s->fontWeight > FW_NORMAL ? -1 : 0,
+ s->fItalic ? -1 : 0, s->fUnderline ? -1 : 0, s->fStrikeOut ? -1 : 0,
+ (int)s->fontScaleX, (int)s->fontScaleY,
+ (int)s->fontSpacing, (float)s->fontAngleZ,
+ s->borderStyle == 0 ? 1 : s->borderStyle == 1 ? 3 : 0,
+ (int)s->outlineWidthY, (int)s->shadowDepthY,
+ s->scrAlignment,
+ s->marginRect.left, s->marginRect.right, (s->marginRect.top + s->marginRect.bottom) / 2,
+ s->charSet);
+ f.WriteString(str2);
+ }
+
+ return(true);
+}
+
+////////////////////////////////////////////////////////////////////
+
+STSStyle::STSStyle()
+{
+ SetDefault();
+}
+
+#ifdef _VSMOD
+STSStyle::STSStyle(STSStyle& s)
+{
+ SetDefault();
+ mod_CopyStyleFrom(s);
+}
+#endif
+
+void STSStyle::SetDefault()
+{
+ marginRect = CRect(20, 20, 20, 20);
+ scrAlignment = 2;
+ borderStyle = 0;
+ outlineWidthX = outlineWidthY = 2;
+ shadowDepthX = shadowDepthY = 3;
+ colors[0] = 0x00ffffff;
+ colors[1] = 0x0000ffff;
+ colors[2] = 0x00000000;
+ colors[3] = 0x00000000;
+ alpha[0] = 0x00;
+ alpha[1] = 0x00;
+ alpha[2] = 0x00;
+ alpha[3] = 0x80;
+ charSet = DEFAULT_CHARSET;
+ fontName = _T("Arial");
+ fontSize = 18;
+ fontScaleX = fontScaleY = 100;
+ fontSpacing = 0;
+ fontWeight = FW_BOLD;
+ fItalic = false;
+ fUnderline = false;
+ fStrikeOut = false;
+ fBlur = 0;
+ fGaussianBlur = 0;
+ fontShiftX = fontShiftY = fontAngleZ = fontAngleX = fontAngleY = 0;
+ relativeTo = 2;
+#ifdef _VSMOD
+ // patch m001. Vertical fontspacing
+ mod_verticalSpace = 0;
+ // patch m002. Z-coord
+ mod_z = 0;
+ // patch m003. random text points
+ mod_rand.clear();
+ // patch m004. gradient colors
+ mod_grad.clear();
+ // patch m007. symbol rotating
+ mod_fontOrient = 0;
+#endif
+}
+
+bool STSStyle::operator == (STSStyle& s)
+{
+ return(marginRect == s.marginRect
+ && scrAlignment == s.scrAlignment
+ && borderStyle == s.borderStyle
+ && outlineWidthX == s.outlineWidthX
+ && outlineWidthY == s.outlineWidthY
+ && shadowDepthX == s.shadowDepthX
+ && shadowDepthY == s.shadowDepthY
+ && *((int*)&colors[0]) == *((int*)&s.colors[0])
+ && *((int*)&colors[1]) == *((int*)&s.colors[1])
+ && *((int*)&colors[2]) == *((int*)&s.colors[2])
+ && *((int*)&colors[3]) == *((int*)&s.colors[3])
+ && alpha[0] == s.alpha[0]
+ && alpha[1] == s.alpha[1]
+ && alpha[2] == s.alpha[2]
+ && alpha[3] == s.alpha[3]
+ && fBlur == s.fBlur
+ && fGaussianBlur == s.fGaussianBlur
+ && relativeTo == s.relativeTo
+#ifdef _VSMOD
+ // patch m001. Vertical fontspacing
+ && mod_verticalSpace == s.mod_verticalSpace
+ // patch m002. Z-coord
+ && mod_z == s.mod_z
+ // patch m003. random text points
+ && mod_rand == s.mod_rand
+ // patch m004. gradient colors
+ && mod_grad == s.mod_grad
+ // patch m007. symbol rotating
+ && mod_fontOrient == s.mod_fontOrient
+ // patch m008. distort
+ && mod_distort == s.mod_distort
+ // patch m011. jitter
+ && mod_jitter == s.mod_jitter
+#endif
+ && IsFontStyleEqual(s));
+}
+
+bool STSStyle::IsFontStyleEqual(STSStyle& s)
+{
+ return(
+ charSet == s.charSet
+ && fontName == s.fontName
+ && fontSize == s.fontSize
+ && fontScaleX == s.fontScaleX
+ && fontScaleY == s.fontScaleY
+ && fontSpacing == s.fontSpacing
+ && fontWeight == s.fontWeight
+ && fItalic == s.fItalic
+ && fUnderline == s.fUnderline
+ && fStrikeOut == s.fStrikeOut
+ && fontAngleZ == s.fontAngleZ
+ && fontAngleX == s.fontAngleX
+ && fontAngleY == s.fontAngleY
+ // patch f001. fax fay patch (many instances at line)
+ && fontShiftX == s.fontShiftX
+ && fontShiftY == s.fontShiftY);
+}
+
+#ifdef _VSMOD
+void STSStyle::mod_CopyStyleFrom(STSStyle& s)
+{
+ marginRect = s.marginRect;
+ scrAlignment = s.scrAlignment;
+ borderStyle = s.borderStyle;
+ outlineWidthX = s.outlineWidthX;
+ outlineWidthY = s.outlineWidthY;
+ shadowDepthX = s.shadowDepthX;
+ shadowDepthY = s.shadowDepthY;
+ *((int*)&colors[0]) = *((int*)&s.colors[0]);
+ *((int*)&colors[1]) = *((int*)&s.colors[1]);
+ *((int*)&colors[2]) = *((int*)&s.colors[2]);
+ *((int*)&colors[3]) = *((int*)&s.colors[3]);
+ alpha[0] = s.alpha[0];
+ alpha[1] = s.alpha[1];
+ alpha[2] = s.alpha[2];
+ alpha[3] = s.alpha[3];
+ fBlur = s.fBlur;
+ fGaussianBlur = s.fGaussianBlur;
+ relativeTo = s.relativeTo;
+
+ //patch m001. Vertical fontspacing
+ mod_verticalSpace = s.mod_verticalSpace;
+ //patch m002. Z-coord
+ mod_z = s.mod_z;
+ //patch m003. random text points
+ mod_rand = s.mod_rand;
+ //patch m004. gradient colors
+ mod_grad = s.mod_grad;
+ // patch m007. symbol rotating
+ mod_fontOrient = s.mod_fontOrient;
+ // patch m008. distort
+ mod_distort = s.mod_distort;
+ // patch m011. jitter
+ mod_jitter = s.mod_jitter;
+ // font
+ charSet = s.charSet;
+ fontName = s.fontName;
+ fontSize = s.fontSize;
+ fontScaleX = s.fontScaleX;
+ fontScaleY = s.fontScaleY;
+ fontSpacing = s.fontSpacing;
+ fontWeight = s.fontWeight;
+ fItalic = s.fItalic;
+ fUnderline = s.fUnderline;
+ fStrikeOut = s.fStrikeOut;
+ fontAngleZ = s.fontAngleZ;
+ fontAngleX = s.fontAngleX;
+ fontAngleY = s.fontAngleY;
+ // patch f001. fax fay patch (many instances at line)
+ fontShiftX = s.fontShiftX;
+ fontShiftY = s.fontShiftY;
+}
+
+void STSStyle::operator = (STSStyle& s)
+{
+ mod_CopyStyleFrom(s);
+}
+#endif
+
+void STSStyle::operator = (LOGFONT& lf)
+{
+ charSet = lf.lfCharSet;
+ fontName = lf.lfFaceName;
+ HDC hDC = GetDC(0);
+ fontSize = -MulDiv(lf.lfHeight, 72, GetDeviceCaps(hDC, LOGPIXELSY));
+ ReleaseDC(0, hDC);
+// fontAngleZ = (float)(1.0*lf.lfEscapement/10);
+ fontWeight = lf.lfWeight;
+ fItalic = !!lf.lfItalic;
+ fUnderline = !!lf.lfUnderline;
+ fStrikeOut = !!lf.lfStrikeOut;
+}
+
+LOGFONTA& operator <<= (LOGFONTA& lfa, STSStyle& s)
+{
+ lfa.lfCharSet = s.charSet;
+ strncpy_s(lfa.lfFaceName, LF_FACESIZE, CStringA(s.fontName), _TRUNCATE);
+ HDC hDC = GetDC(0);
+ lfa.lfHeight = -MulDiv((int)(s.fontSize+0.5), GetDeviceCaps(hDC, LOGPIXELSY), 72);
+ ReleaseDC(0, hDC);
+ lfa.lfWeight = s.fontWeight;
+ lfa.lfItalic = s.fItalic?-1:0;
+ lfa.lfUnderline = s.fUnderline?-1:0;
+ lfa.lfStrikeOut = s.fStrikeOut?-1:0;
+ return(lfa);
+}
+
+LOGFONTW& operator <<= (LOGFONTW& lfw, STSStyle& s)
+{
+ lfw.lfCharSet = s.charSet;
+ wcsncpy_s(lfw.lfFaceName, LF_FACESIZE, CStringW(s.fontName), _TRUNCATE);
+ HDC hDC = GetDC(0);
+ lfw.lfHeight = -MulDiv((int)(s.fontSize+0.5), GetDeviceCaps(hDC, LOGPIXELSY), 72);
+ ReleaseDC(0, hDC);
+ lfw.lfWeight = s.fontWeight;
+ lfw.lfItalic = s.fItalic?-1:0;
+ lfw.lfUnderline = s.fUnderline?-1:0;
+ lfw.lfStrikeOut = s.fStrikeOut?-1:0;
+ return(lfw);
+}
+
+CString& operator <<= (CString& style, STSStyle& s)
+{
+ style.Format(_T("%d;%d;%d;%d;%d;%d;%f;%f;%f;%f;0x%06x;0x%06x;0x%06x;0x%06x;0x%02x;0x%02x;0x%02x;0x%02x;%d;%s;%f;%f;%f;%f;%d;%d;%d;%d;%d;%f;%f;%f;%f;%d"),
+ s.marginRect.left, s.marginRect.right, s.marginRect.top, s.marginRect.bottom,
+ s.scrAlignment, s.borderStyle,
+ s.outlineWidthX, s.outlineWidthY, s.shadowDepthX, s.shadowDepthY,
+ s.colors[0], s.colors[1], s.colors[2], s.colors[3],
+ s.alpha[0], s.alpha[1], s.alpha[2], s.alpha[3],
+ s.charSet,
+ s.fontName,s.fontSize,
+ s.fontScaleX, s.fontScaleY,
+ s.fontSpacing,s.fontWeight,
+ (int)s.fItalic, (int)s.fUnderline, (int)s.fStrikeOut, s.fBlur, s.fGaussianBlur,
+ s.fontAngleZ, s.fontAngleX, s.fontAngleY,
+ s.relativeTo);
+
+ return(style);
+}
+
+STSStyle& operator <<= (STSStyle& s, CString& style)
+{
+ s.SetDefault();
+
+ try
+ {
+ CStringW str = TToW(style);
+ if(str.Find(';')>=0)
+ {
+ s.marginRect.left = GetInt(str, ';');
+ s.marginRect.right = GetInt(str, ';');
+ s.marginRect.top = GetInt(str, ';');
+ s.marginRect.bottom = GetInt(str, ';');
+ s.scrAlignment = GetInt(str, ';');
+ s.borderStyle = GetInt(str, ';');
+ s.outlineWidthX = GetFloat(str, ';');
+ s.outlineWidthY = GetFloat(str, ';');
+ s.shadowDepthX = GetFloat(str, ';');
+ s.shadowDepthY = GetFloat(str, ';');
+ for(ptrdiff_t i = 0; i < 4; i++) s.colors[i] = (COLORREF)GetInt(str, ';');
+ for(ptrdiff_t i = 0; i < 4; i++) s.alpha[i] = GetInt(str, ';');
+ s.charSet = GetInt(str, ';');
+ s.fontName = WToT(GetStr(str, ';'));
+ s.fontSize = GetFloat(str, ';');
+ s.fontScaleX = GetFloat(str, ';');
+ s.fontScaleY = GetFloat(str, ';');
+ s.fontSpacing = GetFloat(str, ';');
+ s.fontWeight = GetInt(str, ';');
+ s.fItalic = !!GetInt(str, ';');
+ s.fUnderline = !!GetInt(str, ';');
+ s.fStrikeOut = !!GetInt(str, ';');
+ s.fBlur = GetInt(str, ';');
+ s.fGaussianBlur = GetFloat(str, ';');
+ s.fontAngleZ = GetFloat(str, ';');
+ s.fontAngleX = GetFloat(str, ';');
+ s.fontAngleY = GetFloat(str, ';');
+ s.relativeTo = GetInt(str, ';');
+ }
+ }
+ catch(...)
+ {
+ s.SetDefault();
+ }
+
+ return(s);
+}
+
+static bool OpenRealText(CTextFile* file, CSimpleTextSubtitle& ret, int CharSet)
+{
+ wstring szFile;
+
+ CStringW buff;
+ while(file->ReadString(buff))
+ {
+ buff.Trim();
+ if(buff.IsEmpty()) continue;
+
+ szFile += CStringW(_T("\n")) + buff.GetBuffer();
+ }
+
+ CRealTextParser RealTextParser;
+ if (!RealTextParser.ParseRealText(szFile))
+ return false;
+
+ CRealTextParser::Subtitles crRealText = RealTextParser.GetParsedSubtitles();
+
+ for (map<pair<int, int>, wstring>::const_iterator i = crRealText.m_mapLines.begin();
+ i != crRealText.m_mapLines.end();
+ ++i)
+ {
+ ret.Add(
+ SubRipper2SSA(i->second.c_str(), CharSet),
+ file->IsUnicode(),
+ i->first.first,
+ i->first.second);
+ }
+
+// std::wofstream wofsOut(L"c:/zzz.srt");
+// RealTextParser.OutputSRT(wofsOut);
+
+ return(ret.GetCount() > 0);
+}
+
+#ifdef _VSMOD // patch m003. random text points
+bool MOD_RANDOM::operator == (MOD_RANDOM& mr)
+{
+ return (X == mr.X
+ && Y == mr.Y
+ && Z == mr.X
+ && Seed == mr.Seed);
+}
+
+void MOD_RANDOM::clear()
+{
+ X = 0;
+ Y = 0;
+ Z = 0;
+ Seed = 0;
+}
+#endif
+
+#ifdef _VSMOD // patch m004. gradient colors
+#include <png.h> // patch m010. png background
+
+MOD_PNGIMAGE::MOD_PNGIMAGE()
+{
+ width = 0;
+ height = 0;
+
+ xoffset = 0;
+ yoffset = 0;
+
+ pointer = NULL;
+
+ // rasterizer
+ alpha = 0xFF;
+}
+
+// read embedded graphics
+void png_default_read_edata(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ png_size_t check;
+
+ if (png_ptr->io_ptr == NULL)
+ return;
+
+ BYTE* eldata = (BYTE*)png_ptr->io_ptr;
+
+ // read from memory
+ memcpy(data,eldata,length);
+ eldata += length;
+ png_ptr->io_ptr = (png_voidp)eldata;
+}
+
+bool MOD_PNGIMAGE::operator == (MOD_PNGIMAGE& png)
+{
+ return(filename == png.filename
+ && xoffset == png.xoffset
+ && yoffset == png.yoffset);
+}
+
+bool MOD_PNGIMAGE::processData(png_structp png_ptr)
+{
+ png_uint_32 color_type;
+ png_uint_32 bit_depth;
+
+ png_infop info_ptr;
+ int number_of_passes;
+
+ /* initialize stuff */
+ info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr) return false; // png_create_info_struct failed
+
+ if (setjmp(png_jmpbuf(png_ptr))) return false; // Error during init_io
+
+ png_set_sig_bytes(png_ptr, 8);
+
+ png_read_info(png_ptr, info_ptr);
+
+ width = info_ptr->width;
+ height = info_ptr->height;
+ color_type = info_ptr->color_type;
+ bit_depth = info_ptr->bit_depth;
+
+ // palette
+ if (color_type==PNG_COLOR_TYPE_PALETTE)
+ png_set_palette_to_rgb(png_ptr);
+
+ // expand to 8 bits
+ if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
+ png_set_expand_gray_1_2_4_to_8(png_ptr);
+
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
+ png_set_tRNS_to_alpha(png_ptr);
+
+ // Strip 16 bit depth files to 8 bit depth
+ if (bit_depth == 16)
+ png_set_strip_16(png_ptr);
+
+ // ARGB -> RGBA
+// if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
+// png_set_swap_alpha(png_ptr);
+
+ // grayscale -> RGB
+ if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+ png_set_gray_to_rgb(png_ptr);
+
+ number_of_passes = png_set_interlace_handling(png_ptr);
+ png_read_update_info(png_ptr, info_ptr);
+
+ /* read file */
+ if (setjmp(png_jmpbuf(png_ptr))) return false; // Error during read_image
+
+ bpp = info_ptr->rowbytes / width;
+ pointer = (png_bytep*) malloc(sizeof(png_bytep) * height);
+ for (int y=0; y<height; y++)
+ pointer[y] = (png_byte*) malloc(info_ptr->rowbytes);
+
+ png_read_image(png_ptr, pointer);
+ return true;
+}
+
+bool MOD_PNGIMAGE::initImage(CString m_fn)
+{
+ if((m_fn==filename)&&(pointer!=NULL)) return true; // already loaded
+
+ char header[8]; // 8 is the maximum size that can be check
+ png_structp png_ptr;
+
+ const wchar_t* wfn = m_fn.GetString();
+ int len = m_fn.GetLength();
+ char* fn = new char[len+1];
+ WideCharToMultiByte(CP_ACP,NULL,wfn,wcslen(wfn),fn,len,NULL,NULL);
+ fn[len]=0;
+ filename = m_fn;
+
+ FILE *fp = fopen(fn, "rb");
+ if (!fp) return false; // File could not be opened for reading
+ fread(header, 1, 8, fp);
+ if (png_sig_cmp((png_bytep)header, 0, 8)) return false; // File is not recognized as a PNG file
+
+ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!png_ptr) return false; // png_create_read_struct failed
+
+ png_init_io(png_ptr, fp);
+ return processData(png_ptr);
+ fclose(fp);
+}
+
+bool MOD_PNGIMAGE::initImage(BYTE* data, CString m_fn)
+{
+ if((m_fn==filename)&&(pointer!=NULL)) return true; // already loaded
+ if(data == NULL) return false; // not loaded
+
+ char header[8]; // 8 is the maximum size that can be check
+ png_structp png_ptr;
+
+ filename = m_fn;
+
+ memcpy(header,data,8);
+ if (png_sig_cmp((png_bytep)header, 0, 8)) return false; // File is not recognized as a PNG file
+
+ data += 8; // don't forget modify pointer
+ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!png_ptr) return false; // png_create_read_struct failed
+
+ png_set_read_fn(png_ptr, (png_voidp)data, &png_default_read_edata);
+ return processData(png_ptr);
+}
+
+void MOD_PNGIMAGE::freeImage()
+{
+ if(pointer!=NULL) delete [] pointer;
+}
+
+MOD_GRADIENT::MOD_GRADIENT()
+{
+ clear();
+}
+
+bool MOD_GRADIENT::operator == (MOD_GRADIENT& mg)
+{
+ return (color[0][0] == mg.color[0][0] // T.T
+ && color[1][0] == mg.color[1][0]
+ && color[2][0] == mg.color[2][0]
+ && color[3][0] == mg.color[3][0]
+ && color[0][1] == mg.color[0][1]
+ && color[1][1] == mg.color[1][1]
+ && color[2][1] == mg.color[2][1]
+ && color[3][1] == mg.color[3][1]
+ && color[0][2] == mg.color[0][2]
+ && color[1][2] == mg.color[1][2]
+ && color[2][2] == mg.color[2][2]
+ && color[3][2] == mg.color[3][2]
+ && color[0][3] == mg.color[0][3]
+ && color[1][3] == mg.color[1][3]
+ && color[2][3] == mg.color[2][3]
+ && color[3][3] == mg.color[3][3]
+ && alpha[0][0] == mg.alpha[0][0]
+ && alpha[1][0] == mg.alpha[1][0]
+ && alpha[2][0] == mg.alpha[2][0]
+ && alpha[3][0] == mg.alpha[3][0]
+ && alpha[0][1] == mg.alpha[0][1]
+ && alpha[1][1] == mg.alpha[1][1]
+ && alpha[2][1] == mg.alpha[2][1]
+ && alpha[3][1] == mg.alpha[3][1]
+ && alpha[0][2] == mg.alpha[0][2]
+ && alpha[1][2] == mg.alpha[1][2]
+ && alpha[2][2] == mg.alpha[2][2]
+ && alpha[3][2] == mg.alpha[3][2]
+ && alpha[0][3] == mg.alpha[0][3]
+ && alpha[1][3] == mg.alpha[1][3]
+ && alpha[2][3] == mg.alpha[2][3]
+ && alpha[3][3] == mg.alpha[3][3]
+ && mode[0] == mg.mode[0]
+ && mode[1] == mg.mode[1]
+ && mode[2] == mg.mode[2]
+ && mode[3] == mg.mode[3]
+ && b_images[0] == mg.b_images[0]
+ && b_images[1] == mg.b_images[1]
+ && b_images[2] == mg.b_images[2]
+ && b_images[3] == mg.b_images[3]);
+}
+
+void MOD_GRADIENT::clear()
+{
+ memset(&color,0,sizeof(color));
+ memset(&colors,0,sizeof(colors));
+ memset(&alpha,0,sizeof(alpha));
+ memset(&alphas,0,sizeof(alphas));
+ memset(&mode,0,sizeof(mode));
+ colors[0] = 0x00ffffff;
+ colors[1] = 0x0000ffff;
+ alphas[3] = 0x80;
+ width = 0;
+ height = 0;
+ xoffset = 0;
+ yoffset = 0;
+ subpixx = 0;
+ subpixy = 0;
+ fadalpha = 0xFF;
+}
+
+#include <math.h>
+DWORD MOD_GRADIENT::getmixcolor(int tx, int ty, int i) // too slow T.T
+{
+ DWORD colorb = 0;
+ tx += xoffset;
+ // gradient
+ if(mode[i]==1)
+ {
+ double x = (double)tx/(double)width;
+ double y = (double)ty/(double)height;
+ for(int j=0;j<3;j++)
+ {
+ colorb |= ((DWORD)(((color[i][0]>>(8*j))&0xff)*(1-x)*y +
+ ((color[i][1]>>(8*j))&0xff)*x*y+
+ ((color[i][2]>>(8*j))&0xff)*(1-y)*(1-x)+
+ ((color[i][3]>>(8*j))&0xff)*x*(1-y))&0xff)<<(8*j);
+ }
+ DWORD al = (DWORD)((alpha[i][0]*(1-x)*y) +
+ (alpha[i][1]*x*y)+
+ (alpha[i][2]*(1-y)*(1-x))+
+ (alpha[i][3]*x*(1-y)))&0xff;
+ colorb |= (((0xff-al)*(0xff-fadalpha))&0xff00)<<(16);
+ return colorb;
+ }
+ // png background
+ if(mode[i]==2)
+ {
+ // unwarp
+ tx += b_images[i].xoffset;
+ ty += b_images[i].yoffset;
+ while(tx>b_images[i].width-1) tx-=b_images[i].width;
+ while(ty>b_images[i].height-1) ty-=b_images[i].height;
+ while(tx<0) tx+=b_images[i].width;
+ while(ty<0) ty+=b_images[i].height;
+ // now tx and ty are valid array indexes
+ // rows are inverted last,...,n,...,1,0
+ bool nlastpixx = (tx>0);
+ bool nlastpixy = (ty<b_images[i].height-1);
+ BYTE* dst11 = b_images[i].pointer[b_images[i].height-1-ty]+tx*b_images[i].bpp;
+ BYTE* dst12 = (nlastpixx) ? b_images[i].pointer[b_images[i].height-1-ty]+(tx-1)*b_images[i].bpp : NULL;
+ BYTE* dst21 = (nlastpixy) ? b_images[i].pointer[b_images[i].height-ty-2]+tx*b_images[i].bpp : NULL;
+ BYTE* dst22 = (nlastpixx&&nlastpixy) ? b_images[i].pointer[b_images[i].height-ty-2]+(tx-1)*b_images[i].bpp : NULL;
+ BYTE r = dst11[0];
+ BYTE g = dst11[1];
+ BYTE b = dst11[2];
+ BYTE a = (b_images[i].bpp==4) ? dst11[3] : 0xFF;
+ // subpixel positioning
+ if(nlastpixx&&!nlastpixy) // last row
+ {
+ r = (r*(8-subpixx)+dst12[0]*subpixx)>>3;
+ g = (g*(8-subpixx)+dst12[1]*subpixx)>>3;
+ b = (b*(8-subpixx)+dst12[2]*subpixx)>>3;
+ a = (b_images[i].bpp==4) ? (a*(8-subpixx)+dst12[3]*subpixx)>>3 : 0xFF;
+ }
+ else if(nlastpixy&&!nlastpixx) // last col
+ {
+ r = (r*(subpixy)+dst21[0]*(8-subpixy))>>3;
+ g = (g*(subpixy)+dst21[1]*(8-subpixy))>>3;
+ b = (b*(subpixy)+dst21[2]*(8-subpixy))>>3;
+ a = (b_images[i].bpp==4) ? (a*(subpixy)+dst21[3]*(8-subpixy))>>3 : 0xFF;
+ }
+ else if(nlastpixy&&nlastpixx)
+ {
+ // T.T
+ r = (((dst21[0]*(8-subpixx)+dst22[0]*subpixx)>>3)*(subpixy)+((r*(8-subpixx)+dst12[0]*subpixx)>>3)*(8-subpixy))>>3;
+ g = (((dst21[1]*(8-subpixx)+dst22[1]*subpixx)>>3)*(subpixy)+((g*(8-subpixx)+dst12[1]*subpixx)>>3)*(8-subpixy))>>3;
+ b = (((dst21[2]*(8-subpixx)+dst22[2]*subpixx)>>3)*(subpixy)+((b*(8-subpixx)+dst12[2]*subpixx)>>3)*(8-subpixy))>>3;
+ a = (b_images[i].bpp==4) ? (((dst21[3]*(8-subpixx)+dst22[3]*subpixx)>>3)*(subpixy)+((a*(8-subpixx)+dst12[3]*subpixx)>>3)*(8-subpixy))>>3 : 0xFF;
+ }
+ // alpha fix
+ DWORD al = (a*b_images[i].alpha*(0xff-fadalpha));
+ colorb = (al & 0xFF0000)<<8 | r<<16 | g<<8 | b;
+
+ return colorb;
+ }
+ // usual color
+// if(mode[i]==0)
+ return (colors[i]|alphas[i]<<24);
+}
+#endif
+
+#ifdef _VSMOD // patch m008. distort
+MOD_DISTORT::MOD_DISTORT()
+{
+ enabled = false;
+ pointsx[0] = 1;
+ pointsy[0] = 0;
+ pointsx[1] = 1;
+ pointsy[1] = 1;
+ pointsx[2] = 0;
+ pointsy[2] = 1;
+}
+
+bool MOD_DISTORT::operator == (MOD_DISTORT& md)
+{
+ return(enabled == md.enabled
+ && pointsx[0] == md.pointsx[0]
+ && pointsx[1] == md.pointsx[1]
+ && pointsx[2] == md.pointsx[2]
+ && pointsy[0] == md.pointsy[0]
+ && pointsy[1] == md.pointsy[1]
+ && pointsy[2] == md.pointsy[2]);
+}
+#endif
+
+#ifdef _VSMOD // patch m011. jitter
+MOD_JITTER::MOD_JITTER()
+{
+ seed = 0;
+ offset = CRect(0,0,0,0);
+ period = 1;
+ enabled = false;
+}
+
+bool MOD_JITTER::operator == (MOD_JITTER& mj)
+{
+ return(seed == mj.seed
+ && offset == mj.offset
+ && period == mj.period);
+}
+
+CPoint MOD_JITTER::getOffset(REFERENCE_TIME rt)
+{
+ if (!enabled) return CPoint(0,0);
+ if(period==0) period = 1;
+ int rseed = (seed + rt / period)*100;
+
+ srand(rseed);
+ rand();
+ int xoffset = rand();
+ xoffset = xoffset%(offset.left+offset.right) - offset.left;
+
+ //srand(rseed+1);
+ int yoffset = rand();
+ yoffset = yoffset%(offset.bottom+offset.top) - offset.top;
+
+ return CPoint((int)xoffset, (int)yoffset);
+}
+#endif \ No newline at end of file
diff --git a/src/Subtitles/STS.h b/src/Subtitles/STS.h
new file mode 100644
index 000000000..992ef6345
--- /dev/null
+++ b/src/Subtitles/STS.h
@@ -0,0 +1,358 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <atlcoll.h>
+#include <wxutil.h>
+#include "TextFile.h"
+#include "GFN.h"
+
+typedef enum {TIME, FRAME} tmode; // the meaning of STSEntry::start/end
+
+#ifdef _VSMOD // patch m003. random text points
+class MOD_RANDOM
+{
+public:
+ int X;
+ int Y;
+ int Z;
+ int Seed; // random seed
+
+ //MOD_RANDOM();
+ bool operator == (MOD_RANDOM& mr);
+
+ void clear();
+};
+#endif
+
+#ifdef _VSMOD // patch m010. png background
+#include <png.h>
+class MOD_PNGIMAGE
+{
+public:
+ CString filename;
+ int width;
+ int height;
+
+ int xoffset;
+ int yoffset;
+
+ int bpp;
+
+ BYTE alpha;
+
+ png_byte color_type;
+ png_byte bit_depth;
+
+ png_bytep* pointer;
+
+ MOD_PNGIMAGE();
+
+ bool operator == (MOD_PNGIMAGE& png);
+ /**/
+ bool processData(png_structp png_ptr);
+ bool initImage(CString m_fn);
+ bool initImage(BYTE* data, CString m_fn);
+ void freeImage();
+};
+#endif
+
+#ifdef _VSMOD // patch m004. gradient colors
+class MOD_GRADIENT
+{
+public:
+ COLORREF colors[4]; // c
+ COLORREF alphas[4]; // a
+ COLORREF color[4][4]; // vc
+ BYTE alpha[4][4]; // va
+ int mode[4];
+
+ // for renderer
+ int height;
+ int width;
+ int xoffset;
+ int yoffset;
+
+ BYTE subpixx;
+ BYTE subpixy;
+ BYTE fadalpha;
+
+ BYTE img_alpha;
+
+ // for background image
+ MOD_PNGIMAGE b_images[4];
+
+ MOD_GRADIENT();
+ bool operator == (MOD_GRADIENT& mg);
+
+ void clear();
+ DWORD getmixcolor(int tx, int ty, int i);
+};
+#endif
+
+#ifdef _VSMOD // patch m008. distort
+class MOD_DISTORT
+{
+public:
+ double pointsx[3]; //P1-P3
+ double pointsy[3]; //P1-P3
+ bool enabled;
+
+ MOD_DISTORT();
+
+ bool operator == (MOD_DISTORT& md);
+};
+#endif
+
+#ifdef _VSMOD // patch m011. jitter
+class MOD_JITTER
+{
+public:
+ CRect offset; // left,top,right,left
+ int seed;
+ int period; // ms
+ bool enabled;
+
+ MOD_JITTER();
+
+ bool operator == (MOD_JITTER& mj);
+
+ CPoint getOffset(REFERENCE_TIME rt);
+};
+#endif
+
+class STSStyle
+{
+public:
+ CRect marginRect; // measured from the sides
+ int scrAlignment; // 1 - 9: as on the numpad, 0: default
+ int borderStyle; // 0: outline, 1: opaque box
+ double outlineWidthX, outlineWidthY;
+ double shadowDepthX, shadowDepthY;
+ COLORREF colors[4]; // usually: {primary, secondary, outline/background, shadow}
+ BYTE alpha[4];
+ int charSet;
+ CString fontName;
+ double fontSize; // height
+ double fontScaleX, fontScaleY; // percent
+ double fontSpacing; // +/- pixels
+ int fontWeight;
+ bool fItalic;
+ bool fUnderline;
+ bool fStrikeOut;
+ int fBlur;
+ double fGaussianBlur;
+ double fontAngleZ, fontAngleX, fontAngleY;
+ double fontShiftX, fontShiftY;
+ int relativeTo; // 0: window, 1: video, 2: undefined (~window)
+#ifdef _VSMOD
+ // patch m001. Vertical fontspacing
+ double mod_verticalSpace;
+ // patch m002. Z-coord
+ double mod_z;
+ // patch m003. random text points
+ MOD_RANDOM mod_rand;
+ // patch m004. gradient colors
+ MOD_GRADIENT mod_grad;
+ // patch m007. symbol rotating
+ int mod_fontOrient;
+ // patch m008. distort
+ MOD_DISTORT mod_distort;
+ // patch m011. jitter
+ MOD_JITTER mod_jitter;
+#endif
+
+ STSStyle();
+#ifdef _VSMOD
+ STSStyle(STSStyle& s);
+#endif
+
+ void SetDefault();
+
+ bool operator == (STSStyle& s);
+ bool IsFontStyleEqual(STSStyle& s);
+#ifdef _VSMOD
+ void mod_CopyStyleFrom(STSStyle& s);
+
+ void operator = (STSStyle& s);
+#endif
+ void operator = (LOGFONT& lf);
+
+ friend LOGFONTA& operator <<= (LOGFONTA& lfa, STSStyle& s);
+ friend LOGFONTW& operator <<= (LOGFONTW& lfw, STSStyle& s);
+
+ friend CString& operator <<= (CString& style, STSStyle& s);
+ friend STSStyle& operator <<= (STSStyle& s, CString& style);
+};
+
+class CSTSStyleMap : public CAtlMap<CString, STSStyle*, CStringElementTraits<CString> >
+{
+public:
+ CSTSStyleMap() {}
+ virtual ~CSTSStyleMap() {Free();}
+ void Free();
+};
+
+typedef struct
+{
+ CStringW str;
+ bool fUnicode;
+ CString style, actor, effect;
+ CRect marginRect;
+ int layer;
+ int start, end;
+ int readorder;
+#ifdef _VSMOD // patch m009. png graphics
+ int mod_scripttype;
+#endif
+} STSEntry;
+
+class STSSegment
+{
+public:
+ int start, end;
+ CAtlArray<int> subs;
+
+ STSSegment() {};
+ STSSegment(int s, int e) {start = s; end = e;}
+ STSSegment(const STSSegment& stss) {*this = stss;}
+ void operator = (const STSSegment& stss) {start = stss.start; end = stss.end; subs.Copy(stss.subs);}
+};
+
+class CSimpleTextSubtitle : public CAtlArray<STSEntry>
+{
+ friend class CSubtitleEditorDlg;
+
+protected:
+ CAtlArray<STSSegment> m_segments;
+ virtual void OnChanged() {}
+
+public:
+ CString m_name;
+ LCID m_lcid;
+ exttype m_exttype;
+ tmode m_mode;
+ CTextFile::enc m_encoding;
+ CString m_path;
+
+ CSize m_dstScreenSize;
+ int m_defaultWrapStyle;
+ int m_collisions;
+ bool m_fScaledBAS;
+
+ bool m_fUsingAutoGeneratedDefaultStyle;
+
+ CSTSStyleMap m_styles;
+
+#ifdef _VSMOD
+ CAtlArray<MOD_PNGIMAGE> mod_images;
+
+ // index array, for fast speed
+ DWORD ind_size; // size of array
+ DWORD* ind_time; // time array
+ DWORD* ind_pos; // segment indexes array (start)
+#endif
+
+ enum EPARCompensationType
+ {
+ EPCTDisabled = 0,
+ EPCTDownscale = 1,
+ EPCTUpscale = 2,
+ EPCTAccurateSize = 3
+ };
+
+ EPARCompensationType m_ePARCompensationType;
+ double m_dPARCompensation;
+
+public:
+ CSimpleTextSubtitle();
+ virtual ~CSimpleTextSubtitle();
+
+ virtual void Copy(CSimpleTextSubtitle& sts);
+ virtual void Empty();
+
+ void Sort(bool fRestoreReadorder = false);
+ void CreateSegments();
+
+ void Append(CSimpleTextSubtitle& sts, int timeoff = -1);
+
+ bool Open(CString fn, int CharSet, CString name = _T(""));
+ bool Open(CTextFile* f, int CharSet, CString name);
+ bool Open(BYTE* data, int len, int CharSet, CString name);
+ bool SaveAs(CString fn, exttype et, double fps = -1, CTextFile::enc = CTextFile::ASCII);
+
+#ifdef _VSMOD // load embedded images
+ bool LoadUUEFile(CTextFile* file, CString m_fn);
+ bool LoadEfile(CString& img, CString m_fn);
+
+ void MakeIndex(int SizeOfSegment);
+#endif
+ void Add(CStringW str, bool fUnicode, int start, int end, CString style = _T("Default"), CString actor = _T(""), CString effect = _T(""), CRect marginRect = CRect(0,0,0,0), int layer = 0, int readorder = -1);
+ STSStyle* CreateDefaultStyle(int CharSet);
+ void ChangeUnknownStylesToDefault();
+ void AddStyle(CString name, STSStyle* style); // style will be stored and freed in Empty() later
+ bool CopyStyles(const CSTSStyleMap& styles, bool fAppend = false);
+
+ bool SetDefaultStyle(STSStyle& s);
+ bool GetDefaultStyle(STSStyle& s);
+
+ void ConvertToTimeBased(double fps);
+ void ConvertToFrameBased(double fps);
+
+ int TranslateStart(int i, double fps);
+ int TranslateEnd(int i, double fps);
+ int SearchSub(int t, double fps);
+
+ int TranslateSegmentStart(int i, double fps);
+ int TranslateSegmentEnd(int i, double fps);
+ const STSSegment* SearchSubs(int t, double fps, /*[out]*/ int* iSegment = NULL, int* nSegments = NULL);
+ const STSSegment* GetSegment(int iSegment) {return iSegment >= 0 && iSegment < (int)m_segments.GetCount() ? &m_segments[iSegment] : NULL;}
+
+ STSStyle* GetStyle(int i);
+ bool GetStyle(int i, STSStyle& stss);
+ int GetCharSet(int i);
+ bool IsEntryUnicode(int i);
+ void ConvertUnicode(int i, bool fUnicode);
+
+ CStringA GetStrA(int i, bool fSSA = false);
+ CStringW GetStrW(int i, bool fSSA = false);
+ CStringW GetStrWA(int i, bool fSSA = false);
+
+#ifdef UNICODE
+#define GetStr GetStrW
+#else
+#define GetStr GetStrA
+#endif
+
+ void SetStr(int i, CStringA str, bool fUnicode /* ignored */);
+ void SetStr(int i, CStringW str, bool fUnicode);
+};
+
+extern BYTE CharSetList[];
+extern TCHAR* CharSetNames[];
+extern int CharSetLen;
+
+class CHtmlColorMap : public CAtlMap<CString, DWORD, CStringElementTraits<CString> > {public: CHtmlColorMap();};
+extern CHtmlColorMap g_colors;
+
+
+
diff --git a/src/Subtitles/SeparableFilter.h b/src/Subtitles/SeparableFilter.h
new file mode 100644
index 000000000..7661fb389
--- /dev/null
+++ b/src/Subtitles/SeparableFilter.h
@@ -0,0 +1,121 @@
+/*
+ Copyright 2007 Niels Martin Hansen
+
+ 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 of the License, 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 this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ Contact:
+ E-mail: <jiifurusu@gmail.com>
+ IRC: jfs in #aegisub on irc.rizon.net
+
+ */
+
+#pragma once
+
+#ifdef _OPENMP
+#include <omp.h>
+#endif
+#include <math.h>
+
+
+// Filter an image in horizontal direction with a one-dimensional filter
+// PixelWidth is the distance in bytes between pixels
+template<ptrdiff_t PixelDist>
+void SeparableFilterX(unsigned char *src, unsigned char *dst, int width, int height, ptrdiff_t stride, int *kernel, int kernel_size, int divisor)
+{
+#pragma omp parallel for
+ for (int y = 0; y < height; y++) {
+ unsigned char *in = src + y*stride;
+ unsigned char *out = dst + y*stride;
+ for (int x = 0; x < width; x++) {
+ int accum = 0;
+ for (int k = 0; k < kernel_size; k++) {
+ int xofs = k - kernel_size/2;
+ if (x+xofs < 0) xofs += width;
+ if (x+xofs >= width) xofs -= width;
+ accum += (int)(in[xofs*PixelDist] * kernel[k]);
+ }
+ accum /= divisor;
+ if (accum > 255) accum = 255;
+ if (accum < 0) accum = 0;
+ *out = (unsigned char)accum;
+ in+=PixelDist;
+ out+=PixelDist;
+ }
+ }
+}
+
+
+// Filter an image in vertical direction with a one-dimensional filter
+// This one templated with PixelWidth since the channel interlacing is horizontal only,
+// filtering once vertically will automatically catch all channels.
+// (Width must be multiplied by pixel width for that to happen though.)
+template<ptrdiff_t PixelDist>
+void SeparableFilterY(unsigned char *src, unsigned char *dst, int width, int height, ptrdiff_t stride, int *kernel, int kernel_size, int divisor)
+{
+ width *= PixelDist;
+#pragma omp parallel for
+ for (int x = 0; x < width; x+=PixelDist) {
+ unsigned char *in = src + x;
+ unsigned char *out = dst + x;
+ for (int y = 0; y < height; y++) {
+ int accum = 0;
+ for (int k = 0; k < kernel_size; k++) {
+ int yofs = k - kernel_size/2;
+ if (y+yofs < 0) yofs += height;
+ if (y+yofs >= height) yofs -= height;
+ accum += (int)(in[yofs*stride] * kernel[k]);
+ }
+ accum /= divisor;
+ if (accum > 255) accum = 255;
+ if (accum < 0) accum = 0;
+ *out = (unsigned char)accum;
+ in += stride;
+ out += stride;
+ }
+ }
+}
+
+
+static inline double NormalDist(double sigma, double x)
+{
+ if (sigma <= 0 && x == 0) return 1;
+ else if (sigma <= 0) return 0;
+ else return exp(-(x*x)/(2*sigma*sigma)) / (sigma * sqrt(2*3.1415926535));
+}
+
+
+struct GaussianKernel {
+ int *kernel;
+ int width;
+ int divisor;
+ inline GaussianKernel(double sigma)
+ {
+ width = (int)(sigma*3 + 0.5) | 1; // binary-or with 1 to make sure the number is odd
+ if (width < 3) width = 3;
+ kernel = DNew int[width];
+ kernel[width/2] = (int)(NormalDist(sigma, 0) * 255);
+ divisor = kernel[width/2];
+ for (int x = width/2-1; x >= 0; x--) {
+ int val = (int)(NormalDist(sigma, width/2-x) * 255 + 0.5);
+ divisor += val*2;
+ kernel[x] = val;
+ kernel[width - x - 1] = val;
+ }
+ }
+ inline ~GaussianKernel()
+ {
+ delete[] kernel;
+ }
+};
diff --git a/src/Subtitles/SubtitleInputPin.cpp b/src/Subtitles/SubtitleInputPin.cpp
new file mode 100644
index 000000000..fae80d1e2
--- /dev/null
+++ b/src/Subtitles/SubtitleInputPin.cpp
@@ -0,0 +1,416 @@
+/*
+ * 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 "SubtitleInputPin.h"
+#include "VobSubFile.h"
+#include "RTS.h"
+#include "SSF.h"
+#include "RenderedHdmvSubtitle.h"
+
+#include <initguid.h>
+#include <uuids.h>
+#include <moreuuids.h>
+
+// our first format id
+#define __GAB1__ "GAB1"
+
+// our tags for __GAB1__ (ushort) + size (ushort)
+
+// "lang" + '0'
+#define __GAB1_LANGUAGE__ 0
+// (int)start+(int)stop+(char*)line+'0'
+#define __GAB1_ENTRY__ 1
+// L"lang" + '0'
+#define __GAB1_LANGUAGE_UNICODE__ 2
+// (int)start+(int)stop+(WCHAR*)line+'0'
+#define __GAB1_ENTRY_UNICODE__ 3
+
+// same as __GAB1__, but the size is (uint) and only __GAB1_LANGUAGE_UNICODE__ is valid
+#define __GAB2__ "GAB2"
+
+// (BYTE*)
+#define __GAB1_RAWTEXTSUBTITLE__ 4
+
+CSubtitleInputPin::CSubtitleInputPin(CBaseFilter* pFilter, CCritSec* pLock, CCritSec* pSubLock, HRESULT* phr)
+ : CBaseInputPin(NAME("CSubtitleInputPin"), pFilter, pLock, phr, L"Input")
+ , m_pSubLock(pSubLock)
+{
+ m_bCanReconnectWhenActive = TRUE;
+}
+
+HRESULT CSubtitleInputPin::CheckMediaType(const CMediaType* pmt)
+{
+ return pmt->majortype == MEDIATYPE_Text && (pmt->subtype == MEDIASUBTYPE_NULL || pmt->subtype == FOURCCMap((DWORD)0))
+ || pmt->majortype == MEDIATYPE_Subtitle && pmt->subtype == MEDIASUBTYPE_UTF8
+ || pmt->majortype == MEDIATYPE_Subtitle && (pmt->subtype == MEDIASUBTYPE_SSA || pmt->subtype == MEDIASUBTYPE_ASS || pmt->subtype == MEDIASUBTYPE_ASS2)
+ || pmt->majortype == MEDIATYPE_Subtitle && pmt->subtype == MEDIASUBTYPE_SSF
+ || pmt->majortype == MEDIATYPE_Subtitle && (pmt->subtype == MEDIASUBTYPE_VOBSUB)
+ || IsHdmvSub(pmt)
+ ? S_OK
+ : E_FAIL;
+}
+
+HRESULT CSubtitleInputPin::CompleteConnect(IPin* pReceivePin)
+{
+ if(m_mt.majortype == MEDIATYPE_Text)
+ {
+ if(!(m_pSubStream = DNew CRenderedTextSubtitle(m_pSubLock))) return E_FAIL;
+ CRenderedTextSubtitle* pRTS = (CRenderedTextSubtitle*)(ISubStream*)m_pSubStream;
+ pRTS->m_name = CString(GetPinName(pReceivePin)) + _T(" (embeded)");
+ pRTS->m_dstScreenSize = CSize(384, 288);
+ pRTS->CreateDefaultStyle(DEFAULT_CHARSET);
+ }
+ else if(m_mt.majortype == MEDIATYPE_Subtitle)
+ {
+ SUBTITLEINFO* psi = (SUBTITLEINFO*)m_mt.pbFormat;
+ DWORD dwOffset = 0;
+ CString name;
+ LCID lcid = 0;
+
+ if (psi != NULL)
+ {
+ dwOffset = psi->dwOffset;
+
+ name = ISO6392ToLanguage(psi->IsoLang);
+ lcid = ISO6392ToLcid(psi->IsoLang);
+ if(name.IsEmpty()) name = _T("Unknown");
+ if(wcslen(psi->TrackName) > 0) name += _T(" (") + CString(psi->TrackName) + _T(")");
+ }
+
+ if(m_mt.subtype == MEDIASUBTYPE_UTF8
+ /*|| m_mt.subtype == MEDIASUBTYPE_USF*/
+ || m_mt.subtype == MEDIASUBTYPE_SSA
+ || m_mt.subtype == MEDIASUBTYPE_ASS
+ || m_mt.subtype == MEDIASUBTYPE_ASS2)
+ {
+ if(!(m_pSubStream = DNew CRenderedTextSubtitle(m_pSubLock))) return E_FAIL;
+ CRenderedTextSubtitle* pRTS = (CRenderedTextSubtitle*)(ISubStream*)m_pSubStream;
+ pRTS->m_name = name;
+ pRTS->m_lcid = lcid;
+ pRTS->m_dstScreenSize = CSize(384, 288);
+ pRTS->CreateDefaultStyle(DEFAULT_CHARSET);
+
+ if(dwOffset > 0 && m_mt.cbFormat - dwOffset > 0)
+ {
+ CMediaType mt = m_mt;
+ if(mt.pbFormat[dwOffset+0] != 0xef
+ && mt.pbFormat[dwOffset+1] != 0xbb
+ && mt.pbFormat[dwOffset+2] != 0xfb)
+ {
+ dwOffset -= 3;
+ mt.pbFormat[dwOffset+0] = 0xef;
+ mt.pbFormat[dwOffset+1] = 0xbb;
+ mt.pbFormat[dwOffset+2] = 0xbf;
+ }
+
+ pRTS->Open(mt.pbFormat + dwOffset, mt.cbFormat - dwOffset, DEFAULT_CHARSET, pRTS->m_name);
+ }
+
+ }
+ else if(m_mt.subtype == MEDIASUBTYPE_SSF)
+ {
+ if(!(m_pSubStream = DNew ssf::CRenderer(m_pSubLock))) return E_FAIL;
+ ssf::CRenderer* pSSF = (ssf::CRenderer*)(ISubStream*)m_pSubStream;
+ pSSF->Open(ssf::MemoryInputStream(m_mt.pbFormat + dwOffset, m_mt.cbFormat - dwOffset, false, false), name);
+ }
+ else if(m_mt.subtype == MEDIASUBTYPE_VOBSUB)
+ {
+ if(!(m_pSubStream = DNew CVobSubStream(m_pSubLock))) return E_FAIL;
+ CVobSubStream* pVSS = (CVobSubStream*)(ISubStream*)m_pSubStream;
+ pVSS->Open(name, m_mt.pbFormat + dwOffset, m_mt.cbFormat - dwOffset);
+ }
+ else if (IsHdmvSub(&m_mt))
+ {
+ if(!(m_pSubStream = DNew CRenderedHdmvSubtitle(m_pSubLock, (m_mt.subtype == MEDIASUBTYPE_DVB_SUBTITLES) ? ST_DVB : ST_HDMV))) return E_FAIL;
+ }
+ }
+
+ AddSubStream(m_pSubStream);
+
+ return __super::CompleteConnect(pReceivePin);
+}
+
+HRESULT CSubtitleInputPin::BreakConnect()
+{
+ RemoveSubStream(m_pSubStream);
+ m_pSubStream = NULL;
+
+ ASSERT(IsStopped());
+
+ return __super::BreakConnect();
+}
+
+STDMETHODIMP CSubtitleInputPin::ReceiveConnection(IPin* pConnector, const AM_MEDIA_TYPE* pmt)
+{
+ if(m_Connected)
+ {
+ RemoveSubStream(m_pSubStream);
+ m_pSubStream = NULL;
+
+ m_Connected->Release();
+ m_Connected = NULL;
+ }
+
+ return __super::ReceiveConnection(pConnector, pmt);
+}
+
+STDMETHODIMP CSubtitleInputPin::NewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate)
+{
+ CAutoLock cAutoLock(&m_csReceive);
+
+ if(m_mt.majortype == MEDIATYPE_Text
+ || m_mt.majortype == MEDIATYPE_Subtitle
+ && (m_mt.subtype == MEDIASUBTYPE_UTF8
+ /*|| m_mt.subtype == MEDIASUBTYPE_USF*/
+ || m_mt.subtype == MEDIASUBTYPE_SSA
+ || m_mt.subtype == MEDIASUBTYPE_ASS
+ || m_mt.subtype == MEDIASUBTYPE_ASS2))
+ {
+ CAutoLock cAutoLock(m_pSubLock);
+ CRenderedTextSubtitle* pRTS = (CRenderedTextSubtitle*)(ISubStream*)m_pSubStream;
+ pRTS->RemoveAll();
+ pRTS->CreateSegments();
+ }
+ else if(m_mt.majortype == MEDIATYPE_Subtitle && m_mt.subtype == MEDIASUBTYPE_SSF)
+ {
+ CAutoLock cAutoLock(m_pSubLock);
+ ssf::CRenderer* pSSF = (ssf::CRenderer*)(ISubStream*)m_pSubStream;
+ // LAME, implement RemoveSubtitles
+ DWORD dwOffset = ((SUBTITLEINFO*)m_mt.pbFormat)->dwOffset;
+ pSSF->Open(ssf::MemoryInputStream(m_mt.pbFormat + dwOffset, m_mt.cbFormat - dwOffset, false, false), _T(""));
+ // pSSF->RemoveSubtitles();
+ }
+ else if(m_mt.majortype == MEDIATYPE_Subtitle && (m_mt.subtype == MEDIASUBTYPE_VOBSUB))
+ {
+ CAutoLock cAutoLock(m_pSubLock);
+ CVobSubStream* pVSS = (CVobSubStream*)(ISubStream*)m_pSubStream;
+ pVSS->RemoveAll();
+ }
+ else if (IsHdmvSub(&m_mt))
+ {
+ CAutoLock cAutoLock(m_pSubLock);
+ CRenderedHdmvSubtitle* pHdmvSubtitle = (CRenderedHdmvSubtitle*)(ISubStream*)m_pSubStream;
+ pHdmvSubtitle->NewSegment (tStart, tStop, dRate);
+ }
+
+ return __super::NewSegment(tStart, tStop, dRate);
+}
+
+interface __declspec(uuid("D3D92BC3-713B-451B-9122-320095D51EA5"))
+IMpeg2DemultiplexerTesting : public IUnknown
+{
+ STDMETHOD(GetMpeg2StreamType)(ULONG* plType) = NULL;
+ STDMETHOD(toto)() = NULL;
+};
+
+
+STDMETHODIMP CSubtitleInputPin::Receive(IMediaSample* pSample)
+{
+ HRESULT hr;
+
+ hr = __super::Receive(pSample);
+ if(FAILED(hr)) return hr;
+
+ CAutoLock cAutoLock(&m_csReceive);
+
+ REFERENCE_TIME tStart, tStop;
+ pSample->GetTime(&tStart, &tStop);
+ tStart += m_tStart;
+ tStop += m_tStart;
+
+ BYTE* pData = NULL;
+ hr = pSample->GetPointer(&pData);
+ if(FAILED(hr) || pData == NULL) return hr;
+
+ int len = pSample->GetActualDataLength();
+
+ bool fInvalidate = false;
+
+ if(m_mt.majortype == MEDIATYPE_Text)
+ {
+ CAutoLock cAutoLock(m_pSubLock);
+ CRenderedTextSubtitle* pRTS = (CRenderedTextSubtitle*)(ISubStream*)m_pSubStream;
+
+ if(!strncmp((char*)pData, __GAB1__, strlen(__GAB1__)))
+ {
+ char* ptr = (char*)&pData[strlen(__GAB1__)+1];
+ char* end = (char*)&pData[len];
+
+ while(ptr < end)
+ {
+ WORD tag = *((WORD*)(ptr)); ptr += 2;
+ WORD size = *((WORD*)(ptr)); ptr += 2;
+
+ if(tag == __GAB1_LANGUAGE__)
+ {
+ pRTS->m_name = CString(ptr);
+ }
+ else if(tag == __GAB1_ENTRY__)
+ {
+ pRTS->Add(AToW(&ptr[8]), false, *(int*)ptr, *(int*)(ptr+4));
+ fInvalidate = true;
+ }
+ else if(tag == __GAB1_LANGUAGE_UNICODE__)
+ {
+ pRTS->m_name = (WCHAR*)ptr;
+ }
+ else if(tag == __GAB1_ENTRY_UNICODE__)
+ {
+ pRTS->Add((WCHAR*)(ptr+8), true, *(int*)ptr, *(int*)(ptr+4));
+ fInvalidate = true;
+ }
+
+ ptr += size;
+ }
+ }
+ else if(!strncmp((char*)pData, __GAB2__, strlen(__GAB2__)))
+ {
+ char* ptr = (char*)&pData[strlen(__GAB2__)+1];
+ char* end = (char*)&pData[len];
+
+ while(ptr < end)
+ {
+ WORD tag = *((WORD*)(ptr)); ptr += 2;
+ DWORD size = *((DWORD*)(ptr)); ptr += 4;
+
+ if(tag == __GAB1_LANGUAGE_UNICODE__)
+ {
+ pRTS->m_name = (WCHAR*)ptr;
+ }
+ else if(tag == __GAB1_RAWTEXTSUBTITLE__)
+ {
+ pRTS->Open((BYTE*)ptr, size, DEFAULT_CHARSET, pRTS->m_name);
+ fInvalidate = true;
+ }
+
+ ptr += size;
+ }
+ }
+ else if(pData != 0 && len > 1 && *pData != 0)
+ {
+ CStringA str((char*)pData, len);
+
+ str.Replace("\r\n", "\n");
+ str.Trim();
+
+ if(!str.IsEmpty())
+ {
+ pRTS->Add(AToW(str), false, (int)(tStart / 10000), (int)(tStop / 10000));
+ fInvalidate = true;
+ }
+ }
+ }
+ else if(m_mt.majortype == MEDIATYPE_Subtitle)
+ {
+ CAutoLock cAutoLock(m_pSubLock);
+
+ if(m_mt.subtype == MEDIASUBTYPE_UTF8)
+ {
+ CRenderedTextSubtitle* pRTS = (CRenderedTextSubtitle*)(ISubStream*)m_pSubStream;
+
+ CStringW str = UTF8To16(CStringA((LPCSTR)pData, len)).Trim();
+ if(!str.IsEmpty())
+ {
+ pRTS->Add(str, true, (int)(tStart / 10000), (int)(tStop / 10000));
+ fInvalidate = true;
+ }
+ }
+ else if(m_mt.subtype == MEDIASUBTYPE_SSA || m_mt.subtype == MEDIASUBTYPE_ASS || m_mt.subtype == MEDIASUBTYPE_ASS2)
+ {
+ CRenderedTextSubtitle* pRTS = (CRenderedTextSubtitle*)(ISubStream*)m_pSubStream;
+
+ CStringW str = UTF8To16(CStringA((LPCSTR)pData, len)).Trim();
+ if(!str.IsEmpty())
+ {
+ STSEntry stse;
+
+ int fields = m_mt.subtype == MEDIASUBTYPE_ASS2 ? 10 : 9;
+
+ CAtlList<CStringW> sl;
+ Explode(str, sl, ',', fields);
+ if(sl.GetCount() == (size_t)fields)
+ {
+ stse.readorder = wcstol(sl.RemoveHead(), NULL, 10);
+ stse.layer = wcstol(sl.RemoveHead(), NULL, 10);
+ stse.style = sl.RemoveHead();
+ stse.actor = sl.RemoveHead();
+ stse.marginRect.left = wcstol(sl.RemoveHead(), NULL, 10);
+ stse.marginRect.right = wcstol(sl.RemoveHead(), NULL, 10);
+ stse.marginRect.top = stse.marginRect.bottom = wcstol(sl.RemoveHead(), NULL, 10);
+ if(fields == 10) stse.marginRect.bottom = wcstol(sl.RemoveHead(), NULL, 10);
+ stse.effect = sl.RemoveHead();
+ stse.str = sl.RemoveHead();
+ }
+
+ if(!stse.str.IsEmpty())
+ {
+ pRTS->Add(stse.str, true, (int)(tStart / 10000), (int)(tStop / 10000),
+ stse.style, stse.actor, stse.effect, stse.marginRect, stse.layer, stse.readorder);
+ fInvalidate = true;
+ }
+ }
+ }
+ else if(m_mt.subtype == MEDIASUBTYPE_SSF)
+ {
+ ssf::CRenderer* pSSF = (ssf::CRenderer*)(ISubStream*)m_pSubStream;
+
+ CStringW str = UTF8To16(CStringA((LPCSTR)pData, len)).Trim();
+ if(!str.IsEmpty())
+ {
+ pSSF->Append(tStart, tStop, str);
+ fInvalidate = true;
+ }
+ }
+ else if(m_mt.subtype == MEDIASUBTYPE_VOBSUB)
+ {
+ CVobSubStream* pVSS = (CVobSubStream*)(ISubStream*)m_pSubStream;
+ pVSS->Add(tStart, tStop, pData, len);
+ }
+ else if (IsHdmvSub(&m_mt))
+ {
+ CAutoLock cAutoLock(m_pSubLock);
+ CRenderedHdmvSubtitle* pHdmvSubtitle = (CRenderedHdmvSubtitle*)(ISubStream*)m_pSubStream;
+ pHdmvSubtitle->ParseSample (pSample);
+ }
+ }
+
+ if(fInvalidate)
+ {
+ TRACE(_T("InvalidateSubtitle(%I64d, ..)\n"), tStart);
+ // IMPORTANT: m_pSubLock must not be locked when calling this
+ InvalidateSubtitle(tStart, m_pSubStream);
+ }
+
+ hr = S_OK;
+
+ return hr;
+}
+
+
+bool CSubtitleInputPin::IsHdmvSub(const CMediaType* pmt)
+{
+ return pmt->majortype == MEDIATYPE_Subtitle && (pmt->subtype == MEDIASUBTYPE_HDMVSUB || // Blu ray presentation graphics
+ pmt->subtype == MEDIASUBTYPE_DVB_SUBTITLES || // DVB subtitles
+ (pmt->subtype == MEDIASUBTYPE_NULL && pmt->formattype == FORMAT_SubtitleInfo)) // Workaround : support for Haali PGS
+ ? true
+ : false;
+} \ No newline at end of file
diff --git a/src/Subtitles/SubtitleInputPin.h b/src/Subtitles/SubtitleInputPin.h
new file mode 100644
index 000000000..c7cc15657
--- /dev/null
+++ b/src/Subtitles/SubtitleInputPin.h
@@ -0,0 +1,54 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include "../SubPic/ISubPic.h"
+
+//
+// CSubtitleInputPin
+//
+
+class CSubtitleInputPin : public CBaseInputPin
+{
+ CCritSec m_csReceive;
+
+ CCritSec* m_pSubLock;
+ CComPtr<ISubStream> m_pSubStream;
+
+protected:
+ virtual void AddSubStream(ISubStream* pSubStream) = 0;
+ virtual void RemoveSubStream(ISubStream* pSubStream) = 0;
+ virtual void InvalidateSubtitle(REFERENCE_TIME rtStart, ISubStream* pSubStream) = 0;
+ bool IsHdmvSub(const CMediaType* pmt);
+
+public:
+ CSubtitleInputPin(CBaseFilter* pFilter, CCritSec* pLock, CCritSec* pSubLock, HRESULT* phr);
+
+ HRESULT CheckMediaType(const CMediaType* pmt);
+ HRESULT CompleteConnect(IPin* pReceivePin);
+ HRESULT BreakConnect();
+ STDMETHODIMP ReceiveConnection(IPin* pConnector, const AM_MEDIA_TYPE* pmt);
+ STDMETHODIMP NewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate);
+ STDMETHODIMP Receive(IMediaSample* pSample);
+
+ ISubStream* GetSubStream() {return m_pSubStream;}
+};
diff --git a/src/Subtitles/Subtitles.vcproj b/src/Subtitles/Subtitles.vcproj
new file mode 100644
index 000000000..5f9ac1b5a
--- /dev/null
+++ b/src/Subtitles/Subtitles.vcproj
@@ -0,0 +1,497 @@
+<?xml version="1.0" encoding="windows-1250"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9,00"
+ Name="subtitles"
+ ProjectGUID="{5E56335F-0FB1-4EEA-B240-D8DC5E0608E4}"
+ RootNamespace="subtitles"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="131072"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ <Platform
+ Name="x64"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\common.vsprops;..\debug.vsprops"
+ UseOfMFC="1"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalOptions="/MP"
+ AdditionalIncludeDirectories="..\..\include;..\filters\BaseClasses;..\libpng\;..\zlib\"
+ PreprocessorDefinitions="WIN32;_DEBUG"
+ UsePrecompiledHeader="0"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Debug|x64"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\common.vsprops;..\debug.vsprops"
+ UseOfMFC="1"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ TargetEnvironment="3"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalOptions="/MP"
+ AdditionalIncludeDirectories="..\..\include;..\filters\BaseClasses;..\libpng\;..\zlib\"
+ PreprocessorDefinitions="_WIN64;_DEBUG"
+ UsePrecompiledHeader="0"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\common.vsprops;..\release.vsprops"
+ UseOfMFC="1"
+ CharacterSet="1"
+ WholeProgramOptimization="0"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalOptions="/MP"
+ AdditionalIncludeDirectories="..\..\include;..\filters\BaseClasses;..\libpng\;..\zlib\"
+ PreprocessorDefinitions="WIN32;NDEBUG"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ AdditionalOptions="/IGNORE:4221"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|x64"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\common.vsprops;..\release.vsprops"
+ UseOfMFC="1"
+ CharacterSet="1"
+ WholeProgramOptimization="0"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ TargetEnvironment="3"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalOptions="/MP"
+ AdditionalIncludeDirectories="..\..\include;..\filters\BaseClasses;..\libpng\;..\zlib\"
+ PreprocessorDefinitions="_WIN64;NDEBUG"
+ EnableEnhancedInstructionSet="0"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ AdditionalOptions="/IGNORE:4221"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm"
+ >
+ <File
+ RelativePath=".\BaseSub.cpp"
+ >
+ </File>
+ <File
+ RelativePath="CCDecoder.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\CompositionObject.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\DVBSub.cpp"
+ >
+ </File>
+ <File
+ RelativePath="GFN.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\HdmvSub.cpp"
+ >
+ </File>
+ <File
+ RelativePath="Rasterizer.cpp"
+ >
+ </File>
+ <File
+ RelativePath="RealTextParser.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\RenderedHdmvSubtitle.cpp"
+ >
+ </File>
+ <File
+ RelativePath="RTS.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|x64"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|x64"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="0"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\SSF.cpp"
+ >
+ </File>
+ <File
+ RelativePath="stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|x64"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|x64"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="STS.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\SubtitleInputPin.cpp"
+ >
+ </File>
+ <File
+ RelativePath="TextFile.cpp"
+ >
+ </File>
+ <File
+ RelativePath="USFSubtitles.cpp"
+ >
+ </File>
+ <File
+ RelativePath="VobSubFile.cpp"
+ >
+ </File>
+ <File
+ RelativePath="VobSubFileRipper.cpp"
+ >
+ </File>
+ <File
+ RelativePath="VobSubImage.cpp"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc"
+ >
+ <File
+ RelativePath=".\BaseSub.h"
+ >
+ </File>
+ <File
+ RelativePath="CCDecoder.h"
+ >
+ </File>
+ <File
+ RelativePath=".\CompositionObject.h"
+ >
+ </File>
+ <File
+ RelativePath=".\DVBSub.h"
+ >
+ </File>
+ <File
+ RelativePath="GFN.h"
+ >
+ </File>
+ <File
+ RelativePath=".\HdmvSub.h"
+ >
+ </File>
+ <File
+ RelativePath="Rasterizer.h"
+ >
+ </File>
+ <File
+ RelativePath="RealTextParser.h"
+ >
+ </File>
+ <File
+ RelativePath=".\RenderedHdmvSubtitle.h"
+ >
+ </File>
+ <File
+ RelativePath="RTS.h"
+ >
+ </File>
+ <File
+ RelativePath=".\SeparableFilter.h"
+ >
+ </File>
+ <File
+ RelativePath=".\SSF.h"
+ >
+ </File>
+ <File
+ RelativePath="stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath="STS.h"
+ >
+ </File>
+ <File
+ RelativePath=".\SubtitleInputPin.h"
+ >
+ </File>
+ <File
+ RelativePath="TextFile.h"
+ >
+ </File>
+ <File
+ RelativePath="USFSubtitles.h"
+ >
+ </File>
+ <File
+ RelativePath="VobSubFile.h"
+ >
+ </File>
+ <File
+ RelativePath="VobSubFileRipper.h"
+ >
+ </File>
+ <File
+ RelativePath="VobSubImage.h"
+ >
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ <Global
+ Name="DevPartner_IsInstrumented"
+ Value="1"
+ />
+ </Globals>
+</VisualStudioProject>
diff --git a/src/Subtitles/TextFile.cpp b/src/Subtitles/TextFile.cpp
new file mode 100644
index 000000000..1dfb62750
--- /dev/null
+++ b/src/Subtitles/TextFile.cpp
@@ -0,0 +1,537 @@
+/*
+ * 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 <atlbase.h>
+#include <afxinet.h>
+#include "TextFile.h"
+
+CTextFile::CTextFile(enc e)
+{
+ m_encoding = m_defaultencoding = e;
+ m_offset = 0;
+}
+
+bool CTextFile::Open(LPCTSTR lpszFileName)
+{
+ if(!__super::Open(lpszFileName, modeRead|typeBinary|shareDenyNone))
+ return(false);
+
+ m_encoding = m_defaultencoding;
+ m_offset = 0;
+
+ if(__super::GetLength() >= 2)
+ {
+ WORD w;
+ if(sizeof(w) != Read(&w, sizeof(w)))
+ return Close(), false;
+
+ if(w == 0xfeff)
+ {
+ m_encoding = LE16;
+ m_offset = 2;
+ }
+ else if(w == 0xfffe)
+ {
+ m_encoding = BE16;
+ m_offset = 2;
+ }
+ else if(w == 0xbbef && __super::GetLength() >= 3)
+ {
+ BYTE b;
+ if(sizeof(b) != Read(&b, sizeof(b)))
+ return Close(), false;
+
+ if(b == 0xbf)
+ {
+ m_encoding = UTF8;
+ m_offset = 3;
+ }
+ }
+ }
+
+ if(m_encoding == m_defaultencoding)
+ {
+ __super::Close(); // CWebTextFile::Close() would delete the temp file if we called it...
+ if(!__super::Open(lpszFileName, modeRead|typeText|shareDenyNone))
+ return(false);
+ }
+
+ return(true);
+}
+
+bool CTextFile::Save(LPCTSTR lpszFileName, enc e)
+{
+ if(!__super::Open(lpszFileName, modeCreate|modeWrite|shareDenyWrite|(e==ASCII?typeText:typeBinary)))
+ return(false);
+
+ if(e == UTF8)
+ {
+ BYTE b[3] = {0xef,0xbb,0xbf};
+ Write(b, sizeof(b));
+ }
+ else if(e == LE16)
+ {
+ BYTE b[2] = {0xff,0xfe};
+ Write(b, sizeof(b));
+ }
+ else if(e == BE16)
+ {
+ BYTE b[2] = {0xfe,0xff};
+ Write(b, sizeof(b));
+ }
+
+ m_encoding = e;
+
+ return true;
+}
+
+void CTextFile::SetEncoding(enc e)
+{
+ m_encoding = e;
+}
+
+CTextFile::enc CTextFile::GetEncoding()
+{
+ return m_encoding;
+}
+
+bool CTextFile::IsUnicode()
+{
+ return m_encoding == UTF8 || m_encoding == LE16 || m_encoding == BE16;
+}
+
+// CFile
+
+CString CTextFile::GetFilePath() const
+{
+ // to avoid a CException coming from CTime
+ return m_strFileName; // __super::GetFilePath();
+}
+
+// CStdioFile
+
+ULONGLONG CTextFile::GetPosition() const
+{
+ return(CStdioFile::GetPosition() - m_offset);
+}
+
+ULONGLONG CTextFile::GetLength() const
+{
+ return(CStdioFile::GetLength() - m_offset);
+}
+
+ULONGLONG CTextFile::Seek(LONGLONG lOff, UINT nFrom)
+{
+ ULONGLONG pos = GetPosition();
+ ULONGLONG len = GetLength();
+
+ switch(nFrom)
+ {
+ default:
+ case begin: lOff = lOff; break;
+ case current: lOff = pos + lOff; break;
+ case end: lOff = len - lOff; break;
+ }
+
+ lOff = max(min(lOff, len), 0) + m_offset;
+
+ pos = CStdioFile::Seek(lOff, begin) - m_offset;
+
+ return(pos);
+}
+
+void CTextFile::WriteString(LPCSTR lpsz/*CStringA str*/)
+{
+ CStringA str(lpsz);
+
+ if(m_encoding == ASCII)
+ {
+ __super::WriteString(AToT(str));
+ }
+ else if(m_encoding == ANSI)
+ {
+ str.Replace("\n", "\r\n");
+ Write((LPCSTR)str, str.GetLength());
+ }
+ else if(m_encoding == UTF8)
+ {
+ WriteString(AToW(str));
+ }
+ else if(m_encoding == LE16)
+ {
+ WriteString(AToW(str));
+ }
+ else if(m_encoding == BE16)
+ {
+ WriteString(AToW(str));
+ }
+}
+
+void CTextFile::WriteString(LPCWSTR lpsz/*CStringW str*/)
+{
+ CStringW str(lpsz);
+
+ if(m_encoding == ASCII)
+ {
+ __super::WriteString(WToT(str));
+ }
+ else if(m_encoding == ANSI)
+ {
+ str.Replace(L"\n", L"\r\n");
+ CStringA stra = CStringA(CString(str)); // TODO: codepage
+ Write((LPCSTR)stra, stra.GetLength());
+ }
+ else if(m_encoding == UTF8)
+ {
+ str.Replace(L"\n", L"\r\n");
+ for(size_t i = 0; i < str.GetLength(); i++)
+ {
+ DWORD c = (WORD)str[i];
+
+ if(0 <= c && c < 0x80) // 0xxxxxxx
+ {
+ Write(&c, 1);
+ }
+ else if(0x80 <= c && c < 0x800) // 110xxxxx 10xxxxxx
+ {
+ c = 0xc080|((c<<2)&0x1f00)|(c&0x003f);
+ Write((BYTE*)&c+1, 1);
+ Write(&c, 1);
+ }
+ else if(0x800 <= c && c < 0xFFFF) // 1110xxxx 10xxxxxx 10xxxxxx
+ {
+ c = 0xe08080|((c<<4)&0x0f0000)|((c<<2)&0x3f00)|(c&0x003f);
+ Write((BYTE*)&c+2, 1);
+ Write((BYTE*)&c+1, 1);
+ Write(&c, 1);
+ }
+ else
+ {
+ c = '?';
+ Write(&c, 1);
+ }
+ }
+ }
+ else if(m_encoding == LE16)
+ {
+ str.Replace(L"\n", L"\r\n");
+ Write((LPCWSTR)str, str.GetLength()*2);
+ }
+ else if(m_encoding == BE16)
+ {
+ str.Replace(L"\n", L"\r\n");
+ for(size_t i = 0; i < str.GetLength(); i++)
+ str.SetAt(i, ((str[i]>>8)&0x00ff)|((str[i]<<8)&0xff00));
+ Write((LPCWSTR)str, str.GetLength()*2);
+ }
+}
+
+BOOL CTextFile::ReadString(CStringA& str)
+{
+ bool fEOF = true;
+
+ str.Empty();
+
+ if(m_encoding == ASCII)
+ {
+ CString s;
+ fEOF = !__super::ReadString(s);
+ str = TToA(s);
+ }
+ else if(m_encoding == ANSI)
+ {
+ char c;
+ while(Read(&c, sizeof(c)) == sizeof(c))
+ {
+ fEOF = false;
+ if(c == '\r') continue;
+ if(c == '\n') break;
+ str += c;
+ }
+ }
+ else if(m_encoding == UTF8)
+ {
+ BYTE b;
+ while(Read(&b, sizeof(b)) == sizeof(b))
+ {
+ fEOF = false;
+ char c = '?';
+ if(!(b&0x80)) // 0xxxxxxx
+ {
+ c = b&0x7f;
+ }
+ else if((b&0xe0) == 0xc0) // 110xxxxx 10xxxxxx
+ {
+ if(Read(&b, sizeof(b)) != sizeof(b)) break;
+ }
+ else if((b&0xf0) == 0xe0) // 1110xxxx 10xxxxxx 10xxxxxx
+ {
+ if(Read(&b, sizeof(b)) != sizeof(b)) break;
+ if(Read(&b, sizeof(b)) != sizeof(b)) break;
+ }
+ if(c == '\r') continue;
+ if(c == '\n') break;
+ str += c;
+ }
+ }
+ else if(m_encoding == LE16)
+ {
+ WORD w;
+ while(Read(&w, sizeof(w)) == sizeof(w))
+ {
+ fEOF = false;
+ char c = '?';
+ if(!(w&0xff00)) c = w&0xff;
+ if(c == '\r') continue;
+ if(c == '\n') break;
+ str += c;
+ }
+ }
+ else if(m_encoding == BE16)
+ {
+ WORD w;
+ while(Read(&w, sizeof(w)) == sizeof(w))
+ {
+ fEOF = false;
+ char c = '?';
+ if(!(w&0xff)) c = w>>8;
+ if(c == '\r') continue;
+ if(c == '\n') break;
+ str += c;
+ }
+ }
+
+ return(!fEOF);
+}
+
+BOOL CTextFile::ReadString(CStringW& str)
+{
+ bool fEOF = true;
+
+ str.Empty();
+
+ if(m_encoding == ASCII)
+ {
+ CString s;
+ fEOF = !__super::ReadString(s);
+ str = TToW(s);
+ }
+ else if(m_encoding == ANSI)
+ {
+ CStringA stra;
+ char c;
+ while(Read(&c, sizeof(c)) == sizeof(c))
+ {
+ fEOF = false;
+ if(c == '\r') continue;
+ if(c == '\n') break;
+ stra += c;
+ }
+ str = CStringW(CString(stra)); // TODO: codepage
+ }
+ else if(m_encoding == UTF8)
+ {
+ BYTE b;
+ while(Read(&b, sizeof(b)) == sizeof(b))
+ {
+ fEOF = false;
+ WCHAR c = '?';
+ if(!(b&0x80)) // 0xxxxxxx
+ {
+ c = b&0x7f;
+ }
+ else if((b&0xe0) == 0xc0) // 110xxxxx 10xxxxxx
+ {
+ c = (b&0x1f)<<6;
+ if(Read(&b, sizeof(b)) != sizeof(b)) break;
+ c |= (b&0x3f);
+ }
+ else if((b&0xf0) == 0xe0) // 1110xxxx 10xxxxxx 10xxxxxx
+ {
+ c = (b&0x0f)<<12;
+ if(Read(&b, sizeof(b)) != sizeof(b)) break;
+ c |= (b&0x3f)<<6;
+ if(Read(&b, sizeof(b)) != sizeof(b)) break;
+ c |= (b&0x3f);
+ }
+ if(c == '\r') continue;
+ if(c == '\n') break;
+ str += c;
+ }
+ }
+ else if(m_encoding == LE16)
+ {
+ WCHAR wc;
+ while(Read(&wc, sizeof(wc)) == sizeof(wc))
+ {
+ fEOF = false;
+ if(wc == '\r') continue;
+ if(wc == '\n') break;
+ str += wc;
+ }
+ }
+ else if(m_encoding == BE16)
+ {
+ WCHAR wc;
+ while(Read(&wc, sizeof(wc)) == sizeof(wc))
+ {
+ fEOF = false;
+ wc = ((wc>>8)&0x00ff)|((wc<<8)&0xff00);
+ if(wc == '\r') continue;
+ if(wc == '\n') break;
+ str += wc;
+ }
+ }
+
+ return(!fEOF);
+}
+
+//
+// CWebTextFile
+//
+
+CWebTextFile::CWebTextFile(LONGLONG llMaxSize)
+ : m_llMaxSize(llMaxSize)
+{
+}
+
+bool CWebTextFile::Open(LPCTSTR lpszFileName)
+{
+ CString fn(lpszFileName);
+
+ if(fn.Find(_T("http://")) != 0)
+ return __super::Open(lpszFileName);
+
+ try
+ {
+ CInternetSession is;
+
+ CAutoPtr<CStdioFile> f(is.OpenURL(fn, 1, INTERNET_FLAG_TRANSFER_BINARY|INTERNET_FLAG_EXISTING_CONNECT));
+ if(!f) return(false);
+
+ TCHAR path[_MAX_PATH];
+ GetTempPath(MAX_PATH, path);
+
+ fn = path + fn.Mid(fn.ReverseFind('/')+1);
+ int i = fn.Find(_T("?"));
+ if(i > 0) fn = fn.Left(i);
+ CFile temp;
+ if(!temp.Open(fn, modeCreate|modeWrite|typeBinary|shareDenyWrite))
+ {
+ f->Close();
+ return(false);
+ }
+
+ BYTE buff[1024];
+ int len, total = 0;
+ while((len = f->Read(buff, 1024)) == 1024 && (m_llMaxSize < 0 || (total+=1024) < m_llMaxSize))
+ temp.Write(buff, len);
+ if(len > 0) temp.Write(buff, len);
+
+ m_tempfn = fn;
+
+ f->Close(); // must close it because the desctructor doesn't seem to do it and we will get an exception when "is" is destroying
+ }
+ catch(CInternetException* ie)
+ {
+ ie->Delete();
+ return(false);
+ }
+
+ return __super::Open(m_tempfn);
+}
+
+bool CWebTextFile::Save(LPCTSTR lpszFileName, enc e)
+{
+ // CWebTextFile is read-only...
+ ASSERT(0);
+ return(false);
+}
+
+void CWebTextFile::Close()
+{
+ __super::Close();
+
+ if(!m_tempfn.IsEmpty())
+ {
+ _tremove(m_tempfn);
+ m_tempfn.Empty();
+ }
+}
+
+///////////////////////////////////////////////////////////////
+
+CStringW AToW(CStringA str)
+{
+ CStringW ret;
+ for(size_t i = 0, j = str.GetLength(); i < j; i++)
+ ret += (WCHAR)(BYTE)str[i];
+ return(ret);
+}
+
+CStringA WToA(CStringW str)
+{
+ CStringA ret;
+ for(size_t i = 0, j = str.GetLength(); i < j; i++)
+ ret += (CHAR)(WORD)str[i];
+ return(ret);
+}
+
+CString AToT(CStringA str)
+{
+ CString ret;
+ for(size_t i = 0, j = str.GetLength(); i < j; i++)
+ ret += (TCHAR)(BYTE)str[i];
+ return(ret);
+}
+
+CString WToT(CStringW str)
+{
+ CString ret;
+ for(size_t i = 0, j = str.GetLength(); i < j; i++)
+ ret += (TCHAR)(WORD)str[i];
+ return(ret);
+}
+
+CStringA TToA(CString str)
+{
+ CStringA ret;
+#ifdef UNICODE
+ for(size_t i = 0, j = str.GetLength(); i < j; i++)
+ ret += (CHAR)(BYTE)str[i];
+#else
+ ret = str;
+#endif
+ return(ret);
+}
+
+CStringW TToW(CString str)
+{
+ CStringW ret;
+#ifdef UNICODE
+ ret = str;
+#else
+ for(size_t i = 0, j = str.GetLength(); i < j; i++)
+ ret += (WCHAR)(BYTE)str[i];
+#endif
+ return(ret);
+}
diff --git a/src/Subtitles/TextFile.h b/src/Subtitles/TextFile.h
new file mode 100644
index 000000000..88adc239b
--- /dev/null
+++ b/src/Subtitles/TextFile.h
@@ -0,0 +1,79 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <afx.h>
+
+class CTextFile : protected CStdioFile
+{
+public:
+ typedef enum {ASCII, UTF8, LE16, BE16, ANSI} enc;
+
+private:
+ enc m_encoding, m_defaultencoding;
+ int m_offset;
+
+public:
+ CTextFile(enc e = ASCII);
+
+ virtual bool Open(LPCTSTR lpszFileName);
+ virtual bool Save(LPCTSTR lpszFileName, enc e /*= ASCII*/);
+
+ void SetEncoding(enc e);
+ enc GetEncoding();
+ bool IsUnicode();
+
+ // CFile
+
+ CString GetFilePath() const;
+
+ // CStdioFile
+
+ ULONGLONG GetPosition() const;
+ ULONGLONG GetLength() const;
+ ULONGLONG Seek(LONGLONG lOff, UINT nFrom);
+
+ void WriteString(LPCSTR lpsz/*CStringA str*/);
+ void WriteString(LPCWSTR lpsz/*CStringW str*/);
+ BOOL ReadString(CStringA& str);
+ BOOL ReadString(CStringW& str);
+};
+
+class CWebTextFile : public CTextFile
+{
+ LONGLONG m_llMaxSize;
+ CString m_tempfn;
+
+public:
+ CWebTextFile(LONGLONG llMaxSize = 1024*1024);
+
+ bool Open(LPCTSTR lpszFileName);
+ bool Save(LPCTSTR lpszFileName, enc e /*= ASCII*/);
+ void Close();
+};
+
+extern CStringW AToW(CStringA str);
+extern CStringA WToA(CStringW str);
+extern CString AToT(CStringA str);
+extern CString WToT(CStringW str);
+extern CStringA TToA(CString str);
+extern CStringW TToW(CString str);
diff --git a/src/Subtitles/USFSubtitles.cpp b/src/Subtitles/USFSubtitles.cpp
new file mode 100644
index 000000000..d76f5b2e6
--- /dev/null
+++ b/src/Subtitles/USFSubtitles.cpp
@@ -0,0 +1,789 @@
+/*
+ * 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 "usfsubtitles.h"
+#include <msxml.h>
+
+#define DeclareNameAndValue(pNode, name, val) \
+ CComBSTR name; \
+ pNode->get_nodeName(&name); \
+ name.ToLower(); \
+ CComVariant val; \
+ pNode->get_nodeValue(&val); \
+
+#define BeginEnumAttribs(pNode, pChild, name, value) \
+ {CComPtr<IXMLDOMNamedNodeMap> pAttribs; \
+ if(SUCCEEDED(pNode->get_attributes(&pAttribs)) && pAttribs != NULL) \
+ { \
+ CComPtr<IXMLDOMNode> pChild; \
+ for(pAttribs->nextNode(&pChild); pChild; pChild = NULL, pAttribs->nextNode(&pChild)) \
+ { \
+
+#define EndEnumAttribs }}}
+
+#define BeginEnumChildren(pNode, pChild) \
+ {CComPtr<IXMLDOMNode> pChild, pNext; \
+ for(pNode->get_firstChild(&pChild); pChild; pNext = NULL, pChild->get_nextSibling(&pNext), pChild = pNext) \
+ { \
+
+#define EndEnumChildren }}
+
+static CStringW GetText(CComPtr<IXMLDOMNode> pNode)
+{
+ CComBSTR bstr;
+ pNode->get_text(&bstr);
+
+ return(CStringW(bstr));
+}
+
+static CStringW GetXML(CComPtr<IXMLDOMNode> pNode)
+{
+ CComBSTR bstr;
+ pNode->get_xml(&bstr);
+ CStringW str(bstr);
+ str.Remove('\r');
+ str.Replace('\n', ' ');
+ for(size_t i = 0; (i = str.Find(L" ", i)) >= 0; )
+ {
+ for(++i; i < str.GetLength() && (str[i] == '\n' || str[i] == ' ');)
+ str.Delete(i);
+ }
+ return(str);
+}
+
+static CStringW GetAttrib(CStringW attrib, CComPtr<IXMLDOMNode> pNode)
+{
+ CStringW ret;
+
+ BeginEnumAttribs(pNode, pChild, name, val)
+ {
+ DeclareNameAndValue(pChild, name, val);
+
+ if(CStringW(name) == attrib && val.vt == VT_BSTR) // TODO: prepare for other types
+ {
+ ret = val.bstrVal;
+ break;
+ }
+ }
+ EndEnumAttribs
+
+ return(ret);
+}
+
+static int TimeToInt(CStringW str)
+{
+ CAtlList<CStringW> sl;
+ int i = 0;
+ for(CStringW token = str.Tokenize(L":.,", i); !token.IsEmpty(); token = str.Tokenize(L":.,", i))
+ sl.AddHead(token);
+
+ if(sl.GetCount() > 4)
+ return(-1);
+
+ int time = 0;
+
+ int mul[4] = {1,1000,60*1000,60*60*1000};
+ POSITION pos = sl.GetHeadPosition();
+ for(i = 0; pos; i++)
+ {
+ const WCHAR* s = sl.GetNext(pos);
+ WCHAR* tmp = NULL;
+ int t = wcstol(s, &tmp, 10);
+ if(s >= tmp) return(-1);
+ time += t * mul[i];
+ }
+
+ return(time);
+}
+
+static DWORD StringToDWORD(CStringW str)
+{
+ if(str.IsEmpty()) return(0);
+ if(str[0] == '#') return((DWORD)wcstol(str, NULL, 16));
+ else return((DWORD)wcstol(str, NULL, 10));
+}
+
+static DWORD ColorToDWORD(CStringW str)
+{
+ if(str.IsEmpty()) return(0);
+
+ DWORD ret = 0;
+
+ if(str[0] == '#')
+ {
+ ret = (DWORD)wcstol(str.TrimLeft('#'), NULL, 16);
+ }
+ else
+ {
+ g_colors.Lookup(CString(str), ret);
+ }
+
+ ret = ((ret&0xff)<<16)|(ret&0xff00ff00)|((ret>>16)&0xff);
+
+ return(ret);
+}
+
+static int TranslateAlignment(CStringW alignment)
+{
+ return
+ !alignment.CompareNoCase(L"BottomLeft") ? 1 :
+ !alignment.CompareNoCase(L"BottomCenter") ? 2 :
+ !alignment.CompareNoCase(L"BottomRight") ? 3 :
+ !alignment.CompareNoCase(L"MiddleLeft") ? 4 :
+ !alignment.CompareNoCase(L"MiddleCenter") ? 5 :
+ !alignment.CompareNoCase(L"MiddleRight") ? 6 :
+ !alignment.CompareNoCase(L"TopLeft") ? 7 :
+ !alignment.CompareNoCase(L"TopCenter") ? 8 :
+ !alignment.CompareNoCase(L"TopRight") ? 9 :
+ 2;
+}
+
+static int TranslateMargin(CStringW margin, int wndsize)
+{
+ int ret = 0;
+
+ if(!margin.IsEmpty())
+ {
+ ret = wcstol(margin, NULL, 10);
+ if(margin.Find('%') >= 0) ret = wndsize * ret / 100;
+ }
+
+ return(ret);
+}
+
+////////////////
+
+CUSFSubtitles::CUSFSubtitles()
+{
+}
+
+CUSFSubtitles::~CUSFSubtitles()
+{
+}
+
+bool CUSFSubtitles::Read(LPCTSTR fn)
+{
+ VARIANT_BOOL vb;
+ CComPtr<IXMLDOMDocument> pDoc;
+ if(FAILED(pDoc.CoCreateInstance(CLSID_DOMDocument))
+ || FAILED(pDoc->put_async(VARIANT_FALSE))
+ || FAILED(pDoc->load(CComVariant(fn), &vb)) || vb != VARIANT_TRUE)
+ return(false);
+
+ styles.RemoveAll();
+ effects.RemoveAll();
+ texts.RemoveAll();
+
+ if(!ParseUSFSubtitles(CComQIPtr<IXMLDOMNode>(pDoc)))
+ return(false);
+
+ POSITION pos = styles.GetHeadPosition();
+ while(pos)
+ {
+ style_t* def = styles.GetNext(pos);
+
+ if(def->name.CompareNoCase(L"Default"))
+ continue;
+
+ POSITION pos2 = styles.GetHeadPosition();
+ while(pos2)
+ {
+ style_t* s = styles.GetNext(pos2);
+
+ if(!s->name.CompareNoCase(L"Default"))
+ continue;
+
+ if(s->fontstyle.face.IsEmpty()) s->fontstyle.face = def->fontstyle.face;
+ if(s->fontstyle.size.IsEmpty()) s->fontstyle.size = def->fontstyle.size;
+ if(s->fontstyle.color[0].IsEmpty()) s->fontstyle.color[0] = def->fontstyle.color[0];
+ if(s->fontstyle.color[1].IsEmpty()) s->fontstyle.color[1] = def->fontstyle.color[1];
+ if(s->fontstyle.color[2].IsEmpty()) s->fontstyle.color[2] = def->fontstyle.color[2];
+ if(s->fontstyle.color[3].IsEmpty()) s->fontstyle.color[3] = def->fontstyle.color[3];
+ if(s->fontstyle.italic.IsEmpty()) s->fontstyle.italic = def->fontstyle.italic;
+ if(s->fontstyle.weight.IsEmpty()) s->fontstyle.weight = def->fontstyle.weight;
+ if(s->fontstyle.underline.IsEmpty()) s->fontstyle.underline = def->fontstyle.underline;
+ if(s->fontstyle.alpha.IsEmpty()) s->fontstyle.alpha = def->fontstyle.alpha;
+ if(s->fontstyle.outline.IsEmpty()) s->fontstyle.outline = def->fontstyle.outline;
+ if(s->fontstyle.shadow.IsEmpty()) s->fontstyle.shadow = def->fontstyle.shadow;
+ if(s->fontstyle.wrap.IsEmpty()) s->fontstyle.wrap = def->fontstyle.wrap;
+ if(s->pal.alignment.IsEmpty()) s->pal.alignment = def->pal.alignment;
+ if(s->pal.relativeto.IsEmpty()) s->pal.relativeto = def->pal.relativeto;
+ if(s->pal.horizontal_margin.IsEmpty()) s->pal.horizontal_margin = def->pal.horizontal_margin;
+ if(s->pal.vertical_margin.IsEmpty()) s->pal.vertical_margin = def->pal.vertical_margin;
+ if(s->pal.rotate[0].IsEmpty()) s->pal.rotate[0] = def->pal.rotate[0];
+ if(s->pal.rotate[1].IsEmpty()) s->pal.rotate[1] = def->pal.rotate[1];
+ if(s->pal.rotate[2].IsEmpty()) s->pal.rotate[2] = def->pal.rotate[2];
+ }
+
+ break;
+ }
+
+ pos = texts.GetHeadPosition();
+ while(pos)
+ {
+ text_t* t = texts.GetNext(pos);
+ if(t->style.IsEmpty()) t->style = L"Default";
+ }
+
+ return(true);
+}
+
+bool CUSFSubtitles::ConvertToSTS(CSimpleTextSubtitle& sts)
+{
+ sts.m_name = metadata.language.text;
+ sts.m_encoding = CTextFile::UTF8;
+ sts.m_dstScreenSize = CSize(640, 480);
+ sts.m_fScaledBAS = true;
+ sts.m_defaultWrapStyle = 1;
+
+ // TODO: map metadata.language.code to charset num (windows doesn't have such a function...)
+ int charSet = DEFAULT_CHARSET;
+
+ POSITION pos = styles.GetHeadPosition();
+ while(pos)
+ {
+ style_t* s = styles.GetNext(pos);
+
+ if(!s->name.CompareNoCase(L"Default") && !s->fontstyle.wrap.IsEmpty())
+ {
+ sts.m_defaultWrapStyle =
+ !s->fontstyle.wrap.CompareNoCase(L"no") ? 2 :
+ !s->fontstyle.wrap.CompareNoCase(L"auto") ? 1 :
+ 1;
+ }
+
+ STSStyle* stss = DNew STSStyle;
+ if(!stss) continue;
+
+ if(!s->pal.horizontal_margin.IsEmpty())
+ stss->marginRect.left = stss->marginRect.right = TranslateMargin(s->pal.horizontal_margin, sts.m_dstScreenSize.cx);
+ if(!s->pal.vertical_margin.IsEmpty())
+ stss->marginRect.top = stss->marginRect.bottom = TranslateMargin(s->pal.vertical_margin, sts.m_dstScreenSize.cy);
+
+ stss->scrAlignment = TranslateAlignment(s->pal.alignment);
+
+ if(!s->pal.relativeto.IsEmpty()) stss->relativeTo =
+ !s->pal.relativeto.CompareNoCase(L"window") ? 0 :
+ !s->pal.relativeto.CompareNoCase(L"video") ? 1 :
+ 0;
+
+ stss->borderStyle = 0;
+ if(!s->fontstyle.outline.IsEmpty()) stss->outlineWidthX = stss->outlineWidthY = wcstol(s->fontstyle.outline, NULL, 10);
+ if(!s->fontstyle.shadow.IsEmpty()) stss->shadowDepthX = stss->shadowDepthY = wcstol(s->fontstyle.shadow, NULL, 10);
+
+ for(ptrdiff_t i = 0; i < 4; i++)
+ {
+ DWORD color = ColorToDWORD(s->fontstyle.color[i]);
+ int alpha = (BYTE)wcstol(s->fontstyle.alpha, NULL, 10);
+
+ stss->colors[i] = color & 0xffffff;
+ stss->alpha[i] = (BYTE)(color>>24);
+
+ stss->alpha[i] = stss->alpha[i] + (255 - stss->alpha[i]) * min(max(alpha, 0), 100) / 100;
+ }
+
+ if(!s->fontstyle.face.IsEmpty()) stss->fontName = s->fontstyle.face;
+ if(!s->fontstyle.size.IsEmpty()) stss->fontSize = wcstol(s->fontstyle.size, NULL, 10);
+ if(!s->fontstyle.weight.IsEmpty()) stss->fontWeight =
+ !s->fontstyle.weight.CompareNoCase(L"normal") ? FW_NORMAL :
+ !s->fontstyle.weight.CompareNoCase(L"bold") ? FW_BOLD :
+ !s->fontstyle.weight.CompareNoCase(L"lighter") ? FW_LIGHT :
+ !s->fontstyle.weight.CompareNoCase(L"bolder") ? FW_SEMIBOLD :
+ wcstol(s->fontstyle.weight, NULL, 10);
+ if(stss->fontWeight == 0) stss->fontWeight = FW_NORMAL;
+ if(!s->fontstyle.italic.IsEmpty()) stss->fItalic = s->fontstyle.italic.CompareNoCase(L"yes") == 0;
+ if(!s->fontstyle.underline.IsEmpty()) stss->fUnderline = s->fontstyle.underline.CompareNoCase(L"yes") == 0;
+ if(!s->pal.rotate[0].IsEmpty()) stss->fontAngleZ = wcstol(s->pal.rotate[0], NULL, 10);
+ if(!s->pal.rotate[1].IsEmpty()) stss->fontAngleX = wcstol(s->pal.rotate[1], NULL, 10);
+ if(!s->pal.rotate[2].IsEmpty()) stss->fontAngleY = wcstol(s->pal.rotate[2], NULL, 10);
+
+ stss->charSet = charSet;
+
+ sts.AddStyle(WToT(s->name), stss);
+ }
+
+ pos = texts.GetHeadPosition();
+ while(pos)
+ {
+ text_t* t = texts.GetNext(pos);
+
+ if(!t->pal.alignment.IsEmpty())
+ {
+ CStringW s;
+ s.Format(L"{\\an%d}", TranslateAlignment(t->pal.alignment));
+ t->str = s + t->str;
+ }
+
+ CRect marginRect;
+ marginRect.SetRectEmpty();
+
+ if(!t->pal.horizontal_margin.IsEmpty())
+ marginRect.left = marginRect.right = TranslateMargin(t->pal.horizontal_margin, sts.m_dstScreenSize.cx);
+ if(!t->pal.vertical_margin.IsEmpty())
+ marginRect.top = marginRect.bottom = TranslateMargin(t->pal.vertical_margin, sts.m_dstScreenSize.cy);
+
+ WCHAR rtags[3][8] = {L"{\\rz%d}", L"{\\rx%d}", L"{\\ry%d}"};
+ for(ptrdiff_t i = 0; i < 3; i++)
+ {
+ if(int angle = wcstol(t->pal.rotate[i], NULL, 10))
+ {
+ CStringW str;
+ str.Format(rtags[i], angle);
+ t->str = str + t->str;
+ }
+ }
+
+ if(t->style.CompareNoCase(L"Default") != 0)
+ {
+ POSITION pos = styles.GetHeadPosition();
+ while(pos)
+ {
+ style_t* s = styles.GetNext(pos);
+ if(s->name == t->style && !s->fontstyle.wrap.IsEmpty())
+ {
+ int WrapStyle =
+ !s->fontstyle.wrap.CompareNoCase(L"no") ? 2 :
+ !s->fontstyle.wrap.CompareNoCase(L"auto") ? 1 :
+ 1;
+
+ if(WrapStyle != sts.m_defaultWrapStyle)
+ {
+ CStringW str;
+ str.Format(L"{\\q%d}", WrapStyle);
+ t->str = str + t->str;
+ }
+
+ break;
+ }
+ }
+ }
+
+ // TODO: apply effects as {\t(..)} after usf's standard clearly defines them
+
+ sts.Add(t->str, true, t->start, t->stop, WToT(t->style), _T(""), _T(""), marginRect);
+ }
+
+ return(true);
+}
+
+bool CUSFSubtitles::ParseUSFSubtitles(CComPtr<IXMLDOMNode> pNode)
+{
+ DeclareNameAndValue(pNode, name, val);
+
+ if(name == L"usfsubtitles")
+ {
+ // metadata
+
+ BeginEnumChildren(pNode, pChild)
+ {
+ DeclareNameAndValue(pChild, name, val);
+
+ if(name == L"metadata")
+ {
+ ParseMetadata(pChild, metadata);
+ }
+ }
+ EndEnumChildren
+
+ // styles
+
+ BeginEnumChildren(pNode, pChild)
+ {
+ DeclareNameAndValue(pChild, name, val);
+
+ if(name == L"styles")
+ {
+ BeginEnumChildren(pChild, pGrandChild) // :)
+ {
+ DeclareNameAndValue(pGrandChild, name, val);
+
+ if(name == L"style")
+ {
+ CAutoPtr<style_t> s(DNew style_t);
+ if(s)
+ {
+ ParseStyle(pGrandChild, s);
+ styles.AddTail(s);
+ }
+ }
+ }
+ EndEnumChildren
+ }
+ }
+ EndEnumChildren
+
+ // effects
+
+ BeginEnumChildren(pNode, pChild)
+ {
+ DeclareNameAndValue(pChild, name, val);
+
+ if(name == L"effects")
+ {
+ BeginEnumChildren(pChild, pGrandChild) // :)
+ {
+ DeclareNameAndValue(pGrandChild, name, val);
+
+ if(name == L"effect")
+ {
+ CAutoPtr<effect_t> e(DNew effect_t);
+ if(e)
+ {
+ ParseEffect(pGrandChild, e);
+ effects.AddTail(e);
+ }
+ }
+ }
+ EndEnumChildren
+ }
+ }
+ EndEnumChildren
+
+ // subtitles
+
+ BeginEnumChildren(pNode, pChild)
+ {
+ DeclareNameAndValue(pChild, name, val);
+
+ if(name == L"subtitles")
+ {
+ BeginEnumChildren(pChild, pGrandChild) // :)
+ {
+ DeclareNameAndValue(pGrandChild, name, val);
+
+ if(name == L"subtitle")
+ {
+ CStringW sstart = GetAttrib(L"start", pGrandChild);
+ CStringW sstop = GetAttrib(L"stop", pGrandChild);
+ CStringW sduration = GetAttrib(L"duration", pGrandChild);
+ if(sstart.IsEmpty() || (sstop.IsEmpty() && sduration.IsEmpty()))
+ continue;
+
+ int start = TimeToInt(sstart);
+ int stop = !sstop.IsEmpty() ? TimeToInt(sstop) : (start + TimeToInt(sduration));
+
+ ParseSubtitle(pGrandChild, start, stop);
+ }
+ }
+ EndEnumChildren
+ }
+ }
+ EndEnumChildren
+
+ return(true);
+ }
+
+ BeginEnumChildren(pNode, pChild)
+ {
+ if(ParseUSFSubtitles(pChild))
+ {
+ return(true);
+ }
+ }
+ EndEnumChildren
+
+ return(false);
+}
+
+void CUSFSubtitles::ParseMetadata(CComPtr<IXMLDOMNode> pNode, metadata_t& m)
+{
+ DeclareNameAndValue(pNode, name, val);
+
+ if(name == L"title")
+ {
+ m.title = GetText(pNode);
+ }
+ else if(name == L"date")
+ {
+ m.date = GetText(pNode);
+ }
+ else if(name == L"comment")
+ {
+ m.comment = GetText(pNode);
+ }
+ else if(name == L"author")
+ {
+ BeginEnumChildren(pNode, pChild)
+ {
+ DeclareNameAndValue(pChild, name, val);
+
+ if(name == L"name")
+ m.author.name = GetText(pChild);
+ else if(name == L"email")
+ m.author.email = GetText(pChild);
+ else if(name == L"url")
+ m.author.url = GetText(pChild);
+ }
+ EndEnumChildren
+
+ return;
+ }
+ else if(name == L"language")
+ {
+ m.language.text = GetText(pNode);
+ m.language.code = GetAttrib(L"code", pNode);
+ }
+ else if(name == L"languageext")
+ {
+ m.languageext.text = GetText(pNode);
+ m.languageext.code = GetAttrib(L"code", pNode);
+ }
+
+ BeginEnumChildren(pNode, pChild)
+ {
+ ParseMetadata(pChild, metadata);
+ }
+ EndEnumChildren
+}
+
+void CUSFSubtitles::ParseStyle(CComPtr<IXMLDOMNode> pNode, style_t* s)
+{
+ DeclareNameAndValue(pNode, name, val);
+
+ if(name == L"style")
+ {
+ s->name = GetAttrib(L"name", pNode);
+ }
+ else if(name == L"fontstyle")
+ {
+ ParseFontstyle(pNode, s->fontstyle);
+ return;
+ }
+ else if(name == L"position")
+ {
+ ParsePal(pNode, s->pal);
+ return;
+ }
+
+ BeginEnumChildren(pNode, pChild)
+ {
+ ParseStyle(pChild, s);
+ }
+ EndEnumChildren
+}
+
+void CUSFSubtitles::ParseFontstyle(CComPtr<IXMLDOMNode> pNode, fontstyle_t& fs)
+{
+ fs.face = GetAttrib(L"face", pNode);
+ fs.size = GetAttrib(L"size", pNode);
+ fs.color[0] = GetAttrib(L"color", pNode);
+ fs.color[1] = GetAttrib(L"back-color", pNode);
+ fs.color[2] = GetAttrib(L"outline-color", pNode);
+ fs.color[3] = GetAttrib(L"shadow-color", pNode);
+ fs.italic = GetAttrib(L"italic", pNode);
+ fs.weight = GetAttrib(L"weight", pNode);
+ fs.underline = GetAttrib(L"underline", pNode);
+ fs.alpha = GetAttrib(L"alpha", pNode);
+ fs.outline = GetAttrib(L"outline-level", pNode);
+ fs.shadow = GetAttrib(L"shadow-level", pNode);
+ fs.wrap = GetAttrib(L"wrap", pNode);
+}
+
+void CUSFSubtitles::ParsePal(CComPtr<IXMLDOMNode> pNode, posattriblist_t& pal)
+{
+ pal.alignment = GetAttrib(L"alignment", pNode);
+ pal.relativeto = GetAttrib(L"relative-to", pNode);
+ pal.horizontal_margin = GetAttrib(L"horizontal-margin", pNode);
+ pal.vertical_margin = GetAttrib(L"vertical-margin", pNode);
+ pal.rotate[0] = GetAttrib(L"rotate-z", pNode);
+ pal.rotate[1] = GetAttrib(L"rotate-x", pNode);
+ pal.rotate[2] = GetAttrib(L"rotate-y", pNode);
+}
+
+void CUSFSubtitles::ParseEffect(CComPtr<IXMLDOMNode> pNode, effect_t* e)
+{
+ DeclareNameAndValue(pNode, name, val);
+
+ if(name == L"effect")
+ {
+ e->name = GetAttrib(L"name", pNode);
+ }
+ else if(name == L"keyframes")
+ {
+ BeginEnumChildren(pNode, pChild)
+ {
+ DeclareNameAndValue(pChild, name, val);
+
+ if(name == L"keyframe")
+ {
+ CAutoPtr<keyframe_t> k(DNew keyframe_t);
+ if(k)
+ {
+ ParseKeyframe(pChild, k);
+ e->keyframes.AddTail(k);
+ }
+ }
+ }
+ EndEnumChildren
+
+ return;
+ }
+
+ BeginEnumChildren(pNode, pChild)
+ {
+ ParseEffect(pChild, e);
+ }
+ EndEnumChildren
+}
+
+void CUSFSubtitles::ParseKeyframe(CComPtr<IXMLDOMNode> pNode, keyframe_t* k)
+{
+ DeclareNameAndValue(pNode, name, val);
+
+ if(name == L"keyframe")
+ {
+ k->position = GetAttrib(L"position", pNode);
+ }
+ else if(name == L"fontstyle")
+ {
+ ParseFontstyle(pNode, k->fontstyle);
+ return;
+ }
+ else if(name == L"position")
+ {
+ ParsePal(pNode, k->pal);
+ return;
+ }
+}
+
+void CUSFSubtitles::ParseSubtitle(CComPtr<IXMLDOMNode> pNode, int start, int stop)
+{
+ DeclareNameAndValue(pNode, name, val);
+
+ if(name == L"text" || name == L"karaoke")
+ {
+ CAutoPtr<text_t> t(DNew text_t);
+ if(t)
+ {
+ t->start = start;
+ t->stop = stop;
+ t->style = GetAttrib(L"style", pNode);
+ t->effect = GetAttrib(L"effect", pNode);
+ ParsePal(pNode, t->pal);
+ ParseText(pNode, t->str);
+ texts.AddTail(t);
+ }
+
+ return;
+ }
+// else if
+
+ BeginEnumChildren(pNode, pChild)
+ {
+ ParseSubtitle(pChild, start, stop);
+ }
+ EndEnumChildren
+}
+
+void CUSFSubtitles::ParseText(CComPtr<IXMLDOMNode> pNode, CStringW& str)
+{
+ DeclareNameAndValue(pNode, name, val);
+
+ CStringW prefix, postfix;
+
+ if(name == L"b")
+ {
+ prefix = L"{\\b1}";
+ postfix = L"{\\b}";
+ }
+ else if(name == L"i")
+ {
+ prefix = L"{\\i1}";
+ postfix = L"{\\i}";
+ }
+ else if(name == L"u")
+ {
+ prefix = L"{\\u1}";
+ postfix = L"{\\u}";
+ }
+ else if(name == L"font")
+ {
+ fontstyle_t fs;
+ ParseFontstyle(pNode, fs);
+
+ if(!fs.face.IsEmpty())
+ {
+ prefix += L"{\\fn" + fs.face + L"}";
+ postfix += L"{\\fn}";
+ }
+ if(!fs.size.IsEmpty())
+ {
+ prefix += L"{\\fs" + fs.size + L"}";
+ postfix += L"{\\fs}";
+ }
+ if(!fs.outline.IsEmpty())
+ {
+ prefix += L"{\\bord" + fs.outline + L"}";
+ postfix += L"{\\bord}";
+ }
+ if(!fs.shadow.IsEmpty())
+ {
+ prefix += L"{\\shad" + fs.shadow + L"}";
+ postfix += L"{\\shad}";
+ }
+
+ for(ptrdiff_t i = 0; i < 4; i++)
+ {
+ if(!fs.color[i].IsEmpty())
+ {
+ CStringW s;
+ s.Format(L"{\\%dc&H%06x&}", i+1, ColorToDWORD(fs.color[i]));
+ prefix += s;
+ s.Format(L"{\\%dc}", i+1);
+ postfix += s;
+ }
+ }
+ }
+ else if(name == L"k")
+ {
+ int t = wcstol(GetAttrib(L"t", pNode), NULL, 10);
+ CStringW s;
+ s.Format(L"{\\kf%d}", t / 10);
+ str += s;
+ return;
+ }
+ else if(name == L"br")
+ {
+ str += L"\\N";
+ return;
+ }
+ else if(name == L"#text")
+ {
+ str += GetXML(pNode);
+ return;
+ }
+
+ BeginEnumChildren(pNode, pChild)
+ {
+ CStringW s;
+ ParseText(pChild, s);
+ str += s;
+ }
+ EndEnumChildren
+
+ str = prefix + str + postfix;
+}
+
+void CUSFSubtitles::ParseShape(CComPtr<IXMLDOMNode> pNode)
+{
+ // no specs on this yet
+}
diff --git a/src/Subtitles/USFSubtitles.h b/src/Subtitles/USFSubtitles.h
new file mode 100644
index 000000000..0046a7d26
--- /dev/null
+++ b/src/Subtitles/USFSubtitles.h
@@ -0,0 +1,104 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <atlcoll.h>
+#include "STS.h"
+
+// metadata
+typedef struct
+{
+ CStringW name, email, url;
+} author_t;
+typedef struct
+{
+ CStringW code, text;
+} language_t;
+typedef struct
+{
+ CStringW title, date, comment;
+ author_t author;
+ language_t language, languageext;
+} metadata_t;
+// style
+typedef struct
+{
+ CStringW alignment, relativeto, horizontal_margin, vertical_margin, rotate[3];
+} posattriblist_t;
+typedef struct
+{
+ CStringW face, size, color[4], weight, italic, underline, alpha, outline, shadow, wrap;
+} fontstyle_t;
+typedef struct
+{
+ CStringW name;
+ fontstyle_t fontstyle;
+ posattriblist_t pal;
+} style_t;
+// effect
+typedef struct
+{
+ CStringW position;
+ fontstyle_t fontstyle;
+ posattriblist_t pal;
+} keyframe_t;
+typedef struct
+{
+ CStringW name;
+ CAutoPtrList<keyframe_t> keyframes;
+} effect_t;
+// subtitle/text
+typedef struct
+{
+ int start, stop;
+ CStringW effect, style, str;
+ posattriblist_t pal;
+} text_t;
+
+class CUSFSubtitles
+{
+ bool ParseUSFSubtitles(CComPtr<IXMLDOMNode> pNode);
+ void ParseMetadata(CComPtr<IXMLDOMNode> pNode, metadata_t& m);
+ void ParseStyle(CComPtr<IXMLDOMNode> pNode, style_t* s);
+ void ParseFontstyle(CComPtr<IXMLDOMNode> pNode, fontstyle_t& fs);
+ void ParsePal(CComPtr<IXMLDOMNode> pNode, posattriblist_t& pal);
+ void ParseEffect(CComPtr<IXMLDOMNode> pNode, effect_t* e);
+ void ParseKeyframe(CComPtr<IXMLDOMNode> pNode, keyframe_t* k);
+ void ParseSubtitle(CComPtr<IXMLDOMNode> pNode, int start, int stop);
+ void ParseText(CComPtr<IXMLDOMNode> pNode, CStringW& assstr);
+ void ParseShape(CComPtr<IXMLDOMNode> pNode);
+
+public:
+ CUSFSubtitles();
+ virtual ~CUSFSubtitles();
+
+ bool Read(LPCTSTR fn);
+// bool Write(LPCTSTR fn); // TODO
+
+ metadata_t metadata;
+ CAutoPtrList<style_t> styles;
+ CAutoPtrList<effect_t> effects;
+ CAutoPtrList<text_t> texts;
+
+ bool ConvertToSTS(CSimpleTextSubtitle& sts);
+// bool ConvertFromSTS(CSimpleTextSubtitle& sts); // TODO
+};
diff --git a/src/Subtitles/VobSubFile.cpp b/src/Subtitles/VobSubFile.cpp
new file mode 100644
index 000000000..bf29132d4
--- /dev/null
+++ b/src/Subtitles/VobSubFile.cpp
@@ -0,0 +1,2460 @@
+/*
+ * 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 <winioctl.h>
+#include "TextFile.h"
+#include <unrar/unrar.h>
+#include "VobSubFile.h"
+
+//
+
+struct lang_type {unsigned short id; LPCSTR lang_long;} lang_tbl[] =
+{
+ {'--', "(Not detected)"},
+ {'cc', "Closed Caption"},
+ {'aa', "Afar"},
+ {'ab', "Abkhazian"},
+ {'af', "Afrikaans"},
+ {'am', "Amharic"},
+ {'ar', "Arabic"},
+ {'as', "Assamese"},
+ {'ay', "Aymara"},
+ {'az', "Azerbaijani"},
+ {'ba', "Bashkir"},
+ {'be', "Byelorussian"},
+ {'bg', "Bulgarian"},
+ {'bh', "Bihari"},
+ {'bi', "Bislama"},
+ {'bn', "Bengali; Bangla"},
+ {'bo', "Tibetan"},
+ {'br', "Breton"},
+ {'ca', "Catalan"},
+ {'co', "Corsican"},
+ {'cs', "Czech"},
+ {'cy', "Welsh"},
+ {'da', "Dansk"},
+ {'de', "Deutsch"},
+ {'dz', "Bhutani"},
+ {'el', "Greek"},
+ {'en', "English"},
+ {'eo', "Esperanto"},
+ {'es', "Espanol"},
+ {'et', "Estonian"},
+ {'eu', "Basque"},
+ {'fa', "Persian"},
+ {'fi', "Finnish"},
+ {'fj', "Fiji"},
+ {'fo', "Faroese"},
+ {'fr', "Francais"},
+ {'fy', "Frisian"},
+ {'ga', "Irish"},
+ {'gd', "Scots Gaelic"},
+ {'gl', "Galician"},
+ {'gn', "Guarani"},
+ {'gu', "Gujarati"},
+ {'ha', "Hausa"},
+ {'he', "Hebrew"},
+ {'hi', "Hindi"},
+ {'hr', "Hrvatski"},
+ {'hu', "Hungarian"},
+ {'hy', "Armenian"},
+ {'ia', "Interlingua"},
+ {'id', "Indonesian"},
+ {'ie', "Interlingue"},
+ {'ik', "Inupiak"},
+ {'in', "Indonesian"},
+ {'is', "Islenska"},
+ {'it', "Italiano"},
+ {'iu', "Inuktitut"},
+ {'iw', "Hebrew"},
+ {'ja', "Japanese"},
+ {'ji', "Yiddish"},
+ {'jw', "Javanese"},
+ {'ka', "Georgian"},
+ {'kk', "Kazakh"},
+ {'kl', "Greenlandic"},
+ {'km', "Cambodian"},
+ {'kn', "Kannada"},
+ {'ko', "Korean"},
+ {'ks', "Kashmiri"},
+ {'ku', "Kurdish"},
+ {'ky', "Kirghiz"},
+ {'la', "Latin"},
+ {'ln', "Lingala"},
+ {'lo', "Laothian"},
+ {'lt', "Lithuanian"},
+ {'lv', "Latvian, Lettish"},
+ {'mg', "Malagasy"},
+ {'mi', "Maori"},
+ {'mk', "Macedonian"},
+ {'ml', "Malayalam"},
+ {'mn', "Mongolian"},
+ {'mo', "Moldavian"},
+ {'mr', "Marathi"},
+ {'ms', "Malay"},
+ {'mt', "Maltese"},
+ {'my', "Burmese"},
+ {'na', "Nauru"},
+ {'ne', "Nepali"},
+ {'nl', "Nederlands"},
+ {'no', "Norsk"},
+ {'oc', "Occitan"},
+ {'om', "(Afan) Oromo"},
+ {'or', "Oriya"},
+ {'pa', "Punjabi"},
+ {'pl', "Polish"},
+ {'ps', "Pashto, Pushto"},
+ {'pt', "Portugues"},
+ {'qu', "Quechua"},
+ {'rm', "Rhaeto-Romance"},
+ {'rn', "Kirundi"},
+ {'ro', "Romanian"},
+ {'ru', "Russian"},
+ {'rw', "Kinyarwanda"},
+ {'sa', "Sanskrit"},
+ {'sd', "Sindhi"},
+ {'sg', "Sangho"},
+ {'sh', "Serbo-Croatian"},
+ {'si', "Sinhalese"},
+ {'sk', "Slovak"},
+ {'sl', "Slovenian"},
+ {'sm', "Samoan"},
+ {'sn', "Shona"},
+ {'so', "Somali"},
+ {'sq', "Albanian"},
+ {'sr', "Serbian"},
+ {'ss', "Siswati"},
+ {'st', "Sesotho"},
+ {'su', "Sundanese"},
+ {'sv', "Svenska"},
+ {'sw', "Swahili"},
+ {'ta', "Tamil"},
+ {'te', "Telugu"},
+ {'tg', "Tajik"},
+ {'th', "Thai"},
+ {'ti', "Tigrinya"},
+ {'tk', "Turkmen"},
+ {'tl', "Tagalog"},
+ {'tn', "Setswana"},
+ {'to', "Tonga"},
+ {'tr', "Turkish"},
+ {'ts', "Tsonga"},
+ {'tt', "Tatar"},
+ {'tw', "Twi"},
+ {'ug', "Uighur"},
+ {'uk', "Ukrainian"},
+ {'ur', "Urdu"},
+ {'uz', "Uzbek"},
+ {'vi', "Vietnamese"},
+ {'vo', "Volapuk"},
+ {'wo', "Wolof"},
+ {'xh', "Xhosa"},
+ {'yi', "Yiddish"}, // formerly ji
+ {'yo', "Yoruba"},
+ {'za', "Zhuang"},
+ {'zh', "Chinese"},
+ {'zu', "Zulu"},
+};
+
+int find_lang(unsigned short id)
+{
+ int mid, lo = 0, hi = countof(lang_tbl) - 1;
+
+ while(lo < hi)
+ {
+ mid = (lo + hi) >> 1;
+ if(id < lang_tbl[mid].id) hi = mid;
+ else if(id > lang_tbl[mid].id) lo = mid + 1;
+ else return(mid);
+ }
+
+ return(id == lang_tbl[lo].id ? lo : 0);
+}
+
+CString FindLangFromId(WORD id)
+{
+ return CString(lang_tbl[find_lang(id)].lang_long);
+}
+
+//
+// CVobSubFile
+//
+
+CVobSubFile::CVobSubFile(CCritSec* pLock)
+ : CSubPicProviderImpl(pLock)
+ , m_sub(1024*1024)
+{
+}
+
+CVobSubFile::~CVobSubFile()
+{
+}
+
+//
+
+bool CVobSubFile::Copy(CVobSubFile& vsf)
+{
+ Close();
+
+ *(CVobSubSettings*)this = *(CVobSubSettings*)&vsf;
+ m_title = vsf.m_title;
+ m_iLang = vsf.m_iLang;
+
+ m_sub.SetLength(vsf.m_sub.GetLength());
+ m_sub.SeekToBegin();
+
+ for(ptrdiff_t i = 0; i < 32; i++)
+ {
+ SubLang& src = vsf.m_langs[i];
+ SubLang& dst = m_langs[i];
+
+ dst.id = src.id;
+ dst.name = src.name;
+ dst.alt = src.alt;
+
+ for(ptrdiff_t j = 0; j < src.subpos.GetCount(); j++)
+ {
+ SubPos& sp = src.subpos[j];
+ if(!sp.fValid) continue;
+
+ if(sp.filepos != (__int64)vsf.m_sub.Seek(sp.filepos, CFile::begin))
+ continue;
+
+ sp.filepos = m_sub.GetPosition();
+
+ BYTE buff[2048];
+ vsf.m_sub.Read(buff, 2048);
+ m_sub.Write(buff, 2048);
+
+ WORD packetsize = (buff[buff[0x16]+0x18]<<8) | buff[buff[0x16]+0x19];
+
+ for(ptrdiff_t k = 0, size, sizeleft = packetsize - 4;
+ k < packetsize - 4;
+ k += size, sizeleft -= size)
+ {
+ int hsize = buff[0x16]+0x18 + ((buff[0x15]&0x80) ? 4 : 0);
+ size = min(sizeleft, 2048 - hsize);
+
+ if(size != sizeleft)
+ {
+ while(vsf.m_sub.Read(buff, 2048))
+ {
+ if(!(buff[0x15]&0x80) && buff[buff[0x16]+0x17] == (i|0x20)) break;
+ }
+
+ m_sub.Write(buff, 2048);
+ }
+ }
+
+ dst.subpos.Add(sp);
+ }
+ }
+
+ m_sub.SetLength(m_sub.GetPosition());
+
+ return(true);
+}
+
+//
+
+void CVobSubFile::TrimExtension(CString& fn)
+{
+ int i = fn.ReverseFind('.');
+ if(i > 0)
+ {
+ CString ext = fn.Mid(i).MakeLower();
+ if(ext == _T(".ifo") || ext == _T(".idx") || ext == _T(".sub")
+ || ext == _T(".sst") || ext == _T(".son") || ext == _T(".rar"))
+ fn = fn.Left(i);
+ }
+}
+
+bool CVobSubFile::Open(CString fn)
+{
+ TrimExtension(fn);
+
+ do
+ {
+ Close();
+
+ int ver;
+ if(!ReadIdx(fn + _T(".idx"), ver))
+ break;
+
+ if(ver < 6 && !ReadIfo(fn + _T(".ifo")))
+ break;
+
+ if(!ReadSub(fn + _T(".sub")) && !ReadRar(fn + _T(".rar")))
+ break;
+
+ m_title = fn;
+
+ for(ptrdiff_t i = 0; i < 32; i++)
+ {
+ CAtlArray<SubPos>& sp = m_langs[i].subpos;
+
+ for(ptrdiff_t j = 0; j < sp.GetCount(); j++)
+ {
+ sp[j].stop = sp[j].start;
+ sp[j].fForced = false;
+
+ int packetsize = 0, datasize = 0;
+ BYTE* buff = GetPacket(j, packetsize, datasize, i);
+ if(!buff) continue;
+
+ m_img.delay = j < (sp.GetCount()-1) ? sp[j+1].start - sp[j].start : 3000;
+ m_img.GetPacketInfo(buff, packetsize, datasize);
+ if(j < (sp.GetCount()-1)) m_img.delay = min(m_img.delay, sp[j+1].start - sp[j].start);
+
+ sp[j].stop = sp[j].start + m_img.delay;
+ sp[j].fForced = m_img.fForced;
+
+ if(j > 0 && sp[j-1].stop > sp[j].start)
+ sp[j-1].stop = sp[j].start;
+
+ delete [] buff;
+ }
+ }
+
+ return(true);
+ }
+ while(false);
+
+ Close();
+
+ return(false);
+}
+
+bool CVobSubFile::Save(CString fn, SubFormat sf)
+{
+ TrimExtension(fn);
+
+ CVobSubFile vsf(NULL);
+ if(!vsf.Copy(*this))
+ return(false);
+
+ switch(sf)
+ {
+ case VobSub: return vsf.SaveVobSub(fn); break;
+ case WinSubMux: return vsf.SaveWinSubMux(fn); break;
+ case Scenarist: return vsf.SaveScenarist(fn); break;
+ case Maestro: return vsf.SaveMaestro(fn); break;
+ default: break;
+ }
+
+ return(false);
+}
+
+void CVobSubFile::Close()
+{
+ InitSettings();
+ m_title.Empty();
+ m_sub.SetLength(0);
+ m_img.Invalidate();
+ m_iLang = -1;
+ for(ptrdiff_t i = 0; i < 32; i++)
+ {
+ m_langs[i].id = 0;
+ m_langs[i].name.Empty();
+ m_langs[i].alt.Empty();
+ m_langs[i].subpos.RemoveAll();
+ }
+}
+
+//
+
+bool CVobSubFile::ReadIdx(CString fn, int& ver)
+{
+ CWebTextFile f;
+ if(!f.Open(fn))
+ return(false);
+
+ bool fError = false;
+
+ int id = -1, delay = 0, vobid = -1, cellid = -1;
+ __int64 celltimestamp = 0;
+
+ CString str;
+ for(ptrdiff_t line = 0; !fError && f.ReadString(str); line++)
+ {
+ str.Trim();
+
+ if(line == 0)
+ {
+ TCHAR buff[] = _T("VobSub index file, v");
+
+ const TCHAR* s = str;
+
+ int i = str.Find(buff);
+ if(i < 0 || _stscanf(&s[i+_tcslen(buff)], _T("%d"), &ver) != 1
+ || ver > VOBSUBIDXVER)
+ {
+ AfxMessageBox(_T("Wrong file version!"));
+ fError = true;
+ continue;
+ }
+ }
+ else if(!str.GetLength())
+ {
+ continue;
+ }
+ else if(str[0] == _T('#'))
+ {
+ TCHAR buff[] = _T("Vob/Cell ID:");
+
+ const TCHAR* s = str;
+
+ int i = str.Find(buff);
+ if(i >= 0)
+ {
+ _stscanf(&s[i+_tcslen(buff)], _T("%d, %d (PTS: %d)"), &vobid, &cellid, &celltimestamp);
+ }
+
+ continue;
+ }
+
+ int i = str.Find(':');
+ if(i <= 0) continue;
+
+ CString entry = str.Left(i).MakeLower();
+
+ str = str.Mid(i+1);
+ str.Trim();
+ if(str.IsEmpty()) continue;
+
+ if(entry == _T("size"))
+ {
+ int x, y;
+ if(_stscanf(str, _T("%dx%d"), &x, &y) != 2) fError = true;
+ m_size.cx = x;
+ m_size.cy = y;
+ }
+ else if(entry == _T("org"))
+ {
+ if(_stscanf(str, _T("%d,%d"), &m_x, &m_y) != 2) fError = true;
+ else m_org = CPoint(m_x, m_y);
+ }
+ else if(entry == _T("scale"))
+ {
+ if(ver < 5)
+ {
+ int scale = 100;
+ if(_stscanf(str, _T("%d%%"), &scale) != 1) fError = true;
+ m_scale_x = m_scale_y = scale;
+ }
+ else
+ {
+ if(_stscanf(str, _T("%d%%,%d%%"), &m_scale_x, &m_scale_y) != 2) fError = true;
+ }
+ }
+ else if(entry == _T("alpha"))
+ {
+ if(_stscanf(str, _T("%d"), &m_alpha) != 1) fError = true;
+ }
+ else if(entry == _T("smooth"))
+ {
+ str.MakeLower();
+
+ if(str.Find(_T("old")) >= 0 || str.Find(_T("2")) >= 0) m_fSmooth = 2;
+ else if(str.Find(_T("on")) >= 0 || str.Find(_T("1")) >= 0) m_fSmooth = 1;
+ else if(str.Find(_T("off")) >= 0 || str.Find(_T("0")) >= 0) m_fSmooth = 0;
+ else fError = true;
+ }
+ else if(entry == _T("fadein/out"))
+ {
+ if(_stscanf(str, _T("%d,%d"), &m_fadein, &m_fadeout) != 2) fError = true;
+ }
+ else if(entry == _T("align"))
+ {
+ str.MakeLower();
+
+ int i = 0, j = 0;
+ for(CString token = str.Tokenize(_T(" "), i);
+ j < 3 && !fError && !token.IsEmpty();
+ token = str.Tokenize(_T(" "), i), j++)
+ {
+ if(j == 0)
+ {
+ if(token == _T("on") || token == _T("1")) m_fAlign = true;
+ else if(token == _T("off") || token == _T("0")) m_fAlign = false;
+ else fError = true;
+ }
+ else if(j == 1)
+ {
+ if(token == _T("at")) {j--; continue;}
+
+ if(token == _T("left")) m_alignhor = 0;
+ else if(token == _T("center")) m_alignhor = 1;
+ else if(token == _T("right")) m_alignhor = 2;
+ else fError = true;
+ }
+ else if(j == 2)
+ {
+ if(token == _T("top")) m_alignver = 0;
+ else if(token == _T("center")) m_alignver = 1;
+ else if(token == _T("bottom")) m_alignver = 2;
+ else fError = true;
+ }
+ }
+ }
+ else if(entry == _T("time offset"))
+ {
+ bool fNegative = false;
+ if(str[0] == '-') fNegative = true;
+ str.TrimLeft(_T("+-"));
+
+ TCHAR c;
+ int hh, mm, ss, ms;
+ int n = _stscanf(str, _T("%d%c%d%c%d%c%d"), &hh, &c, &mm, &c, &ss, &c, &ms);
+
+ m_toff = n == 1
+ ? hh * (fNegative ? -1 : 1)
+ : n == 4+3
+ ? (hh*60*60*1000 + mm*60*1000 + ss*1000 + ms) * (fNegative ? -1 : 1)
+ : fError = true, 0;
+ }
+ else if(entry == _T("forced subs"))
+ {
+ str.MakeLower();
+
+ if(str.Find(_T("on")) >= 0 || str.Find(_T("1")) >= 0) m_fOnlyShowForcedSubs = true;
+ else if(str.Find(_T("off")) >= 0 || str.Find(_T("0")) >= 0) m_fOnlyShowForcedSubs = false;
+ else fError = true;
+ }
+ else if(entry == _T("langidx"))
+ {
+ if(_stscanf(str, _T("%d"), &m_iLang) != 1) fError = true;
+ }
+ else if(entry == _T("palette"))
+ {
+ if(_stscanf(str, _T("%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x"),
+ &m_orgpal[0], &m_orgpal[1], &m_orgpal[2], &m_orgpal[3],
+ &m_orgpal[4], &m_orgpal[5], &m_orgpal[6], &m_orgpal[7],
+ &m_orgpal[8], &m_orgpal[9], &m_orgpal[10], &m_orgpal[11],
+ &m_orgpal[12], &m_orgpal[13], &m_orgpal[14], &m_orgpal[15]
+ ) != 16) fError = true;
+ }
+ else if(entry == _T("custom colors"))
+ {
+ str.MakeLower();
+
+ if(str.Find(_T("on")) == 0 || str.Find(_T("1")) == 0) m_fCustomPal = true;
+ else if(str.Find(_T("off")) == 0 || str.Find(_T("0")) == 0) m_fCustomPal = false;
+ else fError = true;
+
+ i = str.Find(_T("tridx:"));
+ if(i < 0) {fError = true; continue;}
+ str = str.Mid(i + (int)_tcslen(_T("tridx:")));
+
+ int tridx;
+ if(_stscanf(str, _T("%x"), &tridx) != 1) {fError = true; continue;}
+ tridx = ((tridx&0x1000)>>12) | ((tridx&0x100)>>7) | ((tridx&0x10)>>2) | ((tridx&1)<<3);
+
+ i = str.Find(_T("colors:"));
+ if(i < 0) {fError = true; continue;}
+ str = str.Mid(i + (int)_tcslen(_T("colors:")));
+
+ RGBQUAD pal[4];
+ if(_stscanf(str, _T("%x,%x,%x,%x"), &pal[0], &pal[1], &pal[2], &pal[3]) != 4) {fError = true; continue;}
+
+ SetCustomPal(pal, tridx);
+ }
+ else if(entry == _T("id"))
+ {
+ str.MakeLower();
+
+ int langid = ((str[0]&0xff)<<8)|(str[1]&0xff);
+
+ i = str.Find(_T("index:"));
+ if(i < 0) {fError = true; continue;}
+ str = str.Mid(i + (int)_tcslen(_T("index:")));
+
+ if(_stscanf(str, _T("%d"), &id) != 1 || id < 0 || id >= 32) {fError = true; continue;}
+ if(m_iLang < 0) m_iLang = id;
+
+ m_langs[id].id = langid;
+ m_langs[id].name = lang_tbl[find_lang(langid)].lang_long;
+ m_langs[id].alt = lang_tbl[find_lang(langid)].lang_long;
+
+ delay = 0;
+ }
+ else if(id >= 0 && entry == _T("alt"))
+ {
+ m_langs[id].alt = str;
+ }
+ else if(id >= 0 && entry == _T("delay"))
+ {
+ bool fNegative = false;
+ if(str[0] == '-') fNegative = true;
+ str.TrimLeft(_T("+-"));
+
+ TCHAR c;
+ int hh, mm, ss, ms;
+ if(_stscanf(str, _T("%d%c%d%c%d%c%d"), &hh, &c, &mm, &c, &ss, &c, &ms) != 4+3) {fError = true; continue;}
+
+ delay += (hh*60*60*1000 + mm*60*1000 + ss*1000 + ms) * (fNegative ? -1 : 1);
+ }
+ else if(id >= 0 && entry == _T("timestamp"))
+ {
+ SubPos sb;
+
+ sb.vobid = vobid;
+ sb.cellid = cellid;
+ sb.celltimestamp = celltimestamp;
+ sb.fValid = true;
+
+ bool fNegative = false;
+ if(str[0] == '-') fNegative = true;
+ str.TrimLeft(_T("+-"));
+
+ TCHAR c;
+ int hh, mm, ss, ms;
+ if(_stscanf(str, _T("%d%c%d%c%d%c%d"), &hh, &c, &mm, &c, &ss, &c, &ms) != 4+3) {fError = true; continue;}
+
+ sb.start = (hh*60*60*1000 + mm*60*1000 + ss*1000 + ms) * (fNegative ? -1 : 1) + delay;
+
+ i = str.Find(_T("filepos:"));
+ if(i < 0) {fError = true; continue;}
+ str = str.Mid(i + (int)_tcslen(_T("filepos:")));
+
+ if(_stscanf(str, _T("%I64x"), &sb.filepos) != 1) {fError = true; continue;}
+
+ if(delay < 0 && m_langs[id].subpos.GetCount() > 0)
+ {
+ __int64 ts = m_langs[id].subpos[m_langs[id].subpos.GetCount()-1].start;
+
+ if(sb.start < ts)
+ {
+ delay += (int)(ts - sb.start);
+ sb.start = ts;
+ }
+ }
+
+ m_langs[id].subpos.Add(sb);
+ }
+ else fError = true;
+ }
+
+ return(!fError);
+}
+
+bool CVobSubFile::ReadSub(CString fn)
+{
+ CFile f;
+ if(!f.Open(fn, CFile::modeRead|CFile::typeBinary|CFile::shareDenyNone))
+ return(false);
+
+ m_sub.SetLength(f.GetLength());
+ m_sub.SeekToBegin();
+
+ int len;
+ BYTE buff[2048];
+ while((len = f.Read(buff, sizeof(buff))) > 0 && *(DWORD*)buff == 0xba010000)
+ m_sub.Write(buff, len);
+
+ return(true);
+}
+
+static unsigned char* RARbuff = NULL;
+static unsigned int RARpos = 0;
+
+static int PASCAL MyProcessDataProc(unsigned char* Addr, int Size)
+{
+ ASSERT(RARbuff);
+
+ memcpy(&RARbuff[RARpos], Addr, Size);
+ RARpos += Size;
+
+ return(1);
+}
+
+bool CVobSubFile::ReadRar(CString fn)
+{
+#ifdef _WIN64
+ HMODULE h = LoadLibrary(_T("unrar64.dll"));
+#else
+ HMODULE h = LoadLibrary(_T("unrar.dll"));
+#endif
+ if(!h) return(false);
+
+ RAROpenArchiveEx OpenArchiveEx = (RAROpenArchiveEx)GetProcAddress(h, "RAROpenArchiveEx");
+ RARCloseArchive CloseArchive = (RARCloseArchive)GetProcAddress(h, "RARCloseArchive");
+ RARReadHeaderEx ReadHeaderEx = (RARReadHeaderEx)GetProcAddress(h, "RARReadHeaderEx");
+ RARProcessFile ProcessFile = (RARProcessFile)GetProcAddress(h, "RARProcessFile");
+ RARSetChangeVolProc SetChangeVolProc = (RARSetChangeVolProc)GetProcAddress(h, "RARSetChangeVolProc");
+ RARSetProcessDataProc SetProcessDataProc = (RARSetProcessDataProc)GetProcAddress(h, "RARSetProcessDataProc");
+ RARSetPassword SetPassword = (RARSetPassword)GetProcAddress(h, "RARSetPassword");
+
+ if(!(OpenArchiveEx && CloseArchive && ReadHeaderEx && ProcessFile
+ && SetChangeVolProc && SetProcessDataProc && SetPassword))
+ {
+ FreeLibrary(h);
+ return(false);
+ }
+
+ struct RAROpenArchiveDataEx ArchiveDataEx;
+ memset(&ArchiveDataEx, 0, sizeof(ArchiveDataEx));
+#ifdef UNICODE
+ ArchiveDataEx.ArcNameW = (LPTSTR)(LPCTSTR)fn;
+ char fnA[_MAX_PATH];
+ if(wcstombs(fnA, fn, fn.GetLength()+1) == -1) fnA[0] = 0;
+ ArchiveDataEx.ArcName = fnA;
+#else
+ ArchiveDataEx.ArcName = (LPTSTR)(LPCTSTR)fn;
+#endif
+ ArchiveDataEx.OpenMode = RAR_OM_EXTRACT;
+ ArchiveDataEx.CmtBuf = 0;
+ HANDLE hrar = OpenArchiveEx(&ArchiveDataEx);
+ if(!hrar)
+ {
+ FreeLibrary(h);
+ return(false);
+ }
+
+ SetProcessDataProc(hrar, MyProcessDataProc);
+
+ struct RARHeaderDataEx HeaderDataEx;
+ HeaderDataEx.CmtBuf = NULL;
+
+ while(ReadHeaderEx(hrar, &HeaderDataEx) == 0)
+ {
+#ifdef UNICODE
+ CString subfn(HeaderDataEx.FileNameW);
+#else
+ CString subfn(HeaderDataEx.FileName);
+#endif
+
+ if(!subfn.Right(4).CompareNoCase(_T(".sub")))
+ {
+ CAutoVectorPtr<BYTE> buff;
+ if(!buff.Allocate(HeaderDataEx.UnpSize))
+ {
+ CloseArchive(hrar);
+ FreeLibrary(h);
+ return(false);
+ }
+
+ RARbuff = buff;
+ RARpos = 0;
+
+ if(ProcessFile(hrar, RAR_TEST, NULL, NULL))
+ {
+ CloseArchive(hrar);
+ FreeLibrary(h);
+
+ return(false);
+ }
+
+ m_sub.SetLength(HeaderDataEx.UnpSize);
+ m_sub.SeekToBegin();
+ m_sub.Write(buff, HeaderDataEx.UnpSize);
+ m_sub.SeekToBegin();
+
+ RARbuff = NULL;
+ RARpos = 0;
+
+ break;
+ }
+
+ ProcessFile(hrar, RAR_SKIP, NULL, NULL);
+ }
+
+ CloseArchive(hrar);
+ FreeLibrary(h);
+
+ return(true);
+}
+
+#define ReadBEdw(var) \
+ f.Read(&((BYTE*)&var)[3], 1); \
+ f.Read(&((BYTE*)&var)[2], 1); \
+ f.Read(&((BYTE*)&var)[1], 1); \
+ f.Read(&((BYTE*)&var)[0], 1); \
+
+bool CVobSubFile::ReadIfo(CString fn)
+{
+ CFile f;
+ if(!f.Open(fn, CFile::modeRead|CFile::typeBinary|CFile::shareDenyNone))
+ return(false);
+
+ /* PGC1 */
+
+ f.Seek(0xc0+0x0c, SEEK_SET);
+
+ DWORD pos;
+ ReadBEdw(pos);
+
+ f.Seek(pos*0x800 + 0x0c, CFile::begin);
+
+ DWORD offset;
+ ReadBEdw(offset);
+
+ /* Subpic palette */
+
+ f.Seek(pos*0x800 + offset + 0xa4, CFile::begin);
+
+ for(ptrdiff_t i = 0; i < 16; i++)
+ {
+ BYTE y, u, v, tmp;
+
+ f.Read(&tmp, 1);
+ f.Read(&y, 1);
+ f.Read(&u, 1);
+ f.Read(&v, 1);
+
+ y = (y-16)*255/219;
+
+ m_orgpal[i].rgbRed = (BYTE)min(max(1.0*y + 1.4022*(u-128), 0), 255);
+ m_orgpal[i].rgbGreen = (BYTE)min(max(1.0*y - 0.3456*(u-128) - 0.7145*(v-128), 0), 255);
+ m_orgpal[i].rgbBlue = (BYTE)min(max(1.0*y + 1.7710*(v-128), 0) , 255);
+ }
+
+ return(true);
+}
+
+bool CVobSubFile::WriteIdx(CString fn)
+{
+ CTextFile f;
+ if(!f.Save(fn, CTextFile::ASCII))
+ return(false);
+
+ CString str;
+ str.Format(_T("# VobSub index file, v%d (do not modify this line!)\n"), VOBSUBIDXVER);
+
+ f.WriteString(str);
+ f.WriteString(_T("# \n"));
+ f.WriteString(_T("# To repair desyncronization, you can insert gaps this way:\n"));
+ f.WriteString(_T("# (it usually happens after vob id changes)\n"));
+ f.WriteString(_T("# \n"));
+ f.WriteString(_T("#\t delay: [sign]hh:mm:ss:ms\n"));
+ f.WriteString(_T("# \n"));
+ f.WriteString(_T("# Where:\n"));
+ f.WriteString(_T("#\t [sign]: +, - (optional)\n"));
+ f.WriteString(_T("#\t hh: hours (0 <= hh)\n"));
+ f.WriteString(_T("#\t mm/ss: minutes/seconds (0 <= mm/ss <= 59)\n"));
+ f.WriteString(_T("#\t ms: milliseconds (0 <= ms <= 999)\n"));
+ f.WriteString(_T("# \n"));
+ f.WriteString(_T("#\t Note: You can't position a sub before the previous with a negative value.\n"));
+ f.WriteString(_T("# \n"));
+ f.WriteString(_T("# You can also modify timestamps or delete a few subs you don't like.\n"));
+ f.WriteString(_T("# Just make sure they stay in increasing order.\n"));
+ f.WriteString(_T("\n"));
+ f.WriteString(_T("\n"));
+
+ // Settings
+
+ f.WriteString(_T("# Settings\n\n"));
+
+ f.WriteString(_T("# Original frame size\n"));
+ str.Format(_T("size: %dx%d\n\n"), m_size.cx, m_size.cy);
+ f.WriteString(str);
+
+ f.WriteString(_T("# Origin, relative to the upper-left corner, can be overloaded by aligment\n"));
+ str.Format(_T("org: %d, %d\n\n"), m_x, m_y);
+ f.WriteString(str);
+
+ f.WriteString(_T("# Image scaling (hor,ver), origin is at the upper-left corner or at the alignment coord (x, y)\n"));
+ str.Format(_T("scale: %d%%, %d%%\n\n"), m_scale_x, m_scale_y);
+ f.WriteString(str);
+
+ f.WriteString(_T("# Alpha blending\n"));
+ str.Format(_T("alpha: %d%%\n\n"), m_alpha);
+ f.WriteString(str);
+
+ f.WriteString(_T("# Smoothing for very blocky images (use OLD for no filtering)\n"));
+ str.Format(_T("smooth: %s\n\n"), m_fSmooth == 0 ? _T("OFF") : m_fSmooth == 1 ? _T("ON") : _T("OLD"));
+ f.WriteString(str);
+
+ f.WriteString(_T("# In millisecs\n"));
+ str.Format(_T("fadein/out: %d, %d\n\n"), m_fadein, m_fadeout);
+ f.WriteString(str);
+
+ f.WriteString(_T("# Force subtitle placement relative to (org.x, org.y)\n"));
+ str.Format(_T("align: %s %s %s\n\n"),
+ m_fAlign ? _T("ON at") : _T("OFF at"),
+ m_alignhor == 0 ? _T("LEFT") : m_alignhor == 1 ? _T("CENTER") : m_alignhor == 2 ? _T("RIGHT") : _T(""),
+ m_alignver == 0 ? _T("TOP") : m_alignver == 1 ? _T("CENTER") : m_alignver == 2 ? _T("BOTTOM") : _T(""));
+ f.WriteString(str);
+
+ f.WriteString(_T("# For correcting non-progressive desync. (in millisecs or hh:mm:ss:ms)\n"));
+ f.WriteString(_T("# Note: Not effective in DirectVobSub, use \"delay: ... \" instead.\n"));
+ str.Format(_T("time offset: %d\n\n"), m_toff);
+ f.WriteString(str);
+
+ f.WriteString(_T("# ON: displays only forced subtitles, OFF: shows everything\n"));
+ str.Format(_T("forced subs: %s\n\n"), m_fOnlyShowForcedSubs ? _T("ON") : _T("OFF"));
+ f.WriteString(str);
+
+ f.WriteString(_T("# The original palette of the DVD\n"));
+ str.Format(_T("palette: %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x\n\n"),
+ *((unsigned int*)&m_orgpal[0])&0xffffff,
+ *((unsigned int*)&m_orgpal[1])&0xffffff,
+ *((unsigned int*)&m_orgpal[2])&0xffffff,
+ *((unsigned int*)&m_orgpal[3])&0xffffff,
+ *((unsigned int*)&m_orgpal[4])&0xffffff,
+ *((unsigned int*)&m_orgpal[5])&0xffffff,
+ *((unsigned int*)&m_orgpal[6])&0xffffff,
+ *((unsigned int*)&m_orgpal[7])&0xffffff,
+ *((unsigned int*)&m_orgpal[8])&0xffffff,
+ *((unsigned int*)&m_orgpal[9])&0xffffff,
+ *((unsigned int*)&m_orgpal[10])&0xffffff,
+ *((unsigned int*)&m_orgpal[11])&0xffffff,
+ *((unsigned int*)&m_orgpal[12])&0xffffff,
+ *((unsigned int*)&m_orgpal[13])&0xffffff,
+ *((unsigned int*)&m_orgpal[14])&0xffffff,
+ *((unsigned int*)&m_orgpal[15])&0xffffff);
+ f.WriteString(str);
+
+ int tridx = (!!(m_tridx&1))*0x1000 + (!!(m_tridx&2))*0x100 + (!!(m_tridx&4))*0x10 + (!!(m_tridx&8));
+
+ f.WriteString(_T("# Custom colors (transp idxs and the four colors)\n"));
+ str.Format(_T("custom colors: %s, tridx: %04x, colors: %06x, %06x, %06x, %06x\n\n"),
+ m_fCustomPal ? _T("ON") : _T("OFF"),
+ tridx,
+ *((unsigned int*)&m_cuspal[0])&0xffffff,
+ *((unsigned int*)&m_cuspal[1])&0xffffff,
+ *((unsigned int*)&m_cuspal[2])&0xffffff,
+ *((unsigned int*)&m_cuspal[3])&0xffffff);
+ f.WriteString(str);
+
+ f.WriteString(_T("# Language index in use\n"));
+ str.Format(_T("langidx: %d\n\n"), m_iLang);
+ f.WriteString(str);
+
+ // Subs
+
+ for(ptrdiff_t i = 0; i < 32; i++)
+ {
+ SubLang& sl = m_langs[i];
+
+ CAtlArray<SubPos>& sp = sl.subpos;
+ if(sp.IsEmpty() && !sl.id) continue;
+
+ str.Format(_T("# %s\n"), sl.name);
+ f.WriteString(str);
+
+ ASSERT(sl.id);
+ if(!sl.id) sl.id = '--';
+ str.Format(_T("id: %c%c, index: %d\n"), sl.id>>8, sl.id&0xff, i);
+ f.WriteString(str);
+
+ str.Format(_T("# Decomment next line to activate alternative name in DirectVobSub / Windows Media Player 6.x\n"));
+ f.WriteString(str);
+ str.Format(_T("alt: %s\n"), sl.alt);
+ if(sl.name == sl.alt) str = _T("# ") + str;
+ f.WriteString(str);
+
+ char vobid = -1, cellid = -1;
+
+ for(ptrdiff_t j = 0; j < sp.GetCount(); j++)
+ {
+ if(!sp[j].fValid) continue;
+
+ if(sp[j].vobid != vobid || sp[j].cellid != cellid)
+ {
+ str.Format(_T("# Vob/Cell ID: %d, %d (PTS: %d)\n"), sp[j].vobid, sp[j].cellid, sp[j].celltimestamp);
+ f.WriteString(str);
+ vobid = sp[j].vobid;
+ cellid = sp[j].cellid;
+ }
+
+ str.Format(_T("timestamp: %s%02d:%02d:%02d:%03d, filepos: %09I64x\n"),
+ sp[j].start < 0 ? _T("-") : _T(""),
+ abs(int((sp[j].start/1000/60/60)%60)),
+ abs(int((sp[j].start/1000/60)%60)),
+ abs(int((sp[j].start/1000)%60)),
+ abs(int((sp[j].start)%1000)),
+ sp[j].filepos);
+ f.WriteString(str);
+ }
+
+ f.WriteString(_T("\n"));
+ }
+
+ return(true);
+}
+
+bool CVobSubFile::WriteSub(CString fn)
+{
+ CFile f;
+ if(!f.Open(fn, CFile::modeCreate|CFile::modeWrite|CFile::typeBinary|CFile::shareDenyWrite))
+ return(false);
+
+ if(m_sub.GetLength() == 0)
+ return(true); // nothing to do...
+
+ m_sub.SeekToBegin();
+
+ int len;
+ BYTE buff[2048];
+ while((len = m_sub.Read(buff, sizeof(buff))) > 0 && *(DWORD*)buff == 0xba010000)
+ f.Write(buff, len);
+
+ return(true);
+}
+
+//
+
+BYTE* CVobSubFile::GetPacket(int idx, int& packetsize, int& datasize, int iLang)
+{
+ BYTE* ret = NULL;
+
+ if(iLang < 0 || iLang >= 32) iLang = m_iLang;
+ CAtlArray<SubPos>& sp = m_langs[iLang].subpos;
+
+ do
+ {
+ if(idx < 0 || idx >= sp.GetCount())
+ break;
+
+ if((__int64)m_sub.Seek(sp[idx].filepos, CFile::begin) != sp[idx].filepos)
+ break;
+
+ BYTE buff[0x800];
+ if(sizeof(buff) != m_sub.Read(buff, sizeof(buff)))
+ break;
+
+ // let's check a few things to make sure...
+ if(*(DWORD*)&buff[0x00] != 0xba010000
+ || *(DWORD*)&buff[0x0e] != 0xbd010000
+ || !(buff[0x15] & 0x80)
+ || (buff[0x17] & 0xf0) != 0x20
+ || (buff[buff[0x16] + 0x17] & 0xe0) != 0x20
+ || (buff[buff[0x16] + 0x17] & 0x1f) != iLang)
+ break;
+
+ packetsize = (buff[buff[0x16] + 0x18] << 8) + buff[buff[0x16] + 0x19];
+ datasize = (buff[buff[0x16] + 0x1a] << 8) + buff[buff[0x16] + 0x1b];
+
+ ret = DNew BYTE[packetsize];
+ if(!ret) break;
+
+ int i = 0, sizeleft = packetsize;
+ for(ptrdiff_t size;
+ i < packetsize;
+ i += size, sizeleft -= size)
+ {
+ int hsize = 0x18 + buff[0x16];
+ size = min(sizeleft, 0x800 - hsize);
+ memcpy(&ret[i], &buff[hsize], size);
+
+ if(size != sizeleft)
+ {
+ while(m_sub.Read(buff, sizeof(buff)))
+ {
+ if(/*!(buff[0x15] & 0x80) &&*/ buff[buff[0x16] + 0x17] == (iLang|0x20))
+ break;
+ }
+ }
+ }
+
+ if(i != packetsize || sizeleft > 0)
+ delete [] ret, ret = NULL;
+ }
+ while(false);
+
+ return(ret);
+}
+
+bool CVobSubFile::GetFrame(int idx, int iLang)
+{
+ if(iLang < 0 || iLang >= 32) iLang = m_iLang;
+ CAtlArray<SubPos>& sp = m_langs[iLang].subpos;
+
+ if(idx < 0 || idx >= sp.GetCount())
+ return(false);
+
+ if(m_img.iLang != iLang || m_img.iIdx != idx)
+ {
+ int packetsize = 0, datasize = 0;
+ CAutoVectorPtr<BYTE> buff;
+ buff.Attach(GetPacket(idx, packetsize, datasize, iLang));
+ if(!buff || packetsize <= 0 || datasize <= 0) return(false);
+
+ m_img.start = sp[idx].start;
+ m_img.delay = idx < (sp.GetCount()-1)
+ ? sp[idx+1].start - sp[idx].start
+ : 3000;
+
+ bool ret = m_img.Decode(buff, packetsize, datasize, m_fCustomPal, m_tridx, m_orgpal, m_cuspal, true);
+
+ if(idx < (sp.GetCount()-1))
+ m_img.delay = min(m_img.delay, sp[idx+1].start - m_img.start);
+
+ if(!ret) return(false);
+
+ m_img.iIdx = idx;
+ m_img.iLang = iLang;
+ }
+
+ return(m_fOnlyShowForcedSubs ? m_img.fForced : true);
+}
+
+bool CVobSubFile::GetFrameByTimeStamp(__int64 time)
+{
+ return(GetFrame(GetFrameIdxByTimeStamp(time)));
+}
+
+int CVobSubFile::GetFrameIdxByTimeStamp(__int64 time)
+{
+ if(m_iLang < 0 || m_iLang >= 32)
+ return(-1);
+
+ CAtlArray<SubPos>& sp = m_langs[m_iLang].subpos;
+
+ int i = 0, j = (int)sp.GetCount() - 1, ret = -1;
+
+ if(j >= 0 && time >= sp[j].start)
+ return(j);
+
+ while(i < j)
+ {
+ int mid = (i + j) >> 1;
+ int midstart = (int)sp[mid].start;
+
+ if(time == midstart)
+ {
+ ret = mid;
+ break;
+ }
+ else if(time < midstart)
+ {
+ ret = -1;
+ if(j == mid) mid--;
+ j = mid;
+ }
+ else if(time > midstart)
+ {
+ ret = mid;
+ if(i == mid) mid++;
+ i = mid;
+ }
+ }
+
+ return(ret);
+}
+
+//
+
+STDMETHODIMP CVobSubFile::NonDelegatingQueryInterface(REFIID riid, void** ppv)
+{
+ CheckPointer(ppv, E_POINTER);
+ *ppv = NULL;
+
+ return
+ QI(IPersist)
+ QI(ISubStream)
+ QI(ISubPicProvider)
+ __super::NonDelegatingQueryInterface(riid, ppv);
+}
+
+// ISubPicProvider
+
+// TODO: return segments for the fade-in/out time (with animated set to "true" of course)
+
+STDMETHODIMP_(POSITION) CVobSubFile::GetStartPosition(REFERENCE_TIME rt, double fps)
+{
+ rt /= 10000;
+
+ int i = GetFrameIdxByTimeStamp(rt);
+
+ if(!GetFrame(i))
+ return(NULL);
+
+ if(rt >= (m_img.start + m_img.delay))
+ {
+ if(!GetFrame(++i))
+ return(NULL);
+ }
+
+ return((POSITION)(i+1));
+}
+
+STDMETHODIMP_(POSITION) CVobSubFile::GetNext(POSITION pos)
+{
+ int i = (int)pos;
+ return(GetFrame(i) ? (POSITION)(i+1) : NULL);
+}
+
+STDMETHODIMP_(REFERENCE_TIME) CVobSubFile::GetStart(POSITION pos, double fps)
+{
+ int i = (int)pos-1;
+ return(GetFrame(i) ? 10000i64*m_img.start : 0);
+}
+
+STDMETHODIMP_(REFERENCE_TIME) CVobSubFile::GetStop(POSITION pos, double fps)
+{
+ int i = (int)pos-1;
+ return(GetFrame(i) ? 10000i64*(m_img.start + m_img.delay) : 0);
+}
+
+STDMETHODIMP_(bool) CVobSubFile::IsAnimated(POSITION pos)
+{
+ return(false);
+}
+
+STDMETHODIMP CVobSubFile::Render(SubPicDesc& spd, REFERENCE_TIME rt, double fps, RECT& bbox)
+{
+ if(spd.bpp != 32) return E_INVALIDARG;
+
+ rt /= 10000;
+
+ if(!GetFrame(GetFrameIdxByTimeStamp(rt)))
+ return E_FAIL;
+
+ if(rt >= (m_img.start + m_img.delay))
+ return E_FAIL;
+
+ return __super::Render(spd, bbox);
+}
+
+// IPersist
+
+STDMETHODIMP CVobSubFile::GetClassID(CLSID* pClassID)
+{
+ return pClassID ? *pClassID = __uuidof(this), S_OK : E_POINTER;
+}
+
+// ISubStream
+
+STDMETHODIMP_(int) CVobSubFile::GetStreamCount()
+{
+ int iStreamCount = 0;
+ for(ptrdiff_t i = 0; i < 32; i++)
+ if(m_langs[i].subpos.GetCount()) iStreamCount++;
+ return(iStreamCount);
+}
+
+STDMETHODIMP CVobSubFile::GetStreamInfo(int iStream, WCHAR** ppName, LCID* pLCID)
+{
+ for(ptrdiff_t i = 0; i < 32; i++)
+ {
+ SubLang& sl = m_langs[i];
+
+ if(sl.subpos.IsEmpty() || iStream-- > 0)
+ continue;
+
+ if(ppName)
+ {
+ *ppName = (WCHAR*)CoTaskMemAlloc((sl.alt.GetLength() + 1) * sizeof(WCHAR));
+ if(!(*ppName))
+ return E_OUTOFMEMORY;
+
+ wcscpy(*ppName, CStringW(sl.alt));
+ }
+
+ if(pLCID)
+ {
+ *pLCID = 0; // TODO: make lcid out of "sl.id"
+ }
+
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
+STDMETHODIMP_(int) CVobSubFile::GetStream()
+{
+ int iStream = 0;
+
+ for(ptrdiff_t i = 0; i < m_iLang; i++)
+ if(!m_langs[i].subpos.IsEmpty()) iStream++;
+
+ return(iStream);
+}
+
+STDMETHODIMP CVobSubFile::SetStream(int iStream)
+{
+ for(ptrdiff_t i = 0; i < 32; i++)
+ {
+ CAtlArray<SubPos>& sp = m_langs[i].subpos;
+
+ if(sp.IsEmpty() || iStream-- > 0)
+ continue;
+
+ m_iLang = i;
+
+ m_img.Invalidate();
+
+ break;
+ }
+
+ return iStream < 0 ? S_OK : E_FAIL;
+}
+
+STDMETHODIMP CVobSubFile::Reload()
+{
+ CFileStatus s;
+ if(!CFile::GetStatus(m_title + _T(".idx"), s)) return E_FAIL;
+ return !m_title.IsEmpty() && Open(m_title) ? S_OK : E_FAIL;
+}
+
+// StretchBlt
+
+static void PixelAtBiLinear(RGBQUAD& c, int x, int y, CVobSubImage& src)
+{
+ int w = src.rect.Width(),
+ h = src.rect.Height();
+
+ int x1 = (x >> 16), y1 = (y >> 16) * w,
+ x2 = min(x1 + 1, w-1), y2 = min(y1 + w, (h-1)*w);
+
+ RGBQUAD* ptr = src.lpPixels;
+
+ RGBQUAD c11 = ptr[y1 + x1], c12 = ptr[y1 + x2],
+ c21 = ptr[y2 + x1], c22 = ptr[y2 + x2];
+
+ __int64 u2 = x & 0xffff,
+ v2 = y & 0xffff,
+ u1 = 0x10000 - u2,
+ v1 = 0x10000 - v2;
+
+ int v1u1 = int(v1*u1 >> 16) * c11.rgbReserved,
+ v1u2 = int(v1*u2 >> 16) * c12.rgbReserved,
+ v2u1 = int(v2*u1 >> 16) * c21.rgbReserved,
+ v2u2 = int(v2*u2 >> 16) * c22.rgbReserved;
+
+ c.rgbRed = (c11.rgbRed * v1u1 + c12.rgbRed * v1u2
+ + c21.rgbRed * v2u1 + c22.rgbRed * v2u2) >> 24;
+ c.rgbGreen = (c11.rgbGreen * v1u1 + c12.rgbGreen * v1u2
+ + c21.rgbGreen * v2u1 + c22.rgbGreen * v2u2) >> 24;
+ c.rgbBlue = (c11.rgbBlue * v1u1 + c12.rgbBlue * v1u2
+ + c21.rgbBlue * v2u1 + c22.rgbBlue * v2u2) >> 24;
+ c.rgbReserved = (v1u1 + v1u2
+ + v2u1 + v2u2) >> 16;
+}
+
+static void StretchBlt(SubPicDesc& spd, CRect dstrect, CVobSubImage& src)
+{
+ if(dstrect.IsRectEmpty()) return;
+
+ if((dstrect & CRect(0, 0, spd.w, spd.h)).IsRectEmpty()) return;
+
+ int sw = src.rect.Width(),
+ sh = src.rect.Height(),
+ dw = dstrect.Width(),
+ dh = dstrect.Height();
+
+ int srcx = 0,
+ srcy = 0,
+ srcdx = (sw << 16) / dw >> 1,
+ srcdy = (sh << 16) / dh >> 1;
+
+ if(dstrect.left < 0)
+ {
+ srcx = -dstrect.left * (srcdx << 1);
+ dstrect.left = 0;
+ }
+ if(dstrect.top < 0)
+ {
+ srcy = -dstrect.top * (srcdy << 1);
+ dstrect.top = 0;
+ }
+ if(dstrect.right > spd.w)
+ {
+ dstrect.right = spd.w;
+ }
+ if(dstrect.bottom > spd.h)
+ {
+ dstrect.bottom = spd.h;
+ }
+
+ if((dstrect & CRect(0, 0, spd.w, spd.h)).IsRectEmpty()) return;
+
+ dw = dstrect.Width();
+ dh = dstrect.Height();
+
+ for(ptrdiff_t y = dstrect.top; y < dstrect.bottom; y++, srcy += (srcdy<<1))
+ {
+ RGBQUAD* ptr = (RGBQUAD*)&((BYTE*)spd.bits)[y*spd.pitch] + dstrect.left;
+ RGBQUAD* endptr = ptr + dw;
+
+ for(ptrdiff_t sx = srcx; ptr < endptr; sx += (srcdx<<1), ptr++)
+ {
+// PixelAtBiLinear(*ptr, sx, srcy, src);
+////
+ RGBQUAD cc[4];
+
+ PixelAtBiLinear(cc[0], sx, srcy, src);
+ PixelAtBiLinear(cc[1], sx+srcdx, srcy, src);
+ PixelAtBiLinear(cc[2], sx, srcy+srcdy, src);
+ PixelAtBiLinear(cc[3], sx+srcdx, srcy+srcdy, src);
+
+ ptr->rgbRed = (cc[0].rgbRed + cc[1].rgbRed + cc[2].rgbRed + cc[3].rgbRed) >> 2;
+ ptr->rgbGreen = (cc[0].rgbGreen + cc[1].rgbGreen + cc[2].rgbGreen + cc[3].rgbGreen) >> 2;
+ ptr->rgbBlue = (cc[0].rgbBlue + cc[1].rgbBlue + cc[2].rgbBlue + cc[3].rgbBlue) >> 2;
+ ptr->rgbReserved = (cc[0].rgbReserved + cc[1].rgbReserved + cc[2].rgbReserved + cc[3].rgbReserved) >> 2;
+////
+ ptr->rgbRed = ptr->rgbRed * ptr->rgbReserved >> 8;
+ ptr->rgbGreen = ptr->rgbGreen * ptr->rgbReserved >> 8;
+ ptr->rgbBlue = ptr->rgbBlue * ptr->rgbReserved >> 8;
+ ptr->rgbReserved = ~ptr->rgbReserved;
+
+ }
+ }
+}
+
+//
+// CVobSubSettings
+//
+
+void CVobSubSettings::InitSettings()
+{
+ m_size.SetSize(720, 480);
+ m_toff = m_x = m_y = 0;
+ m_org.SetPoint(0, 0);
+ m_scale_x = m_scale_y = m_alpha = 100;
+ m_fadein = m_fadeout = 50;
+ m_fSmooth = 0;
+ m_fAlign = false;
+ m_alignhor = m_alignver = 0;
+ m_fOnlyShowForcedSubs = false;
+ m_fCustomPal = false;
+ m_tridx = 0;
+ memset(m_orgpal, 0, sizeof(m_orgpal));
+ memset(m_cuspal, 0, sizeof(m_cuspal));
+}
+
+bool CVobSubSettings::GetCustomPal(RGBQUAD* cuspal, int& tridx)
+{
+ memcpy(cuspal, m_cuspal, sizeof(RGBQUAD)*4);
+ tridx = m_tridx;
+ return(m_fCustomPal);
+}
+
+void CVobSubSettings::SetCustomPal(RGBQUAD* cuspal, int tridx)
+{
+ memcpy(m_cuspal, cuspal, sizeof(RGBQUAD)*4);
+ m_tridx = tridx & 0xf;
+ for(ptrdiff_t i = 0; i < 4; i++) m_cuspal[i].rgbReserved = (tridx&(1<<i)) ? 0 : 0xff;
+ m_img.Invalidate();
+}
+
+void CVobSubSettings::GetDestrect(CRect& r)
+{
+ int w = MulDiv(m_img.rect.Width(), m_scale_x, 100);
+ int h = MulDiv(m_img.rect.Height(), m_scale_y, 100);
+
+ if(!m_fAlign)
+ {
+ r.left = MulDiv(m_img.rect.left, m_scale_x, 100);
+ r.right = MulDiv(m_img.rect.right, m_scale_x, 100);
+ r.top = MulDiv(m_img.rect.top, m_scale_y, 100);
+ r.bottom = MulDiv(m_img.rect.bottom, m_scale_y, 100);
+ }
+ else
+ {
+ switch(m_alignhor)
+ {
+ case 0:
+ r.left = 0;
+ r.right = w;
+ break; // left
+ case 1:
+ r.left = -(w >> 1);
+ r.right = -(w >> 1) + w;
+ break; // center
+ case 2:
+ r.left = -w;
+ r.right = 0;
+ break; // right
+ default:
+ r.left = MulDiv(m_img.rect.left, m_scale_x, 100);
+ r.right = MulDiv(m_img.rect.right, m_scale_x, 100);
+ break;
+ }
+
+ switch(m_alignver)
+ {
+ case 0:
+ r.top = 0;
+ r.bottom = h;
+ break; // top
+ case 1:
+ r.top = -(h >> 1);
+ r.bottom = -(h >> 1) + h;
+ break; // center
+ case 2:
+ r.top = -h;
+ r.bottom = 0;
+ break; // bottom
+ default:
+ r.top = MulDiv(m_img.rect.top, m_scale_y, 100);
+ r.bottom = MulDiv(m_img.rect.bottom, m_scale_y, 100);
+ break;
+ }
+ }
+
+ r += m_org;
+}
+
+void CVobSubSettings::GetDestrect(CRect& r, int w, int h)
+{
+ GetDestrect(r);
+
+ r.left = MulDiv(r.left, w, m_size.cx);
+ r.right = MulDiv(r.right, w, m_size.cx);
+ r.top = MulDiv(r.top, h, m_size.cy);
+ r.bottom = MulDiv(r.bottom, h, m_size.cy);
+}
+
+void CVobSubSettings::SetAlignment(bool fAlign, int x, int y, int hor, int ver)
+{
+ m_fAlign = fAlign;
+ if(fAlign)
+ {
+ m_org.x = MulDiv(m_size.cx, x, 100);
+ m_org.y = MulDiv(m_size.cy, y, 100);
+ m_alignhor = min(max(hor, 0), 2);
+ m_alignver = min(max(ver, 0), 2);
+ }
+ else
+ {
+ m_org.x = m_x;
+ m_org.y = m_y;
+ }
+}
+
+#include "RTS.h"
+
+HRESULT CVobSubSettings::Render(SubPicDesc& spd, RECT& bbox)
+{
+ CRect r;
+ GetDestrect(r, spd.w, spd.h);
+ StretchBlt(spd, r, m_img);
+ /*
+ CRenderedTextSubtitle rts(NULL);
+ rts.CreateDefaultStyle(DEFAULT_CHARSET);
+ rts.m_dstScreenSize.SetSize(m_size.cx, m_size.cy);
+ CStringW assstr;
+ m_img.Polygonize(assstr, false);
+ REFERENCE_TIME rtStart = 10000i64*m_img.start, rtStop = 10000i64*(m_img.start+m_img.delay);
+ rts.Add(assstr, true, rtStart, rtStop);
+ rts.Render(spd, (rtStart+rtStop)/2, 25, r);
+ */
+ r &= CRect(CPoint(0, 0), CSize(spd.w, spd.h));
+ bbox = r;
+ return !r.IsRectEmpty() ? S_OK : S_FALSE;
+}
+
+/////////////////////////////////////////////////////////
+
+static bool CompressFile(CString fn)
+{
+ if(GetVersion() < 0)
+ return(false);
+
+ BOOL b = FALSE;
+
+ HANDLE h = CreateFile(fn, GENERIC_WRITE|GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0);
+ if(h != INVALID_HANDLE_VALUE)
+ {
+ USHORT us = COMPRESSION_FORMAT_DEFAULT;
+ DWORD nBytesReturned;
+ b = DeviceIoControl(h, FSCTL_SET_COMPRESSION, (LPVOID)&us, 2, NULL, 0, (LPDWORD)&nBytesReturned, NULL);
+ CloseHandle(h);
+ }
+
+ return(!!b);
+}
+
+bool CVobSubFile::SaveVobSub(CString fn)
+{
+ return WriteIdx(fn + _T(".idx")) && WriteSub(fn + _T(".sub"));
+}
+
+bool CVobSubFile::SaveWinSubMux(CString fn)
+{
+ TrimExtension(fn);
+
+ CStdioFile f;
+ if(!f.Open(fn + _T(".sub"), CFile::modeCreate|CFile::modeWrite|CFile::typeText|CFile::shareDenyWrite))
+ return(false);
+
+ m_img.Invalidate();
+
+ CAutoVectorPtr<BYTE> p4bpp;
+ if(!p4bpp.Allocate(720*576/2))
+ return(false);
+
+ CAtlArray<SubPos>& sp = m_langs[m_iLang].subpos;
+ for(ptrdiff_t i = 0; i < sp.GetCount(); i++)
+ {
+ if(!GetFrame(i)) continue;
+
+ int pal[4] = {0, 1, 2, 3};
+
+ for(ptrdiff_t j = 0; j < 5; j++)
+ {
+ if(j == 4 || !m_img.pal[j].tr)
+ {
+ j &= 3;
+ memset(p4bpp, (j<<4)|j, 720*576/2);
+ pal[j] ^= pal[0], pal[0] ^= pal[j], pal[j] ^= pal[0];
+ break;
+ }
+ }
+
+ int tr[4] = {m_img.pal[pal[0]].tr, m_img.pal[pal[1]].tr, m_img.pal[pal[2]].tr, m_img.pal[pal[3]].tr};
+
+ DWORD uipal[4+12];
+
+ if(!m_fCustomPal)
+ {
+ uipal[0] = *((DWORD*)&m_img.orgpal[m_img.pal[pal[0]].pal]);
+ uipal[1] = *((DWORD*)&m_img.orgpal[m_img.pal[pal[1]].pal]);
+ uipal[2] = *((DWORD*)&m_img.orgpal[m_img.pal[pal[2]].pal]);
+ uipal[3] = *((DWORD*)&m_img.orgpal[m_img.pal[pal[3]].pal]);
+ }
+ else
+ {
+ uipal[0] = *((DWORD*)&m_img.cuspal[pal[0]]) & 0xffffff;
+ uipal[1] = *((DWORD*)&m_img.cuspal[pal[1]]) & 0xffffff;
+ uipal[2] = *((DWORD*)&m_img.cuspal[pal[2]]) & 0xffffff;
+ uipal[3] = *((DWORD*)&m_img.cuspal[pal[3]]) & 0xffffff;
+ }
+
+ CAtlMap<DWORD,BYTE> palmap;
+ palmap[uipal[0]] = 0;
+ palmap[uipal[1]] = 1;
+ palmap[uipal[2]] = 2;
+ palmap[uipal[3]] = 3;
+
+ uipal[0] = 0xff; // blue background
+
+ int w = m_img.rect.Width()-2;
+ int h = m_img.rect.Height()-2;
+ int pitch = (((w+1)>>1) + 3) & ~3;
+
+ for(ptrdiff_t y = 0; y < h; y++)
+ {
+ DWORD* p = (DWORD*)&m_img.lpPixels[(y+1)*(w+2)+1];
+
+ for(ptrdiff_t x = 0; x < w; x++, p++)
+ {
+ BYTE c = 0;
+
+ if(*p & 0xff000000)
+ {
+ DWORD uic = *p & 0xffffff;
+ palmap.Lookup(uic, c);
+ }
+
+ BYTE& c4bpp = p4bpp[(h-y-1)*pitch+(x>>1)];
+ c4bpp = (x&1) ? ((c4bpp&0xf0)|c) : ((c4bpp&0x0f)|(c<<4));
+ }
+ }
+
+ int t1 = m_img.start, t2 = t1 + m_img.delay /*+ (m_size.cy==480?(1000/29.97+1):(1000/25))*/;
+
+ ASSERT(t2>t1);
+
+ if(t2 <= 0) continue;
+ if(t1 < 0) t1 = 0;
+
+ CString bmpfn;
+ bmpfn.Format(_T("%s_%06d.bmp"), fn, i+1);
+
+ CString str;
+ str.Format(_T("%s\t%02d:%02d:%02d:%02d %02d:%02d:%02d:%02d\t%03d %03d %03d %03d %d %d %d %d\n"),
+ bmpfn,
+ t1/1000/60/60, (t1/1000/60)%60, (t1/1000)%60, (t1%1000)/10,
+ t2/1000/60/60, (t2/1000/60)%60, (t2/1000)%60, (t2%1000)/10,
+ m_img.rect.Width(), m_img.rect.Height(), m_img.rect.left, m_img.rect.top,
+ (tr[0]<<4)|tr[0], (tr[1]<<4)|tr[1], (tr[2]<<4)|tr[2], (tr[3]<<4)|tr[3]);
+ f.WriteString(str);
+
+ BITMAPFILEHEADER fhdr =
+ {
+ 0x4d42,
+ sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 16*sizeof(RGBQUAD) + pitch*h,
+ 0, 0,
+ sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 16*sizeof(RGBQUAD)
+ };
+
+ BITMAPINFOHEADER ihdr =
+ {
+ sizeof(BITMAPINFOHEADER),
+ w, h, 1, 4, 0,
+ 0,
+ pitch*h, 0,
+ 16, 4
+ };
+
+ CFile bmp;
+ if(bmp.Open(bmpfn, CFile::modeCreate|CFile::modeWrite|CFile::typeBinary|CFile::shareDenyWrite))
+ {
+ bmp.Write(&fhdr, sizeof(fhdr));
+ bmp.Write(&ihdr, sizeof(ihdr));
+ bmp.Write(uipal, sizeof(RGBQUAD)*16);
+ bmp.Write(p4bpp, pitch*h);
+ bmp.Close();
+
+ CompressFile(bmpfn);
+ }
+ }
+
+ return(true);
+}
+
+bool CVobSubFile::SaveScenarist(CString fn)
+{
+ TrimExtension(fn);
+
+ CStdioFile f;
+ if(!f.Open(fn + _T(".sst"), CFile::modeCreate|CFile::modeWrite|CFile::typeText|CFile::shareDenyWrite))
+ return(false);
+
+ m_img.Invalidate();
+
+ fn.Replace('\\', '/');
+ CString title = fn.Mid(fn.ReverseFind('/')+1);
+
+ TCHAR buff[_MAX_PATH], * pFilePart = buff;
+ if(GetFullPathName(fn, MAX_PATH, buff, &pFilePart) == 0)
+ return(false);
+
+ CString fullpath = CString(buff).Left(pFilePart - buff);
+ fullpath.TrimRight(_T("\\/"));
+ if(fullpath.IsEmpty())
+ return(false);
+
+ CString str, str2;
+ str += _T("st_format\t2\n");
+ str += _T("Display_Start\t%s\n");
+ str += _T("TV_Type\t\t%s\n");
+ str += _T("Tape_Type\tNON_DROP\n");
+ str += _T("Pixel_Area\t(0 %d)\n");
+ str += _T("Directory\t%s\n");
+ str += _T("Subtitle\t%s\n");
+ str += _T("Display_Area\t(0 2 719 %d)\n");
+ str += _T("Contrast\t(15 15 15 0)\n");
+ str += _T("\n");
+ str += _T("PA\t(0 0 255 - - - )\n");
+ str += _T("E1\t(255 0 0 - - - )\n");
+ str += _T("E2\t(0 0 0 - - - )\n");
+ str += _T("BG\t(255 255 255 - - - )\n");
+ str += _T("\n");
+ str += _T("SP_NUMBER\tSTART\tEND\tFILE_NAME\n");
+ str2.Format(str,
+ !m_fOnlyShowForcedSubs ? _T("non_forced") : _T("forced"),
+ m_size.cy == 480 ? _T("NTSC") : _T("PAL"),
+ m_size.cy-3,
+ fullpath,
+ title,
+ m_size.cy == 480 ? 479 : 574);
+
+ f.WriteString(str2);
+
+ f.Flush();
+
+ RGBQUAD pal[16] =
+ {
+ {255, 0, 0, 0},
+ {0, 0, 255, 0},
+ {0, 0, 0, 0},
+ {255, 255, 255, 0},
+ {0, 255, 0, 0},
+ {255, 0, 255, 0},
+ {0, 255, 255, 0},
+ {125, 125, 0, 0},
+ {125, 125, 125, 0},
+ {225, 225, 225, 0},
+ {0, 0, 125, 0},
+ {0, 125, 0, 0},
+ {125, 0, 0, 0},
+ {255, 0, 222, 0},
+ {0, 125, 222, 0},
+ {125, 0, 125, 0},
+ };
+
+ BITMAPFILEHEADER fhdr =
+ {
+ 0x4d42,
+ sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 16*sizeof(RGBQUAD) + 360*(m_size.cy-2),
+ 0, 0,
+ sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 16*sizeof(RGBQUAD)
+ };
+
+ BITMAPINFOHEADER ihdr =
+ {
+ sizeof(BITMAPINFOHEADER),
+ 720, m_size.cy-2, 1, 4, 0,
+ 360*(m_size.cy-2),
+ 0, 0,
+ 16, 4
+ };
+
+ bool fCustomPal = m_fCustomPal;
+ m_fCustomPal = true;
+ RGBQUAD tempCusPal[4], newCusPal[4+12] = {{255, 0, 0, 0}, {0, 0, 255, 0}, {0, 0, 0, 0}, {255, 255, 255, 0}};
+ memcpy(tempCusPal, m_cuspal, sizeof(tempCusPal));
+ memcpy(m_cuspal, newCusPal, sizeof(m_cuspal));
+
+ CAutoVectorPtr<BYTE> p4bpp;
+ if(!p4bpp.Allocate((m_size.cy-2)*360))
+ return(false);
+
+ BYTE colormap[16];
+
+ for(ptrdiff_t i = 0; i < 16; i++)
+ {
+ int idx = 0, maxdif = 255*255*3+1;
+
+ for(ptrdiff_t j = 0; j < 16 && maxdif; j++)
+ {
+ int rdif = pal[j].rgbRed - m_orgpal[i].rgbRed;
+ int gdif = pal[j].rgbGreen - m_orgpal[i].rgbGreen;
+ int bdif = pal[j].rgbBlue - m_orgpal[i].rgbBlue;
+
+ int dif = rdif*rdif + gdif*gdif + bdif*bdif;
+ if(dif < maxdif) {maxdif = dif; idx = j;}
+ }
+
+ colormap[i] = idx+1;
+ }
+
+ int pc[4] = {1, 1, 1, 1}, pa[4] = {15, 15, 15, 0};
+
+ CAtlArray<SubPos>& sp = m_langs[m_iLang].subpos;
+ for(ptrdiff_t i = 0, k = 0; i < sp.GetCount(); i++)
+ {
+ if(!GetFrame(i)) continue;
+
+ for(ptrdiff_t j = 0; j < 5; j++)
+ {
+ if(j == 4 || !m_img.pal[j].tr)
+ {
+ j &= 3;
+ memset(p4bpp, (j<<4)|j, (m_size.cy-2)*360);
+ break;
+ }
+ }
+
+ for(ptrdiff_t y = max(m_img.rect.top+1, 2); y < m_img.rect.bottom-1; y++)
+ {
+ ASSERT(m_size.cy-y-1 >= 0);
+ if(m_size.cy-y-1 < 0) break;
+
+ DWORD* p = (DWORD*)&m_img.lpPixels[(y-m_img.rect.top)*m_img.rect.Width()+1];
+
+ for(ptrdiff_t x = m_img.rect.left+1; x < m_img.rect.right-1; x++, p++)
+ {
+ DWORD rgb = *p&0xffffff;
+ BYTE c = rgb == 0x0000ff ? 0 : rgb == 0xff0000 ? 1 : rgb == 0x000000 ? 2 : 3;
+ BYTE& c4bpp = p4bpp[(m_size.cy-y-1)*360+(x>>1)];
+ c4bpp = (x&1) ? ((c4bpp&0xf0)|c) : ((c4bpp&0x0f)|(c<<4));
+ }
+ }
+
+ CString bmpfn;
+ bmpfn.Format(_T("%s_%04d.bmp"), fn, i+1);
+ title = bmpfn.Mid(bmpfn.ReverseFind('/')+1);
+
+ // E1, E2, P, Bg
+ int c[4] = {colormap[m_img.pal[1].pal], colormap[m_img.pal[2].pal], colormap[m_img.pal[0].pal], colormap[m_img.pal[3].pal]};
+ c[0]^=c[1], c[1]^=c[0], c[0]^=c[1];
+
+ if(memcmp(pc, c, sizeof(c)))
+ {
+ memcpy(pc, c, sizeof(c));
+ str.Format(_T("Color\t (%d %d %d %d)\n"), c[0], c[1], c[2], c[3]);
+ f.WriteString(str);
+ }
+
+ // E1, E2, P, Bg
+ int a[4] = {m_img.pal[1].tr, m_img.pal[2].tr, m_img.pal[0].tr, m_img.pal[3].tr};
+ a[0]^=a[1], a[1]^=a[0], a[0]^=a[1];
+
+ if(memcmp(pa, a, sizeof(a)))
+ {
+ memcpy(pa, a, sizeof(a));
+ str.Format(_T("Contrast (%d %d %d %d)\n"), a[0], a[1], a[2], a[3]);
+ f.WriteString(str);
+ }
+
+ int t1 = sp[i].start;
+ int h1 = t1/1000/60/60, m1 = (t1/1000/60)%60, s1 = (t1/1000)%60;
+ int f1 = (int)((m_size.cy==480?29.97:25)*(t1%1000)/1000);
+
+ int t2 = sp[i].stop;
+ int h2 = t2/1000/60/60, m2 = (t2/1000/60)%60, s2 = (t2/1000)%60;
+ int f2 = (int)((m_size.cy==480?29.97:25)*(t2%1000)/1000);
+
+ if(t2 <= 0) continue;
+ if(t1 < 0) t1 = 0;
+
+ if(h1 == h2 && m1 == m2 && s1 == s2 && f1 == f2)
+ {
+ f2++;
+ if(f2 == (m_size.cy == 480 ? 30 : 25))
+ {
+ f2 = 0;
+ s2++;
+ if(s2 == 60)
+ {
+ s2 = 0;
+ m2++;
+ if(m2 == 60)
+ {
+ m2 = 0;
+ h2++;
+ }
+ }
+ }
+ }
+
+ if(i < sp.GetCount()-1)
+ {
+ int t3 = sp[i+1].start;
+ int h3 = t3/1000/60/60, m3 = (t3/1000/60)%60, s3 = (t3/1000)%60;
+ int f3 = (int)((m_size.cy==480?29.97:25)*(t3%1000)/1000);
+
+ if(h3 == h2 && m3 == m2 && s3 == s2 && f3 == f2)
+ {
+ f2--;
+ if(f2 == -1)
+ {
+ f2 = (m_size.cy == 480 ? 29 : 24);
+ s2--;
+ if(s2 == -1)
+ {
+ s2 = 59;
+ m2--;
+ if(m2 == -1)
+ {
+ m2 = 59;
+ if(h2 > 0) h2--;
+ }
+ }
+ }
+ }
+ }
+
+ if(h1 == h2 && m1 == m2 && s1 == s2 && f1 == f2)
+ continue;
+
+ str.Format(_T("%04d\t%02d:%02d:%02d:%02d\t%02d:%02d:%02d:%02d\t%s\n"),
+ ++k,
+ h1, m1, s1, f1,
+ h2, m2, s2, f2,
+ title);
+ f.WriteString(str);
+
+ CFile bmp;
+ if(bmp.Open(bmpfn, CFile::modeCreate|CFile::modeWrite|CFile::modeRead|CFile::typeBinary))
+ {
+ bmp.Write(&fhdr, sizeof(fhdr));
+ bmp.Write(&ihdr, sizeof(ihdr));
+ bmp.Write(newCusPal, sizeof(RGBQUAD)*16);
+ bmp.Write(p4bpp, 360*(m_size.cy-2));
+ bmp.Close();
+
+ CompressFile(bmpfn);
+ }
+ }
+
+ m_fCustomPal = fCustomPal;
+ memcpy(m_cuspal, tempCusPal, sizeof(m_cuspal));
+
+ return(true);
+}
+
+bool CVobSubFile::SaveMaestro(CString fn)
+{
+ TrimExtension(fn);
+
+ CStdioFile f;
+ if(!f.Open(fn + _T(".son"), CFile::modeCreate|CFile::modeWrite|CFile::typeText|CFile::shareDenyWrite))
+ return(false);
+
+ m_img.Invalidate();
+
+ fn.Replace('\\', '/');
+ CString title = fn.Mid(fn.ReverseFind('/')+1);
+
+ TCHAR buff[_MAX_PATH], * pFilePart = buff;
+ if(GetFullPathName(fn, MAX_PATH, buff, &pFilePart) == 0)
+ return(false);
+
+ CString fullpath = CString(buff).Left(pFilePart - buff);
+ fullpath.TrimRight(_T("\\/"));
+ if(fullpath.IsEmpty())
+ return(false);
+
+ CString str, str2;
+ str += _T("st_format\t2\n");
+ str += _T("Display_Start\t%s\n");
+ str += _T("TV_Type\t\t%s\n");
+ str += _T("Tape_Type\tNON_DROP\n");
+ str += _T("Pixel_Area\t(0 %d)\n");
+ str += _T("Directory\t%s\n");
+ str += _T("Subtitle\t%s\n");
+ str += _T("Display_Area\t(0 2 719 %d)\n");
+ str += _T("Contrast\t(15 15 15 0)\n");
+ str += _T("\n");
+ str += _T("SP_NUMBER\tSTART\tEND\tFILE_NAME\n");
+ str2.Format(str,
+ !m_fOnlyShowForcedSubs ? _T("non_forced") : _T("forced"),
+ m_size.cy == 480 ? _T("NTSC") : _T("PAL"),
+ m_size.cy-3,
+ fullpath,
+ title,
+ m_size.cy == 480 ? 479 : 574);
+
+ f.WriteString(str2);
+
+ f.Flush();
+
+ BITMAPFILEHEADER fhdr =
+ {
+ 0x4d42,
+ sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 16*sizeof(RGBQUAD) + 360*(m_size.cy-2),
+ 0, 0,
+ sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 16*sizeof(RGBQUAD)
+ };
+
+ BITMAPINFOHEADER ihdr =
+ {
+ sizeof(BITMAPINFOHEADER),
+ 720, m_size.cy-2, 1, 4, 0,
+ 360*(m_size.cy-2),
+ 0, 0,
+ 16, 4
+ };
+
+ bool fCustomPal = m_fCustomPal;
+ m_fCustomPal = true;
+ RGBQUAD tempCusPal[4], newCusPal[4+12] = {{255, 0, 0, 0}, {0, 0, 255, 0}, {0, 0, 0, 0}, {255, 255, 255, 0}};
+ memcpy(tempCusPal, m_cuspal, sizeof(tempCusPal));
+ memcpy(m_cuspal, newCusPal, sizeof(m_cuspal));
+
+ CAutoVectorPtr<BYTE> p4bpp;
+ if(!p4bpp.Allocate((m_size.cy-2)*360))
+ return(false);
+
+ BYTE colormap[16];
+ for(ptrdiff_t i = 0; i < 16; i++)
+ colormap[i] = i;
+
+ CFile spf;
+ if(spf.Open(fn + _T(".spf"), CFile::modeCreate|CFile::modeWrite|CFile::typeBinary))
+ {
+ for(ptrdiff_t i = 0; i < 16; i++)
+ {
+ COLORREF c = (m_orgpal[i].rgbBlue<<16) | (m_orgpal[i].rgbGreen<<8) | m_orgpal[i].rgbRed;
+ spf.Write(&c, sizeof(COLORREF));
+ }
+
+ spf.Close();
+ }
+
+ int pc[4] = {1,1,1,1}, pa[4] = {15,15,15,0};
+
+ CAtlArray<SubPos>& sp = m_langs[m_iLang].subpos;
+ for(ptrdiff_t i = 0, k = 0; i < sp.GetCount(); i++)
+ {
+ if(!GetFrame(i)) continue;
+
+ for(ptrdiff_t j = 0; j < 5; j++)
+ {
+ if(j == 4 || !m_img.pal[j].tr)
+ {
+ j &= 3;
+ memset(p4bpp, (j<<4)|j, (m_size.cy-2)*360);
+ break;
+ }
+ }
+
+ for(ptrdiff_t y = max(m_img.rect.top+1, 2); y < m_img.rect.bottom-1; y++)
+ {
+ ASSERT(m_size.cy-y-1 >= 0);
+ if(m_size.cy-y-1 < 0) break;
+
+ DWORD* p = (DWORD*)&m_img.lpPixels[(y-m_img.rect.top)*m_img.rect.Width()+1];
+
+ for(ptrdiff_t x = m_img.rect.left+1; x < m_img.rect.right-1; x++, p++)
+ {
+ DWORD rgb = *p&0xffffff;
+ BYTE c = rgb == 0x0000ff ? 0 : rgb == 0xff0000 ? 1 : rgb == 0x000000 ? 2 : 3;
+ BYTE& c4bpp = p4bpp[(m_size.cy-y-1)*360+(x>>1)];
+ c4bpp = (x&1) ? ((c4bpp&0xf0)|c) : ((c4bpp&0x0f)|(c<<4));
+ }
+ }
+
+ CString bmpfn;
+ bmpfn.Format(_T("%s_%04d.bmp"), fn, i+1);
+ title = bmpfn.Mid(bmpfn.ReverseFind('/')+1);
+
+ // E1, E2, P, Bg
+ int c[4] = {colormap[m_img.pal[1].pal], colormap[m_img.pal[2].pal], colormap[m_img.pal[0].pal], colormap[m_img.pal[3].pal]};
+
+ if(memcmp(pc, c, sizeof(c)))
+ {
+ memcpy(pc, c, sizeof(c));
+ str.Format(_T("Color\t (%d %d %d %d)\n"), c[0], c[1], c[2], c[3]);
+ f.WriteString(str);
+ }
+
+ // E1, E2, P, Bg
+ int a[4] = {m_img.pal[1].tr, m_img.pal[2].tr, m_img.pal[0].tr, m_img.pal[3].tr};
+
+ if(memcmp(pa, a, sizeof(a)))
+ {
+ memcpy(pa, a, sizeof(a));
+ str.Format(_T("Contrast (%d %d %d %d)\n"), a[0], a[1], a[2], a[3]);
+ f.WriteString(str);
+ }
+
+ int t1 = sp[i].start;
+ int h1 = t1/1000/60/60, m1 = (t1/1000/60)%60, s1 = (t1/1000)%60;
+ int f1 = (int)((m_size.cy==480?29.97:25)*(t1%1000)/1000);
+
+ int t2 = sp[i].stop;
+ int h2 = t2/1000/60/60, m2 = (t2/1000/60)%60, s2 = (t2/1000)%60;
+ int f2 = (int)((m_size.cy==480?29.97:25)*(t2%1000)/1000);
+
+ if(t2 <= 0) continue;
+ if(t1 < 0) t1 = 0;
+
+ if(h1 == h2 && m1 == m2 && s1 == s2 && f1 == f2)
+ {
+ f2++;
+ if(f2 == (m_size.cy == 480 ? 30 : 25))
+ {
+ f2 = 0;
+ s2++;
+ if(s2 == 60)
+ {
+ s2 = 0;
+ m2++;
+ if(m2 == 60)
+ {
+ m2 = 0;
+ h2++;
+ }
+ }
+ }
+ }
+
+ if(i < sp.GetCount()-1)
+ {
+ int t3 = sp[i+1].start;
+ int h3 = t3/1000/60/60, m3 = (t3/1000/60)%60, s3 = (t3/1000)%60;
+ int f3 = (int)((m_size.cy==480?29.97:25)*(t3%1000)/1000);
+
+ if(h3 == h2 && m3 == m2 && s3 == s2 && f3 == f2)
+ {
+ f2--;
+ if(f2 == -1)
+ {
+ f2 = (m_size.cy == 480 ? 29 : 24);
+ s2--;
+ if(s2 == -1)
+ {
+ s2 = 59;
+ m2--;
+ if(m2 == -1)
+ {
+ m2 = 59;
+ if(h2 > 0) h2--;
+ }
+ }
+ }
+ }
+ }
+
+ if(h1 == h2 && m1 == m2 && s1 == s2 && f1 == f2)
+ continue;
+
+ str.Format(_T("%04d\t%02d:%02d:%02d:%02d\t%02d:%02d:%02d:%02d\t%s\n"),
+ ++k,
+ h1, m1, s1, f1,
+ h2, m2, s2, f2,
+ title);
+ f.WriteString(str);
+
+ CFile bmp;
+ if(bmp.Open(bmpfn, CFile::modeCreate|CFile::modeWrite|CFile::typeBinary))
+ {
+ bmp.Write(&fhdr, sizeof(fhdr));
+ bmp.Write(&ihdr, sizeof(ihdr));
+ bmp.Write(newCusPal, sizeof(RGBQUAD)*16);
+ bmp.Write(p4bpp, 360*(m_size.cy-2));
+ bmp.Close();
+
+ CompressFile(bmpfn);
+ }
+ }
+
+ m_fCustomPal = fCustomPal;
+ memcpy(m_cuspal, tempCusPal, sizeof(m_cuspal));
+
+ return(true);
+}
+
+//
+// CVobSubStream
+//
+
+CVobSubStream::CVobSubStream(CCritSec* pLock)
+ : CSubPicProviderImpl(pLock)
+{
+}
+
+CVobSubStream::~CVobSubStream()
+{
+}
+
+void CVobSubStream::Open(CString name, BYTE* pData, int len)
+{
+ CAutoLock cAutoLock(&m_csSubPics);
+
+ m_name = name;
+
+ CAtlList<CString> lines;
+ Explode(CString(CStringA((CHAR*)pData, len)), lines, '\n');
+ while(lines.GetCount())
+ {
+ CAtlList<CString> sl;
+ Explode(lines.RemoveHead(), sl, ':', 2);
+ if(sl.GetCount() != 2) continue;
+ CString key = sl.GetHead();
+ CString value = sl.GetTail();
+ if(key == _T("size"))
+ _stscanf(value, _T("%dx %d"), &m_size.cx, &m_size.cy);
+ else if(key == _T("org"))
+ _stscanf(value, _T("%d, %d"), &m_org.x, &m_org.y);
+ else if(key == _T("scale"))
+ _stscanf(value, _T("%d%%, %d%%"), &m_scale_x, &m_scale_y);
+ else if(key == _T("alpha"))
+ _stscanf(value, _T("%d%%"), &m_alpha);
+ else if(key == _T("smooth"))
+ m_fSmooth =
+ value == _T("0") || value == _T("OFF") ? 0 :
+ value == _T("1") || value == _T("ON") ? 1 :
+ value == _T("2") || value == _T("OLD") ? 2 :
+ 0;
+ else if(key == _T("align"))
+ {
+ Explode(value, sl, ' ');
+ if(sl.GetCount() == 4) sl.RemoveAt(sl.FindIndex(1));
+ if(sl.GetCount() == 3)
+ {
+ m_fAlign = sl.RemoveHead() == _T("ON");
+ CString hor = sl.GetHead(), ver = sl.GetTail();
+ m_alignhor = hor == _T("LEFT") ? 0 : hor == _T("CENTER") ? 1 : hor == _T("RIGHT") ? 2 : 1;
+ m_alignver = ver == _T("TOP") ? 0 : ver == _T("CENTER") ? 1 : ver == _T("BOTTOM") ? 2 : 2;
+ }
+ }
+ else if(key == _T("fade in/out"))
+ _stscanf(value, _T("%d%, %d%"), &m_fadein, &m_fadeout);
+ else if(key == _T("time offset"))
+ m_toff = _tcstol(value, NULL, 10);
+ else if(key == _T("forced subs"))
+ m_fOnlyShowForcedSubs = value == _T("1") || value == _T("ON");
+ else if(key == _T("palette"))
+ {
+ Explode(value, sl, ',', 16);
+ for(ptrdiff_t i = 0; i < 16 && sl.GetCount(); i++)
+ *(DWORD*)&m_orgpal[i] = _tcstol(sl.RemoveHead(), NULL, 16);
+ }
+ else if(key == _T("custom colors"))
+ {
+ m_fCustomPal = Explode(value, sl, ',', 3) == _T("ON");
+ if(sl.GetCount() == 3)
+ {
+ sl.RemoveHead();
+ CAtlList<CString> tridx, colors;
+ Explode(sl.RemoveHead(), tridx, ':', 2);
+ if(tridx.RemoveHead() == _T("tridx"))
+ {
+ TCHAR tr[4];
+ _stscanf(tridx.RemoveHead(), _T("%c%c%c%c"), &tr[0], &tr[1], &tr[2], &tr[3]);
+ for(ptrdiff_t i = 0; i < 4; i++)
+ m_tridx |= ((tr[i]=='1')?1:0)<<i;
+ }
+ Explode(sl.RemoveHead(), colors, ':', 2);
+ if(colors.RemoveHead() == _T("colors"))
+ {
+ Explode(colors.RemoveHead(), colors, ',', 4);
+ for(ptrdiff_t i = 0; i < 4 && colors.GetCount(); i++)
+ *(DWORD*)&m_cuspal[i] = _tcstol(colors.RemoveHead(), NULL, 16);
+ }
+ }
+ }
+ }
+}
+
+void CVobSubStream::Add(REFERENCE_TIME tStart, REFERENCE_TIME tStop, BYTE* pData, int len)
+{
+ if(len <= 4 || ((pData[0]<<8)|pData[1]) != len) return;
+
+ CVobSubImage vsi;
+ vsi.GetPacketInfo(pData, (pData[0]<<8)|pData[1], (pData[2]<<8)|pData[3]);
+
+ CAutoPtr<SubPic> p(DNew SubPic());
+ p->tStart = tStart;
+ p->tStop = vsi.delay > 0 ? (tStart + 10000i64*vsi.delay) : tStop;
+ p->pData.SetCount(len);
+ memcpy(p->pData.GetData(), pData, p->pData.GetCount());
+
+ CAutoLock cAutoLock(&m_csSubPics);
+ while(m_subpics.GetCount() && m_subpics.GetTail()->tStart >= tStart)
+ {
+ m_subpics.RemoveTail();
+ m_img.iIdx = -1;
+ }
+ m_subpics.AddTail(p);
+}
+
+void CVobSubStream::RemoveAll()
+{
+ CAutoLock cAutoLock(&m_csSubPics);
+ m_subpics.RemoveAll();
+ m_img.iIdx = -1;
+}
+
+STDMETHODIMP CVobSubStream::NonDelegatingQueryInterface(REFIID riid, void** ppv)
+{
+ CheckPointer(ppv, E_POINTER);
+ *ppv = NULL;
+
+ return
+ QI(IPersist)
+ QI(ISubStream)
+ QI(ISubPicProvider)
+ __super::NonDelegatingQueryInterface(riid, ppv);
+}
+
+// ISubPicProvider
+
+STDMETHODIMP_(POSITION) CVobSubStream::GetStartPosition(REFERENCE_TIME rt, double fps)
+{
+ CAutoLock cAutoLock(&m_csSubPics);
+ POSITION pos = m_subpics.GetTailPosition();
+ for(; pos; m_subpics.GetPrev(pos))
+ {
+ SubPic* sp = m_subpics.GetAt(pos);
+ if(sp->tStart <= rt)
+ {
+ if(sp->tStop <= rt) m_subpics.GetNext(pos);
+ break;
+ }
+ }
+ return(pos);
+}
+
+STDMETHODIMP_(POSITION) CVobSubStream::GetNext(POSITION pos)
+{
+ CAutoLock cAutoLock(&m_csSubPics);
+ m_subpics.GetNext(pos);
+ return pos;
+}
+
+STDMETHODIMP_(REFERENCE_TIME) CVobSubStream::GetStart(POSITION pos, double fps)
+{
+ CAutoLock cAutoLock(&m_csSubPics);
+ return m_subpics.GetAt(pos)->tStart;
+}
+
+STDMETHODIMP_(REFERENCE_TIME) CVobSubStream::GetStop(POSITION pos, double fps)
+{
+ CAutoLock cAutoLock(&m_csSubPics);
+ return m_subpics.GetAt(pos)->tStop;
+}
+
+STDMETHODIMP_(bool) CVobSubStream::IsAnimated(POSITION pos)
+{
+ return(false);
+}
+
+STDMETHODIMP CVobSubStream::Render(SubPicDesc& spd, REFERENCE_TIME rt, double fps, RECT& bbox)
+{
+ if(spd.bpp != 32) return E_INVALIDARG;
+
+ POSITION pos = m_subpics.GetTailPosition();
+ for(; pos; m_subpics.GetPrev(pos))
+ {
+ SubPic* sp = m_subpics.GetAt(pos);
+ if(sp->tStart <= rt && rt < sp->tStop)
+ {
+ if(m_img.iIdx != (int)pos)
+ {
+ BYTE* pData = sp->pData.GetData();
+ m_img.Decode(
+ pData, (pData[0]<<8)|pData[1], (pData[2]<<8)|pData[3],
+ m_fCustomPal, m_tridx, m_orgpal, m_cuspal, true);
+ m_img.iIdx = (int)pos;
+ }
+
+ return __super::Render(spd, bbox);
+ }
+ }
+
+ return E_FAIL;
+}
+
+// IPersist
+
+STDMETHODIMP CVobSubStream::GetClassID(CLSID* pClassID)
+{
+ return pClassID ? *pClassID = __uuidof(this), S_OK : E_POINTER;
+}
+
+// ISubStream
+
+STDMETHODIMP_(int) CVobSubStream::GetStreamCount()
+{
+ return 1;
+}
+
+STDMETHODIMP CVobSubStream::GetStreamInfo(int i, WCHAR** ppName, LCID* pLCID)
+{
+ CAutoLock cAutoLock(&m_csSubPics);
+
+ if(ppName)
+ {
+ *ppName = (WCHAR*)CoTaskMemAlloc((m_name.GetLength() + 1) * sizeof(WCHAR));
+ if(!(*ppName))
+ return E_OUTOFMEMORY;
+ wcscpy(*ppName, CStringW(m_name));
+ }
+
+ if(pLCID)
+ {
+ *pLCID = 0; // TODO
+ }
+
+ return S_OK;
+}
+
+STDMETHODIMP_(int) CVobSubStream::GetStream()
+{
+ return 0;
+}
+
+STDMETHODIMP CVobSubStream::SetStream(int iStream)
+{
+ return iStream == 0 ? S_OK : E_FAIL;
+}
diff --git a/src/Subtitles/VobSubFile.h b/src/Subtitles/VobSubFile.h
new file mode 100644
index 000000000..6ffe5feae
--- /dev/null
+++ b/src/Subtitles/VobSubFile.h
@@ -0,0 +1,184 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <atlcoll.h>
+#include "VobSubImage.h"
+#include "../SubPic/SubPicProviderImpl.h"
+
+#define VOBSUBIDXVER 7
+
+extern CString FindLangFromId(WORD id);
+
+class CVobSubSettings
+{
+protected:
+ HRESULT Render(SubPicDesc& spd, RECT& bbox);
+
+public:
+ CSize m_size;
+ int m_x, m_y;
+ CPoint m_org;
+ int m_scale_x, m_scale_y; // % (don't set it to unsigned because as a side effect it will mess up negative coordinates in GetDestrect())
+ int m_alpha; // %
+ int m_fSmooth; // 0: OFF, 1: ON, 2: OLD (means no filtering at all)
+ int m_fadein, m_fadeout; // ms
+ bool m_fAlign;
+ int m_alignhor, m_alignver; // 0: left/top, 1: center, 2: right/bottom
+ unsigned int m_toff; // ms
+ bool m_fOnlyShowForcedSubs;
+ bool m_fCustomPal;
+ int m_tridx;
+ RGBQUAD m_orgpal[16], m_cuspal[4];
+
+ CVobSubImage m_img;
+
+ CVobSubSettings() {InitSettings();}
+ void InitSettings();
+
+ bool GetCustomPal(RGBQUAD* cuspal, int& tridx);
+ void SetCustomPal(RGBQUAD* cuspal, int tridx);
+
+ void GetDestrect(CRect& r); // destrect of m_img, considering the current alignment mode
+ void GetDestrect(CRect& r, int w, int h); // this will scale it to the frame size of (w, h)
+
+ void SetAlignment(bool fAlign, int x, int y, int hor, int ver);
+};
+
+class __declspec(uuid("998D4C9A-460F-4de6-BDCD-35AB24F94ADF"))
+CVobSubFile : public CVobSubSettings, public ISubStream, public CSubPicProviderImpl
+{
+protected:
+ CString m_title;
+
+ void TrimExtension(CString& fn);
+ bool ReadIdx(CString fn, int& ver), ReadSub(CString fn), ReadRar(CString fn), ReadIfo(CString fn);
+ bool WriteIdx(CString fn), WriteSub(CString fn);
+
+ CMemFile m_sub;
+
+ BYTE* GetPacket(int idx, int& packetsize, int& datasize, int iLang = -1);
+ bool GetFrame(int idx, int iLang = -1);
+ bool GetFrameByTimeStamp(__int64 time);
+ int GetFrameIdxByTimeStamp(__int64 time);
+
+ bool SaveVobSub(CString fn);
+ bool SaveWinSubMux(CString fn);
+ bool SaveScenarist(CString fn);
+ bool SaveMaestro(CString fn);
+
+public:
+ typedef struct
+ {
+ __int64 filepos;
+ __int64 start, stop;
+ bool fForced;
+ char vobid, cellid;
+ __int64 celltimestamp;
+ bool fValid;
+ } SubPos;
+
+ typedef struct
+ {
+ int id;
+ CString name, alt;
+ CAtlArray<SubPos> subpos;
+ } SubLang;
+
+ int m_iLang;
+ SubLang m_langs[32];
+
+public:
+ CVobSubFile(CCritSec* pLock);
+ virtual ~CVobSubFile();
+
+ bool Copy(CVobSubFile& vsf);
+
+ typedef enum {None,VobSub,WinSubMux,Scenarist,Maestro} SubFormat;
+
+ bool Open(CString fn);
+ bool Save(CString fn, SubFormat sf = VobSub);
+ void Close();
+
+ CString GetTitle() {return(m_title);}
+
+ DECLARE_IUNKNOWN
+ STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void** ppv);
+
+ // ISubPicProvider
+ STDMETHODIMP_(POSITION) GetStartPosition(REFERENCE_TIME rt, double fps);
+ STDMETHODIMP_(POSITION) GetNext(POSITION pos);
+ STDMETHODIMP_(REFERENCE_TIME) GetStart(POSITION pos, double fps);
+ STDMETHODIMP_(REFERENCE_TIME) GetStop(POSITION pos, double fps);
+ STDMETHODIMP_(bool) IsAnimated(POSITION pos);
+ STDMETHODIMP Render(SubPicDesc& spd, REFERENCE_TIME rt, double fps, RECT& bbox);
+
+ // IPersist
+ STDMETHODIMP GetClassID(CLSID* pClassID);
+
+ // ISubStream
+ STDMETHODIMP_(int) GetStreamCount();
+ STDMETHODIMP GetStreamInfo(int i, WCHAR** ppName, LCID* pLCID);
+ STDMETHODIMP_(int) GetStream();
+ STDMETHODIMP SetStream(int iStream);
+ STDMETHODIMP Reload();
+};
+
+class __declspec(uuid("D7FBFB45-2D13-494F-9B3D-FFC9557D5C45"))
+CVobSubStream : public CVobSubSettings, public ISubStream, public CSubPicProviderImpl
+{
+ CString m_name;
+
+ CCritSec m_csSubPics;
+ struct SubPic {REFERENCE_TIME tStart, tStop; CAtlArray<BYTE> pData;};
+ CAutoPtrList<SubPic> m_subpics;
+
+public:
+ CVobSubStream(CCritSec* pLock);
+ virtual ~CVobSubStream();
+
+ void Open(CString name, BYTE* pData, int len);
+
+ void Add(REFERENCE_TIME tStart, REFERENCE_TIME tStop, BYTE* pData, int len);
+ void RemoveAll();
+
+ DECLARE_IUNKNOWN
+ STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void** ppv);
+
+ // ISubPicProvider
+ STDMETHODIMP_(POSITION) GetStartPosition(REFERENCE_TIME rt, double fps);
+ STDMETHODIMP_(POSITION) GetNext(POSITION pos);
+ STDMETHODIMP_(REFERENCE_TIME) GetStart(POSITION pos, double fps);
+ STDMETHODIMP_(REFERENCE_TIME) GetStop(POSITION pos, double fps);
+ STDMETHODIMP_(bool) IsAnimated(POSITION pos);
+ STDMETHODIMP Render(SubPicDesc& spd, REFERENCE_TIME rt, double fps, RECT& bbox);
+
+ // IPersist
+ STDMETHODIMP GetClassID(CLSID* pClassID);
+
+ // ISubStream
+ STDMETHODIMP_(int) GetStreamCount();
+ STDMETHODIMP GetStreamInfo(int i, WCHAR** ppName, LCID* pLCID);
+ STDMETHODIMP_(int) GetStream();
+ STDMETHODIMP SetStream(int iStream);
+ STDMETHODIMP Reload() {return E_NOTIMPL;}
+};
diff --git a/src/Subtitles/VobSubFileRipper.cpp b/src/Subtitles/VobSubFileRipper.cpp
new file mode 100644
index 000000000..946484618
--- /dev/null
+++ b/src/Subtitles/VobSubFileRipper.cpp
@@ -0,0 +1,1259 @@
+/*
+ * 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 "vobsubfileripper.h"
+#include "../decss/VobDec.h"
+#include "../subtitles/CCDecoder.h"
+
+//
+// CVobSubFileRipper
+//
+
+CVobSubFileRipper::CVobSubFileRipper()
+ : CVobSubFile(NULL)
+ , m_fThreadActive(false)
+ , m_fBreakThread(false)
+ , m_fIndexing(false)
+{
+ m_rd.Reset();
+ CAMThread::Create();
+}
+
+CVobSubFileRipper::~CVobSubFileRipper()
+{
+ CAMThread::CallWorker(CMD_EXIT);
+ CAMThread::Close();
+}
+
+STDMETHODIMP CVobSubFileRipper::NonDelegatingQueryInterface(REFIID riid, void** ppv)
+{
+ return
+ QI(IVSFRipper)
+ __super::NonDelegatingQueryInterface(riid, ppv);
+}
+
+void CVobSubFileRipper::Log(log_t type, LPCTSTR lpszFormat, ...)
+{
+ CAutoLock cAutoLock(&m_csCallback);
+ if(!m_pCallback) return;
+
+ TCHAR buff[1024];
+
+ va_list args;
+ va_start(args, lpszFormat);
+ _vstprintf(buff, lpszFormat, args);
+ va_end(args);
+
+ CString msg;
+ switch(type)
+ {
+ default:
+ case LOG_INFO: msg = _T(""); break;
+ case LOG_WARNING: msg = _T("WARNING: "); break;
+ case LOG_ERROR: msg = _T("ERROR: "); break;
+ }
+
+ msg += buff;
+
+ m_pCallback->OnMessage(msg);
+}
+
+void CVobSubFileRipper::Progress(double progress)
+{
+ CAutoLock cAutoLock(&m_csCallback);
+ if(!m_pCallback) return;
+
+ m_pCallback->OnProgress(progress);
+}
+
+void CVobSubFileRipper::Finished(bool fSucceeded)
+{
+ CAutoLock cAutoLock(&m_csCallback);
+ if(!m_pCallback) return;
+
+ m_pCallback->OnFinished(fSucceeded);
+}
+
+#define ReadBEb(var) \
+ f.Read(&((BYTE*)&var)[0], 1); \
+
+#define ReadBEw(var) \
+ f.Read(&((BYTE*)&var)[1], 1); \
+ f.Read(&((BYTE*)&var)[0], 1); \
+
+#define ReadBEdw(var) \
+ f.Read(&((BYTE*)&var)[3], 1); \
+ f.Read(&((BYTE*)&var)[2], 1); \
+ f.Read(&((BYTE*)&var)[1], 1); \
+ f.Read(&((BYTE*)&var)[0], 1); \
+
+bool CVobSubFileRipper::LoadIfo(CString fn)
+{
+ CString str;
+
+ CFileStatus status;
+ if(!CFileGetStatus(fn, status) || !status.m_size)
+ {
+ Log(LOG_ERROR, _T("Invalid ifo"));
+ return(false);
+ }
+
+ CFile f;
+ if(!f.Open(fn, CFile::modeRead|CFile::typeBinary|CFile::shareDenyNone))
+ {
+ Log(LOG_ERROR, _T("Cannot open ifo"));
+ return(false);
+ }
+
+ Log(LOG_INFO, _T("Opening ifo OK"));
+
+ char hdr[13];
+ f.Read(hdr, 12);
+ hdr[12] = 0;
+ if(strcmp(hdr, "DVDVIDEO-VTS"))
+ {
+ Log(LOG_ERROR, _T("Not a Video Title Set IFO file!"));
+ return(false);
+ }
+
+ // lang ids
+
+ f.Seek(0x254, CFile::begin);
+
+ WORD ids[32];
+ memset(ids, 0, sizeof(ids));
+
+ int len = 0;
+ ReadBEw(len);
+
+ for(ptrdiff_t i = 0; i < len; i++)
+ {
+ f.Seek(2, CFile::current); // 01 00 ?
+ ReadBEw(ids[i]);
+ if(ids[i] == 0) ids[i] = '--';
+ f.Seek(2, CFile::current); // 00 00 ?
+ }
+
+ /* Video info */
+
+ f.Seek(0x200, CFile::begin);
+ f.Read(&m_rd.vidinfo, 2);
+
+ SIZE res[4][2] =
+ {
+ {{720,480},{720,576}},
+ {{704,480},{704,576}},
+ {{352,480},{352,576}},
+ {{352,240},{352,288}}
+ };
+
+ m_rd.vidsize = res[m_rd.vidinfo.source_res][m_rd.vidinfo.system&1];
+
+ double rate = (m_rd.vidinfo.system == 0) ? 30.0/29.97 : 1.0;
+
+ /* PGCs */
+
+ {
+ DWORD offset;
+
+ DWORD pgcpos;
+ f.Seek(0xc0+0x0c, CFile::begin);
+ ReadBEdw(pgcpos);
+ pgcpos *= 0x800;
+
+ WORD nPGC;
+ f.Seek(pgcpos, CFile::begin);
+ ReadBEw(nPGC);
+
+ m_rd.pgcs.RemoveAll();
+ m_rd.pgcs.SetCount(nPGC);
+
+ for(ptrdiff_t i = 0; i < nPGC; i++)
+ {
+ PGC& pgc = m_rd.pgcs[i];
+
+ f.Seek(pgcpos + 8 + i*8 + 4, CFile::begin);
+ ReadBEdw(offset);
+ offset += pgcpos;
+
+ BYTE nProgs, nCells;
+ f.Seek(offset + 2, CFile::begin);
+ ReadBEb(nProgs);
+ ReadBEb(nCells);
+
+ //
+
+ memcpy(pgc.ids, ids, sizeof(ids));
+
+ struct splanginfo {BYTE res1, id1, id2, res2;};
+ splanginfo splinfo[32];
+
+ f.Seek(offset + 0x1c, CFile::begin);
+ f.Read(splinfo, 32*4);
+
+ for(ptrdiff_t j = 0; j < 32; j++)
+ {
+ if(splinfo[j].id1 || splinfo[i].id2)
+ {
+ WORD tmpids[32];
+ memset(tmpids, 0, sizeof(tmpids));
+
+ for(j = 0; j < 32; j++)
+ {
+ if(!(splinfo[j].res1 & 0x80)) break;
+
+ pgc.ids[splinfo[j].id1] = ids[j];
+ pgc.ids[splinfo[j].id2] = ids[j];
+ }
+
+ break;
+ }
+ }
+
+ //
+
+ f.Seek(offset + 0xa4, CFile::begin);
+
+ for(ptrdiff_t j = 0; j < 16; j++)
+ {
+ BYTE y, u, v, tmp;
+
+ f.Read(&tmp, 1);
+ f.Read(&y, 1);
+ f.Read(&u, 1);
+ f.Read(&v, 1);
+
+ y = (y-16)*255/219;
+
+ pgc.pal[j].rgbRed = (BYTE)min(max(1.0*y + 1.4022*(u-128), 0), 255);
+ pgc.pal[j].rgbGreen = (BYTE)min(max(1.0*y - 0.3456*(u-128) - 0.7145*(v-128), 0), 255);
+ pgc.pal[j].rgbBlue = (BYTE)min(max(1.0*y + 1.7710*(v-128), 0) , 255);
+ }
+
+ //
+
+ WORD progoff, celladdroff, vobcelloff;
+ f.Seek(offset + 0xe6, CFile::begin);
+ ReadBEw(progoff);
+ f.Seek(offset + 0xe8, CFile::begin);
+ ReadBEw(celladdroff);
+ f.Seek(offset + 0xea, CFile::begin);
+ ReadBEw(vobcelloff);
+
+ //
+
+ CAtlArray<BYTE> progs;
+ progs.SetCount(nProgs);
+ f.Seek(offset + progoff, CFile::begin);
+ f.Read(progs.GetData(), nProgs);
+
+ //
+
+ pgc.angles[0].SetCount(nCells);
+ pgc.iSelAngle = 0;
+
+ //
+
+ f.Seek(offset + vobcelloff, CFile::begin);
+ for(ptrdiff_t j = 0; j < nCells; j++)
+ {
+ ReadBEw(pgc.angles[0][j].vob);
+ ReadBEw(pgc.angles[0][j].cell);
+ }
+
+ //
+
+ DWORD tOffset = 0, tTotal = 0;
+
+ int iAngle = 0;
+
+ pgc.nAngles = 0;
+
+ f.Seek(offset + celladdroff, CFile::begin);
+ for(ptrdiff_t j = 0; j < nCells; j++)
+ {
+ BYTE b;
+ ReadBEb(b);
+ switch(b>>6)
+ {
+ case 0: iAngle = 0; break; // normal
+ case 1: iAngle = 1; break; // first angle block
+ case 2: iAngle++; break; // middle angle block
+ case 3: iAngle++; break; // last angle block (no more should follow)
+ }
+ pgc.angles[0][j].iAngle = iAngle;
+ pgc.nAngles = max(pgc.nAngles, iAngle);
+
+ f.Seek(3, CFile::current);
+ ReadBEdw(pgc.angles[0][j].tTime);
+ ReadBEdw(pgc.angles[0][j].start);
+ f.Seek(8, CFile::current);
+ ReadBEdw(pgc.angles[0][j].end);
+
+ float fps;
+ switch((pgc.angles[0][j].tTime>>6)&0x3)
+ {
+ default:
+ case 3: fps = 30; break;
+ case 1: fps = 25; break;
+ }
+
+ int t = pgc.angles[0][j].tTime;
+ int hh = ((t>>28)&0xf)*10+((t>>24)&0xf);
+ int mm = ((t>>20)&0xf)*10+((t>>16)&0xf);
+ int ss = ((t>>12)&0xf)*10+((t>>8)&0xf);
+ int ms = (int)(1000.0 * (((t>>4)&0x3)*10+((t>>0)&0xf)) / fps);
+ pgc.angles[0][j].tTime = (DWORD)((((hh*60+mm)*60+ss)*1000+ms)*rate);
+
+ // time discontinuity
+ if(b&0x02) tOffset = tTotal;
+ pgc.angles[0][j].fDiscontinuity = !!(b&0x02);
+
+ pgc.angles[0][j].tTotal = tTotal;
+ pgc.angles[0][j].tOffset = tOffset;
+
+ tTotal += pgc.angles[0][j].tTime;
+ }
+
+ for(iAngle = 1; iAngle <= 9; iAngle++)
+ {
+ tOffset = tTotal = 0;
+
+ for(ptrdiff_t j = 0, k = 0; j < nCells; j++)
+ {
+ if(pgc.angles[0][j].iAngle != 0
+ && pgc.angles[0][j].iAngle != iAngle)
+ continue;
+
+ pgc.angles[iAngle].Add(pgc.angles[0][j]);
+
+ if(pgc.angles[iAngle][k].fDiscontinuity) tOffset = tTotal;
+
+ pgc.angles[iAngle][k].tTotal = tTotal;
+ pgc.angles[iAngle][k].tOffset = tOffset;
+
+ tTotal += pgc.angles[iAngle][k].tTime;
+
+ k++;
+ }
+ }
+ }
+ }
+
+ Log(LOG_INFO, _T("Parsing ifo OK"));
+
+ return(true);
+}
+
+bool CVobSubFileRipper::LoadVob(CString fn)
+{
+ Log(LOG_INFO, _T("Searching vobs..."));
+/*
+ CAtlList<CString> m_vobs;
+
+ fn = fn.Left(fn.ReverseFind('.')+1);
+ fn.TrimRight(_T(".0123456789"));
+ for(ptrdiff_t i = 0; i < 100; i++)
+ {
+ CString vob;
+ vob.Format(_T("%s%d.vob"), fn, i);
+
+ CFileStatus status;
+ if(!(CFileGetStatus(vob, status) && status.m_size))
+ {
+ if(i > 0) break;
+ else continue;
+ }
+
+ if(status.m_size&0x7ff)
+ {
+ Log(LOG_ERROR, _T("Length of %s is not n*2048!"), vob);
+ m_vobs.RemoveAll();
+ break;
+ }
+
+ CString str = _T("Found ") + vob;
+
+ if(i == 0)
+ {
+ str += _T(" (skipping, if not a menu vob rename it)");
+ }
+ else
+ {
+ m_vobs.AddTail(vob);
+ }
+
+ Log(LOG_INFO, str);
+ }
+
+ if(m_vobs.GetCount() <= 0)
+ {
+ Log(LOG_ERROR, _T("Nothing found! (%s*.vob)"), fn);
+ return(false);
+ }
+*/
+ CAtlList<CString> vobs;
+ if(!m_vob.Open(fn, vobs/*m_vobs*/))
+ {
+ Log(LOG_ERROR, _T("Cannot open vob sequence"));
+ return(false);
+ }
+
+ if(vobs.GetCount() <= 0)
+ {
+ Log(LOG_ERROR, _T("Nothing found! (%s*.vob)"), fn);
+ return(false);
+ }
+
+ POSITION pos = vobs.GetHeadPosition();
+ while(pos) Log(LOG_INFO, _T("Found ") + vobs.GetNext(pos));
+
+ if(m_vob.IsDVD())
+ {
+ Log(LOG_INFO, _T("DVD detected..."));
+
+ BYTE key[5];
+
+ if(m_vob.HasDiscKey(key))
+ Log(LOG_INFO, _T("Disc key: %02x%02x%02x%02x%02x"), key[0], key[1], key[2], key[3], key[4]);
+ else
+ Log(LOG_WARNING, _T("Couldn't get the disc key"));
+
+ if(m_vob.HasTitleKey(key))
+ Log(LOG_INFO, _T("Title key: %02x%02x%02x%02x%02x"), key[0], key[1], key[2], key[3], key[4]);
+ else
+ Log(LOG_WARNING, _T("Couldn't get the title key"));
+
+ BYTE buff[2048];
+
+ m_vob.Seek(0);
+ if(!m_vob.Read(buff))
+ {
+ Log(LOG_ERROR, _T("Can't read vob, please unlock it with a software player!"));
+ return(false);
+ }
+ m_vob.Seek(0);
+ }
+
+ return(true);
+}
+
+DWORD CVobSubFileRipper::ThreadProc()
+{
+ SetThreadPriority(m_hThread, THREAD_PRIORITY_BELOW_NORMAL);
+
+ while(1)
+ {
+ DWORD cmd = GetRequest();
+
+ m_fThreadActive = true;
+
+ switch(cmd)
+ {
+ case CMD_EXIT:
+ Reply(S_OK);
+ return 0;
+
+ case CMD_INDEX:
+ Reply(S_OK);
+ {
+ m_fIndexing = true;
+ bool fSucceeded = Create();
+ m_fIndexing = false;
+ Finished(fSucceeded);
+ }
+ break;
+
+ default:
+ Reply((DWORD)E_FAIL);
+ return (DWORD)-1;
+ }
+
+ m_fBreakThread = false;
+ m_fThreadActive = false;
+ }
+
+ return 1;
+}
+
+static int SubPosSortProc(const void* e1, const void* e2)
+{
+ return((int)(((CVobSubFile::SubPos*)e1)->start - ((CVobSubFile::SubPos*)e2)->start));
+}
+
+bool CVobSubFileRipper::Create()
+{
+ CAutoLock cAutoLock(&m_csAccessLock);
+
+ if(m_rd.iSelPGC < 0 || m_rd.iSelPGC >= m_rd.pgcs.GetCount())
+ {
+ Log(LOG_ERROR, _T("Invalid program chain number (%d)!"), m_rd.iSelPGC);
+ return(false);
+ }
+
+ PGC& pgc = m_rd.pgcs[m_rd.iSelPGC];
+
+ if(pgc.iSelAngle < 0 || pgc.iSelAngle > 9 || pgc.angles[pgc.iSelAngle].GetCount() == 0)
+ {
+ Log(LOG_ERROR, _T("Invalid angle number (%d)!"), pgc.iSelAngle);
+ return(false);
+ }
+
+ CAtlArray<vc_t>& angle = pgc.angles[pgc.iSelAngle];
+
+ if(m_rd.selids.GetCount() == 0 && !m_rd.fClosedCaption)
+ {
+ Log(LOG_ERROR, _T("No valid stream set to be extacted!"));
+ return(false);
+ }
+
+ if(m_rd.selvcs.GetCount() == 0)
+ {
+ Log(LOG_ERROR, _T("No valid vob/cell id set to be extacted!"));
+ return(false);
+ }
+
+ Log(LOG_INFO, _T("Indexing..."));
+
+ // initalize CVobSubFile
+ CVobSubFile::Close();
+ InitSettings();
+ m_title = m_outfn;
+ m_size = m_rd.vidsize;
+ TrimExtension(m_title);
+ memcpy(m_orgpal, pgc.pal, sizeof(m_orgpal));
+ m_sub.SetLength(0);
+
+ CCDecoder ccdec(m_title + _T(".cc.srt"), m_title + _T(".cc.raw"));
+
+ CVobDec vd;
+
+ __int64 SCR, PTS = 0, tOffset = 0, tPrevOffset = 0, tTotal = 0, tStart = 0;
+ int vob = 0, cell = 0;
+ bool fDiscontinuity = false, fDiscontinuityFixApplied = false, fNavpackFound = false;
+
+ int PTSframeoffset = 0, minPTSframeoffset = 0;
+
+ if(m_rd.fResetTime)
+ {
+ for(size_t i = 0; i < angle.GetCount() && (UINT)((angle[i].vob<<16)|angle[i].cell) != m_rd.selvcs[0]; i++)
+ tStart += angle[i].tTime;
+
+ Log(LOG_INFO, _T("Counting timestamps from %I64dms (v%02dc%02d)"),
+ tStart, m_rd.selvcs[0]>>16, m_rd.selvcs[0]&0xffff);
+ }
+
+ CAtlMap<DWORD, int> selvcmap;
+ selvcmap.RemoveAll();
+ for(ptrdiff_t i = 0; i < m_rd.selvcs.GetCount(); i++)
+ selvcmap[m_rd.selvcs[i]] = 90000;
+
+ CAtlArray<vcchunk> chunks, foundchunks, loadedchunks;
+
+ if(m_vob.IsDVD())
+ {
+ Log(LOG_INFO, _T("Indexing mode: DVD"));
+
+ for(ptrdiff_t i = 0; i < angle.GetCount(); i++)
+ {
+ DWORD vc = (angle[i].vob<<16)|angle[i].cell;
+ if(!selvcmap.Lookup(vc))
+ continue;
+
+ vcchunk c = {2048i64*angle[i].start, 2048i64*angle[i].end+2048, vc};
+ chunks.Add(c);
+
+ Log(LOG_INFO, _T("Adding: 0x%x - 0x%x (lba) for vob %d cell %d"),
+ angle[i].start, angle[i].end, angle[i].vob, angle[i].cell);
+ }
+ }
+ else if(LoadChunks(loadedchunks))
+ {
+ Log(LOG_INFO, _T("Indexing mode: File"));
+
+ for(ptrdiff_t i = 0; i < loadedchunks.GetCount(); i++)
+ {
+ DWORD vcid = loadedchunks[i].vc;
+ if(!selvcmap.Lookup(vcid))
+ continue;
+
+ chunks.Add(loadedchunks[i]);
+ }
+
+ Log(LOG_INFO, _T(".chunk file loaded"));
+ }
+ else
+ {
+ Log(LOG_INFO, _T("Indexing mode: File"));
+
+ chunks.RemoveAll();
+ vcchunk c = {0, 2048i64*m_vob.GetLength(), 0};
+ chunks.Add(c);
+ }
+
+ __int64 sizedone = 0, sizetotal = 0;
+ for(ptrdiff_t i = 0; i < chunks.GetCount(); i++)
+ sizetotal += chunks[i].end - chunks[i].start;
+
+ for(ptrdiff_t i = 0; !m_fBreakThread && i < chunks.GetCount(); i++)
+ {
+ __int64 curpos = chunks[i].start, endpos = chunks[i].end;
+
+ vcchunk curchunk = {curpos, curpos, chunks[i].vc};
+
+ for(m_vob.Seek((int)(curpos/2048)); !m_fBreakThread && curpos < endpos; curpos += 2048, sizedone += 2048)
+ {
+ if(!(curpos&0x7ffff))
+ Progress(1.0 * sizedone / sizetotal);
+
+ static BYTE buff[2048];
+
+ if(!m_vob.Read(buff))
+ {
+ Log(LOG_ERROR, _T("Cannot read, either locked dvd or truncated/missing files!"));
+ return(false);
+ }
+
+ curchunk.end = curpos;
+
+ if(buff[0x14] & 0x30)
+ {
+ if(!vd.m_fFoundKey)
+ {
+ Log(LOG_INFO, _T("Encrypted sector found, searching key..."));
+
+ __int64 savepos = curpos;
+
+ m_vob.Seek(0);
+ for(__int64 pos = 0; !m_fBreakThread && pos < endpos; pos += 2048)
+ {
+ if(!m_vob.Read(buff))
+ {
+ Log(LOG_ERROR, _T("Cannot read, either locked dvd or truncated/missing files!"));
+ return(false);
+ }
+
+ if(vd.FindKey(buff))
+ break;
+ }
+
+ if(m_fBreakThread)
+ break;
+
+ if(!vd.m_fFoundKey)
+ {
+ Log(LOG_ERROR, _T("Key not found, can't decrypt!"));
+ return(false);
+ }
+
+ Log(LOG_INFO, _T("Key found, continuing extraction..."));
+
+ m_vob.Seek((int)((curpos = savepos)/2048));
+ m_vob.Read(buff);
+ }
+
+ vd.Decrypt(buff);
+ }
+
+ if(*((DWORD*)&buff[0]) != 0xba010000)
+ {
+ Log(LOG_WARNING, _T("Bad sector header at block %08d!"), (int)(curpos/2048));
+
+ if(AfxMessageBox(_T("Bad packet header found, do you want to continue?"), MB_YESNO) == IDNO)
+ {
+ Log(LOG_ERROR, _T("Terminated!"));
+ return(false);
+ }
+ }
+
+ SCR = (__int64(buff[0x04] & 0x38) >> 3) << 30
+ | __int64(buff[0x04] & 0x03) << 28
+ | __int64(buff[0x05]) << 20
+ | (__int64(buff[0x06] & 0xf8) >> 3) << 15
+ | __int64(buff[0x06] & 0x03) << 13
+ | __int64(buff[0x07]) << 5
+ | (__int64(buff[0x08] & 0xf8) >> 3) << 0;
+
+ bool hasPTS = false;
+
+ if((*(DWORD*)&buff[0x0e] == 0xe0010000 || *(DWORD*)&buff[0x0e] == 0xbd010000)
+ && buff[0x15] & 0x80)
+ {
+ PTS = (__int64)(buff[0x17] & 0x0e) << 29 // 32-30 (+marker)
+ | ((__int64)(buff[0x18]) << 22) // 29-22
+ | ((__int64)(buff[0x19] & 0xfe) << 14) // 21-15 (+marker)
+ | ((__int64)(buff[0x1a]) << 7) // 14-07
+ | ((__int64)(buff[0x1b]) >> 1); // 06-00 (+marker)
+
+ hasPTS = true;
+ }
+
+ if(*((DWORD*)&buff[0x0e]) == 0xbb010000)
+ {
+ fNavpackFound = true;
+
+ if(vob == buff[0x420] && cell == buff[0x422])
+ continue;
+
+ vob = buff[0x420];
+ cell = buff[0x422];
+
+ tOffset = tTotal = 0;
+
+ for(ptrdiff_t i = 0; i < angle.GetCount(); i++)
+ {
+ if(angle[i].vob == vob && angle[i].cell == cell)
+ {
+ tPrevOffset = tOffset;
+ tOffset = (__int64)angle[i].tOffset;
+ tTotal = (__int64)angle[i].tTotal;
+ fDiscontinuity = angle[i].fDiscontinuity;
+ fDiscontinuityFixApplied = false;
+ break;
+ }
+ }
+
+ if(curchunk.vc != (DWORD)((vob<<16)|cell))
+ {
+ if(curchunk.vc != 0) foundchunks.Add(curchunk);
+ curchunk.start = curchunk.end = curpos;
+ curchunk.vc = (vob<<16)|cell;
+ }
+
+ CString str, str2;
+ str.Format(_T("v%02d c%02d lba%08d"), vob, cell, (int)(curpos/2048));
+ UINT vcid = (vob<<16)|cell;
+ if(!selvcmap.Lookup(vcid, minPTSframeoffset)) str2 = _T(", skipping");
+ else str2.Format(_T(", total=%I64dms, off=%I64dms, corr=%I64dms, discont.:%d"),
+ tTotal, tOffset, -tStart, (int)fDiscontinuity);
+ Log(LOG_INFO, str + str2);
+ }
+
+ DWORD vcid = (vob<<16)|cell;
+ if(!selvcmap.Lookup(vcid, minPTSframeoffset))
+ continue;
+
+ if(hasPTS && fDiscontinuity && !fDiscontinuityFixApplied)
+ {
+ __int64 tDiff = tOffset - tPrevOffset;
+ if(tDiff > 0 && tDiff < (PTS/90+1000))
+ {
+ CString str;
+ str.Format(_T("False discontinuity detected, correcting time by %I64dms"), -tDiff);
+ Log(LOG_INFO, str);
+
+ tStart += tDiff;
+ }
+ fDiscontinuityFixApplied = true;
+ }
+
+ if(*(DWORD*)&buff[0x0e] == 0xe0010000)
+ {
+ if(fDiscontinuity)
+ {
+ if(PTS < minPTSframeoffset)
+ {
+ selvcmap[vcid] = PTSframeoffset = PTS;
+ }
+
+ fDiscontinuity = false;
+ }
+
+ if(m_rd.fClosedCaption)
+ ccdec.ExtractCC(buff, 2048, tOffset + ((PTS - PTSframeoffset) / 90) - tStart);
+ }
+ else if(*(DWORD*)&buff[0x0e] == 0xbd010000)
+ {
+ BYTE id = buff[0x17 + buff[0x16]], iLang = id&0x1f;
+
+ if((id & 0xe0) == 0x20 && m_rd.selids.Lookup(iLang))
+ {
+ if(hasPTS)
+ {
+ SubPos sb;
+ sb.filepos = m_sub.GetPosition();
+ sb.start = tOffset + ((PTS - PTSframeoffset) / 90) - tStart;
+ sb.vobid = (char)vob;
+ sb.cellid = (char)cell;
+ sb.celltimestamp = tTotal;
+ sb.fValid = true;
+ m_langs[iLang].subpos.Add(sb);
+ }
+
+ m_sub.Write(buff, 2048);
+ }
+ }
+ }
+
+ if(curchunk.vc != (DWORD)((vob<<16)|cell))
+ {
+ if(curchunk.vc != 0) foundchunks.Add(curchunk);
+ curchunk.start = curchunk.end = curpos;
+ curchunk.vc = (vob<<16)|cell;
+ }
+ }
+
+ if(sizedone < sizetotal)
+ {
+ Log(LOG_ERROR, _T("Indexing terminated before reaching the end!"));
+ Progress(0);
+ return(false);
+ }
+
+ if(!fNavpackFound)
+ {
+ Log(LOG_ERROR, _T("Could not find any system header start code! (0x000001bb)"));
+ if(!m_vob.IsDVD()) Log(LOG_ERROR, _T("Make sure the ripper doesn't strip these packets."));
+ Progress(0);
+ return(false);
+ }
+
+ Log(LOG_INFO, _T("Indexing finished"));
+ Progress(1);
+
+ for(ptrdiff_t i = 0; i < 32; i++)
+ {
+ if(m_iLang == -1 && m_langs[i].subpos.GetCount() > 0) m_iLang = i;
+ m_langs[i].id = pgc.ids[i];
+ m_langs[i].name = m_langs[i].alt = FindLangFromId(m_langs[i].id);
+
+ CAtlArray<SubPos>& sp = m_langs[i].subpos;
+ qsort(sp.GetData(), sp.GetCount(), sizeof(SubPos), SubPosSortProc);
+
+ if(m_rd.fForcedOnly)
+ {
+ Log(LOG_INFO, _T("Searching for forced subs..."));
+ Progress(0);
+
+ for(ptrdiff_t j = 0, len = sp.GetCount(); j < len; j++)
+ {
+ Progress(1.0 * j / len);
+
+ sp[j].fValid = false;
+ int packetsize = 0, datasize = 0;
+ if(BYTE* buff = GetPacket(j, packetsize, datasize, i))
+ {
+ m_img.GetPacketInfo(buff, packetsize, datasize);
+ sp[j].fValid = m_img.fForced;
+ delete [] buff;
+ }
+ }
+
+ Progress(1);
+ }
+ }
+
+ Log(LOG_INFO, _T("Saving files..."));
+
+ if(m_iLang != -1)
+ {
+ if(!Save(m_title))
+ {
+ Log(LOG_ERROR, _T("Could not save output files!"));
+ return(false);
+ }
+ }
+
+ Log(LOG_INFO, _T("Subtitles saved"));
+
+ if(!m_vob.IsDVD() && loadedchunks.GetCount() == 0)
+ {
+ if(SaveChunks(foundchunks))
+ {
+ Log(LOG_INFO, _T(".chunk file saved"));
+ }
+ }
+
+ Log(LOG_INFO, _T("Done!"));
+
+ return(true);
+}
+
+static const DWORD s_version = 1;
+
+bool CVobSubFileRipper::LoadChunks(CAtlArray<vcchunk>& chunks)
+{
+ CFile f;
+
+ CString fn = m_infn;
+ TrimExtension(fn);
+ fn += _T(".chunks");
+
+ DWORD chksum = 0, chunklen, version;
+ __int64 voblen = 0;
+
+ if(!f.Open(fn, CFile::modeRead|CFile::typeBinary|CFile::shareDenyNone))
+ return(false);
+ f.Read(&version, sizeof(version));
+ if(version == 1)
+ {
+ f.Read(&chksum, sizeof(chksum));
+ f.Read(&voblen, sizeof(voblen));
+ f.Read(&chunklen, sizeof(chunklen));
+ chunks.SetCount(chunklen);
+ f.Read(chunks.GetData(), sizeof(vcchunk)*chunks.GetCount());
+ }
+ f.Close();
+
+ if(voblen != m_vob.GetLength())
+ {
+ chunks.RemoveAll();
+ return(false);
+ }
+
+ if(!f.Open(m_infn, CFile::modeRead|CFile::typeBinary|CFile::shareDenyNone))
+ return(false);
+ DWORD dw, chksum2 = 0;
+ while(f.Read(&dw, sizeof(dw)) == sizeof(dw)) chksum2 += dw;
+ f.Close();
+
+ if(chksum != chksum2)
+ {
+ chunks.RemoveAll();
+ return(false);
+ }
+
+ return(true);
+}
+
+bool CVobSubFileRipper::SaveChunks(CAtlArray<vcchunk>& chunks)
+{
+ CFile f;
+
+ CString fn = m_infn;
+ TrimExtension(fn);
+ fn += _T(".chunks");
+
+ DWORD chksum = 0, chunklen = chunks.GetCount();
+ __int64 voblen = m_vob.GetLength();
+
+ if(!f.Open(m_infn, CFile::modeRead|CFile::typeBinary|CFile::shareDenyNone))
+ return(false);
+ DWORD dw;
+ while(f.Read(&dw, sizeof(dw)) == sizeof(dw)) chksum += dw;
+ f.Close();
+
+ if(!f.Open(fn, CFile::modeCreate|CFile::modeWrite|CFile::typeBinary|CFile::shareDenyWrite))
+ return(false);
+ f.Write(&s_version, sizeof(s_version));
+ f.Write(&chksum, sizeof(chksum));
+ f.Write(&voblen, sizeof(voblen));
+ f.Write(&chunklen, sizeof(chunklen));
+ f.Write(chunks.GetData(), sizeof(vcchunk)*chunklen);
+ f.Close();
+
+ return(true);
+}
+
+// IVSFRipper
+
+STDMETHODIMP CVobSubFileRipper::SetCallBack(IVSFRipperCallback* pCallback)
+{
+ CAutoLock cAutoLock(&m_csCallback);
+ m_pCallback = pCallback;
+ return S_OK;
+}
+
+STDMETHODIMP CVobSubFileRipper::LoadParamFile(CString fn)
+{
+ CAutoLock cAutoLock(&m_csAccessLock);
+
+ m_rd.Reset();
+
+ CStdioFile f;
+ if(!f.Open(fn, CFile::modeRead|CFile::typeText))
+ return E_FAIL;
+
+ TCHAR langid[256];
+
+ enum {P_INPUT, P_OUTPUT, P_PGC, P_ANGLE, P_LANGS, P_OPTIONS};
+ int phase = P_INPUT;
+
+ CString line;
+ while(f.ReadString(line))
+ {
+ if(line.Trim().IsEmpty() || line[0] == '#') continue;
+
+ if(phase == P_INPUT)
+ {
+ if(S_OK != SetInput(line)) break;
+ phase = P_OUTPUT;
+ }
+ else if(phase == P_OUTPUT)
+ {
+ if(S_OK != SetOutput(line)) break;
+ phase = P_PGC;
+ }
+ else if(phase == P_PGC)
+ {
+ m_rd.iSelPGC = _tcstol(line, NULL, 10)-1;
+ if(m_rd.iSelPGC < 0 || m_rd.iSelPGC >= m_rd.pgcs.GetCount()) break;
+ phase = P_ANGLE;
+ }
+ else if(phase == 3)
+ {
+ PGC& pgc = m_rd.pgcs[m_rd.iSelPGC];
+
+ pgc.iSelAngle = _tcstol(line, NULL, 10);
+ if(pgc.iSelAngle < 0 || pgc.iSelAngle > max(1, pgc.nAngles) || pgc.iSelAngle > 9) break;
+
+ CAtlArray<vc_t>& angle = pgc.angles[pgc.iSelAngle];
+
+ if(line.Find('v') >= 0)
+ {
+ int vob = 0, cell = 0;
+
+ line += ' ';
+
+ TCHAR* s = (LPTSTR)(LPCTSTR)line;
+ TCHAR* e = s + line.GetLength();
+ while(s < e)
+ {
+ if(*s == 'v' || s == e-1)
+ {
+ s++;
+ if(vob != 0 && cell == 0)
+ {
+ for(ptrdiff_t i = 0; i < angle.GetCount(); i++)
+ {
+ if(angle[i].vob == vob)
+ m_rd.selvcs.Add((angle[i].vob<<16)|angle[i].cell);
+ }
+ }
+
+ vob = _tcstol(s, &s, 10);
+ cell = 0;
+ }
+ else if(*s == 'c' && vob > 0)
+ {
+ s++;
+ cell = _tcstol(s, &s, 10);
+
+ for(ptrdiff_t i = 0; i < angle.GetCount(); i++)
+ {
+ if(angle[i].vob == vob && angle[i].cell == cell)
+ {
+ m_rd.selvcs.Add((vob<<16)|cell);
+ break;
+ }
+ }
+ }
+ else
+ {
+ s++;
+ }
+ }
+ }
+ else
+ {
+ for(ptrdiff_t i = 0; i < angle.GetCount(); i++)
+ m_rd.selvcs.Add((angle[i].vob<<16)|angle[i].cell);
+ }
+
+ phase = P_LANGS;
+ }
+ else if(phase == 4)
+ {
+ if(!line.CompareNoCase(_T("ALL")))
+ {
+ for(ptrdiff_t i = 0; i < 32; i++) m_rd.selids[i] = true;
+ m_rd.fClosedCaption = true;
+ phase = P_OPTIONS;
+ }
+ else
+ {
+ line += ' ';
+
+ while(line.GetLength() > 0)
+ {
+ int n = line.Find(_T(" "));
+
+ CString lang = line.Left(n);
+
+ line = line.Mid(n);
+ line.TrimLeft();
+
+ n = 0;
+
+ int langnum;
+
+ if(_istdigit(lang[0]))
+ {
+ n = _stscanf(lang, _T("%d"), &langnum);
+ if(n != 1) break;
+
+ m_rd.selids[langnum] = true;
+ }
+ else if(_istalpha(lang[0]))
+ {
+ n = _stscanf(lang, _T("%s"), langid);
+ if(n != 1) break;
+
+ int id = (langid[0] << 8) + langid[1];
+
+ if(id == 'cc')
+ {
+ m_rd.fClosedCaption = true;
+ }
+ else
+ {
+ m_rd.selids[id] = true;
+ }
+ }
+ else break;
+
+ if(n != 1) break;
+ }
+
+ if((m_rd.selids.GetCount() > 0 || m_rd.fClosedCaption) && line.IsEmpty())
+ phase = P_OPTIONS;
+ }
+ }
+ else if(phase == 5 && !line.CompareNoCase(_T("CLOSE")))
+ m_rd.fClose = true;
+ else if(phase == 5 && !line.CompareNoCase(_T("BEEP")))
+ m_rd.fBeep = true;
+ else if(phase == 5 && !line.CompareNoCase(_T("RESETTIME")))
+ m_rd.fResetTime = true;
+ else if(phase == 5 && !line.CompareNoCase(_T("FORCEDONLY")))
+ m_rd.fForcedOnly = true;
+ else if(phase == 5 && !line.CompareNoCase(_T("CLOSEIGNOREERRORS")))
+ m_rd.fCloseIgnoreError = true;
+
+ }
+
+ m_rd.fAuto = true;
+
+ return phase == P_OPTIONS ? S_OK : E_FAIL;
+}
+
+STDMETHODIMP CVobSubFileRipper::SetInput(CString infn)
+{
+ CAutoLock cAutoLock(&m_csAccessLock);
+
+ m_rd.Reset();
+
+ if(!LoadIfo(infn) || !LoadVob(infn))
+ return E_INVALIDARG;
+
+ m_infn = infn;
+
+ return S_OK;
+}
+
+STDMETHODIMP CVobSubFileRipper::SetOutput(CString outfn)
+{
+ CAutoLock cAutoLock(&m_csAccessLock);
+ m_outfn = outfn;
+ return S_OK;
+}
+
+STDMETHODIMP CVobSubFileRipper::GetRipperData(VSFRipperData& rd)
+{
+ CAutoLock cAutoLock(&m_csAccessLock);
+ rd.Copy(m_rd);
+ return S_OK;
+}
+
+STDMETHODIMP CVobSubFileRipper::UpdateRipperData(VSFRipperData& rd)
+{
+ CAutoLock cAutoLock(&m_csAccessLock);
+ m_rd.Copy(rd);
+ return S_OK;
+}
+
+STDMETHODIMP CVobSubFileRipper::Index()
+{
+ if(m_fIndexing) return E_FAIL;
+ CAMThread::CallWorker(CMD_INDEX);
+ return S_OK;
+}
+
+
+STDMETHODIMP CVobSubFileRipper::IsIndexing()
+{
+ return m_fIndexing ? S_OK : S_FALSE;
+}
+
+STDMETHODIMP CVobSubFileRipper::Abort(bool fSavePartial)
+{
+ m_fBreakThread = true;
+ return S_OK;
+}
+
+//
+
+void VSFRipperData::Reset()
+{
+ vidsize.SetSize(0,0);
+ memset(&vidinfo, 0, sizeof(vidinfo));
+ pgcs.RemoveAll();
+ iSelPGC = -1;
+ fResetTime = fClosedCaption = true;
+ fForcedOnly = false;
+ fClose = fBeep = fAuto = false;
+ fCloseIgnoreError = false;
+
+ selvcs.RemoveAll();
+ selids.RemoveAll();
+}
+
+void VSFRipperData::Copy(VSFRipperData& rd)
+{
+ Reset();
+
+ vidsize = rd.vidsize;
+ vidinfo = rd.vidinfo;
+ if(int len = rd.pgcs.GetCount())
+ {
+ pgcs.SetCount(len);
+ for(ptrdiff_t i = 0; i < len; i++)
+ {
+ PGC& src = rd.pgcs[i];
+ PGC& dst = pgcs[i];
+ dst.nAngles = src.nAngles;
+ for(ptrdiff_t i = 0; i < countof(dst.angles); i++)
+ dst.angles[i].Copy(src.angles[i]);
+ dst.iSelAngle = src.iSelAngle;
+ memcpy(dst.pal, src.pal, sizeof(src.pal));
+ memcpy(dst.ids, src.ids, sizeof(src.ids));
+ }
+ }
+ iSelPGC = rd.iSelPGC;
+ fResetTime = rd.fResetTime;
+ fClosedCaption = rd.fClosedCaption;
+ fForcedOnly = rd.fForcedOnly;
+ fClose = rd.fClose;
+ fBeep = rd.fBeep;
+ fAuto = rd.fAuto;
+ fCloseIgnoreError = rd.fCloseIgnoreError;
+ selvcs.Copy(rd.selvcs);
+ POSITION pos = rd.selids.GetStartPosition();
+ while(pos)
+ {
+ BYTE key;
+ bool val;
+ rd.selids.GetNextAssoc(pos, key, val);
+ selids[key] = val;
+ }
+}
+
diff --git a/src/Subtitles/VobSubFileRipper.h b/src/Subtitles/VobSubFileRipper.h
new file mode 100644
index 000000000..781917e22
--- /dev/null
+++ b/src/Subtitles/VobSubFileRipper.h
@@ -0,0 +1,197 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <atlcoll.h>
+#include "../decss/VobFile.h"
+#include "VobSubFile.h"
+
+#pragma pack(push)
+#pragma pack(1)
+
+typedef struct
+{
+ WORD perm_displ : 2;
+ WORD ratio : 2;
+ WORD system : 2;
+ WORD compression : 2;
+ WORD mode : 1;
+ WORD letterboxed : 1;
+ WORD source_res : 2;
+ WORD cbrvbr : 2;
+ WORD line21_2 : 1;
+ WORD line21_1 : 1;
+} vidinfo;
+
+typedef struct
+{
+ BYTE vob, cell;
+ DWORD tTime, tOffset, tTotal;
+ DWORD start, end;
+ int iAngle;
+ bool fDiscontinuity;
+} vc_t;
+
+typedef struct
+{
+ int nAngles;
+ CAtlArray<vc_t> angles[10];
+ int iSelAngle;
+ RGBQUAD pal[16];
+ WORD ids[32];
+} PGC;
+
+typedef struct VSFRipperData_t
+{
+ CSize vidsize;
+ vidinfo vidinfo;
+ CAtlArray<PGC> pgcs;
+ int iSelPGC;
+ bool fResetTime, fClosedCaption, fForcedOnly;
+
+ bool fClose, fBeep, fAuto; // only used by the UI externally, but may be set through the parameter file
+ bool fCloseIgnoreError;
+
+ CAtlArray<UINT> selvcs;
+ CAtlMap<BYTE, bool> selids;
+
+ void Reset();
+ void Copy(struct VSFRipperData_t& rd);
+
+} VSFRipperData;
+
+typedef struct
+{
+ __int64 start, end;
+ DWORD vc;
+} vcchunk;
+
+#pragma pack(pop)
+
+// note: these interfaces only meant to be used internally with static linking
+
+//
+// IVSFRipperCallback
+//
+
+interface __declspec(uuid("9E2EBB5C-AD7C-452f-A48B-38685716AC46"))
+IVSFRipperCallback : public IUnknown
+{
+ STDMETHOD (OnMessage) (LPCTSTR msg) PURE;
+ STDMETHOD (OnProgress) (double progress /*0->1*/) PURE;
+ STDMETHOD (OnFinished) (bool fSucceeded) PURE;
+};
+
+// IVSFRipperCallbackImpl
+
+#ifndef QI
+#define QI(i) (riid == __uuidof(i)) ? GetInterface((i*)this, ppv) :
+#endif
+
+class IVSFRipperCallbackImpl : public CUnknown, public IVSFRipperCallback
+{
+protected:
+ DECLARE_IUNKNOWN
+ STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void** ppv)
+ {
+ return
+ QI(IVSFRipperCallback)
+ __super::NonDelegatingQueryInterface(riid, ppv);
+ }
+
+ // IVSFRipperCallback
+ STDMETHODIMP OnMessage(LPCTSTR msg) {return S_FALSE;}
+ STDMETHODIMP OnProgress(double progress /*0->1*/) {return S_FALSE;}
+ STDMETHODIMP OnFinished(bool fSucceeded) {return S_FALSE;}
+
+public:
+ IVSFRipperCallbackImpl() : CUnknown(NAME("IVSFRipperCallbackImpl"), NULL) {}
+};
+
+//
+// IVSFRipper
+//
+
+interface __declspec(uuid("69F935BB-B8D0-43f5-AA2E-BBD0851CC9A6"))
+IVSFRipper : public IUnknown
+{
+ STDMETHOD (SetCallBack) (IVSFRipperCallback* pCallback) PURE;
+ STDMETHOD (LoadParamFile) (CString fn) PURE;
+ STDMETHOD (SetInput) (CString infn) PURE;
+ STDMETHOD (SetOutput) (CString outfn) PURE;
+ STDMETHOD (GetRipperData) (VSFRipperData& rd) PURE;
+ STDMETHOD (UpdateRipperData) (VSFRipperData& rd) PURE;
+ STDMETHOD (Index) () PURE;
+ STDMETHOD (IsIndexing) () PURE;
+ STDMETHOD (Abort) (bool fSavePartial) PURE;
+};
+
+class CVobSubFileRipper : public CVobSubFile, protected CAMThread, public IVSFRipper
+{
+private:
+ bool m_fThreadActive, m_fBreakThread, m_fIndexing;
+ enum {CMD_EXIT, CMD_INDEX};
+ DWORD ThreadProc();
+ bool Create();
+
+ //
+
+ typedef enum {LOG_INFO, LOG_WARNING, LOG_ERROR} log_t;
+ void Log(log_t type, LPCTSTR lpszFormat, ...);
+ void Progress(double progress);
+ void Finished(bool fSucceeded);
+
+ //
+
+ CCritSec m_csAccessLock;
+ CString m_infn, m_outfn;
+ CVobFile m_vob;
+ VSFRipperData m_rd;
+
+ bool LoadIfo(CString fn);
+ bool LoadVob(CString fn);
+ bool LoadChunks(CAtlArray<vcchunk>& chunks);
+ bool SaveChunks(CAtlArray<vcchunk>& chunks);
+
+ //
+
+ CCritSec m_csCallback;
+ CComPtr<IVSFRipperCallback> m_pCallback;
+
+public:
+ CVobSubFileRipper();
+ virtual ~CVobSubFileRipper();
+
+ DECLARE_IUNKNOWN
+ STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void** ppv);
+
+ // IVSFRipper
+ STDMETHODIMP SetCallBack(IVSFRipperCallback* pCallback);
+ STDMETHODIMP LoadParamFile(CString fn);
+ STDMETHODIMP SetInput(CString infn);
+ STDMETHODIMP SetOutput(CString outfn);
+ STDMETHODIMP GetRipperData(VSFRipperData& rd);
+ STDMETHODIMP UpdateRipperData(VSFRipperData& rd);
+ STDMETHODIMP Index();
+ STDMETHODIMP IsIndexing();
+ STDMETHODIMP Abort(bool fSavePartial);
+};
diff --git a/src/Subtitles/VobSubImage.cpp b/src/Subtitles/VobSubImage.cpp
new file mode 100644
index 000000000..33108a63b
--- /dev/null
+++ b/src/Subtitles/VobSubImage.cpp
@@ -0,0 +1,1196 @@
+/*
+ * 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 "VobSubImage.h"
+
+CVobSubImage::CVobSubImage()
+{
+ iLang = iIdx = -1;
+ fForced = false;
+ start = delay = 0;
+ rect = CRect(0,0,0,0);
+ lpPixels = lpTemp1 = lpTemp2 = NULL;
+ org = CSize(0,0);
+}
+
+CVobSubImage::~CVobSubImage()
+{
+ Free();
+}
+
+bool CVobSubImage::Alloc(int w, int h)
+{
+ // if there is nothing to crop TrimSubImage might even add a 1 pixel
+ // wide border around the text, that's why we need a bit more memory
+ // to be allocated.
+
+ if(lpTemp1 == NULL || w*h > org.cx*org.cy || (w+2)*(h+2) > (org.cx+2)*(org.cy+2))
+ {
+ Free();
+
+ lpTemp1 = DNew RGBQUAD[w*h];
+ if(!lpTemp1) return(false);
+
+ lpTemp2 = DNew RGBQUAD[(w+2)*(h+2)];
+ if(!lpTemp2) {delete [] lpTemp1; lpTemp1 = NULL; return(false);}
+
+ org.cx = w;
+ org.cy = h;
+ }
+
+ lpPixels = lpTemp1;
+
+ return(true);
+}
+
+void CVobSubImage::Free()
+{
+ if(lpTemp1) delete [] lpTemp1;
+ lpTemp1 = NULL;
+
+ if(lpTemp2) delete [] lpTemp2;
+ lpTemp2 = NULL;
+
+ lpPixels = NULL;
+}
+
+bool CVobSubImage::Decode(BYTE* lpData, int packetsize, int datasize,
+ bool fCustomPal,
+ int tridx,
+ RGBQUAD* orgpal /*[16]*/, RGBQUAD* cuspal /*[4]*/,
+ bool fTrim)
+{
+ GetPacketInfo(lpData, packetsize, datasize);
+
+ if(!Alloc(rect.Width(), rect.Height())) return(false);
+
+ lpPixels = lpTemp1;
+
+ nPlane = 0;
+ fAligned = 1;
+
+ this->fCustomPal = fCustomPal;
+ this->orgpal = orgpal;
+ this->tridx = tridx;
+ this->cuspal = cuspal;
+
+ CPoint p(rect.left, rect.top);
+
+ int end0 = nOffset[1];
+ int end1 = datasize;
+
+ while((nPlane == 0 && nOffset[0] < end0) || (nPlane == 1 && nOffset[1] < end1))
+ {
+ DWORD code;
+
+ if((code = GetNibble(lpData)) >= 0x4
+ || (code = (code << 4) | GetNibble(lpData)) >= 0x10
+ || (code = (code << 4) | GetNibble(lpData)) >= 0x40
+ || (code = (code << 4) | GetNibble(lpData)) >= 0x100)
+ {
+ DrawPixels(p, code >> 2, code & 3);
+ if((p.x += code >> 2) < rect.right) continue;
+ }
+
+ DrawPixels(p, rect.right - p.x, code & 3);
+
+ if(!fAligned) GetNibble(lpData); // align to byte
+
+ p.x = rect.left;
+ p.y++;
+ nPlane = 1 - nPlane;
+ }
+
+ rect.bottom = min(p.y, rect.bottom);
+
+ if(fTrim) TrimSubImage();
+
+ return(true);
+}
+
+void CVobSubImage::GetPacketInfo(BYTE* lpData, int packetsize, int datasize)
+{
+// delay = 0;
+
+ int i, nextctrlblk = datasize;
+ WORD pal = 0, tr = 0;
+
+ do
+ {
+ i = nextctrlblk;
+
+ int t = (lpData[i] << 8) | lpData[i+1]; i += 2;
+ nextctrlblk = (lpData[i] << 8) | lpData[i+1]; i += 2;
+
+ if(nextctrlblk > packetsize || nextctrlblk < datasize)
+ {
+ ASSERT(0);
+ return;
+ }
+
+ bool fBreak = false;
+
+ while(!fBreak)
+ {
+ int len = 0;
+
+ switch(lpData[i])
+ {
+ case 0x00: len = 0; break;
+ case 0x01: len = 0; break;
+ case 0x02: len = 0; break;
+ case 0x03: len = 2; break;
+ case 0x04: len = 2; break;
+ case 0x05: len = 6; break;
+ case 0x06: len = 4; break;
+ default: len = 0; break;
+ }
+
+ if(i+len >= packetsize)
+ {
+ TRACE(_T("Warning: Wrong subpicture parameter block ending\n"));
+ break;
+ }
+
+ switch(lpData[i++])
+ {
+ case 0x00: // forced start displaying
+ fForced = true;
+ break;
+ case 0x01: // start displaying
+ fForced = false;
+ break;
+ case 0x02: // stop displaying
+ delay = 1024 * t / 90;
+ break;
+ case 0x03:
+ pal = (lpData[i] << 8) | lpData[i+1]; i += 2;
+ break;
+ case 0x04:
+ tr = (lpData[i] << 8) | lpData[i+1]; i += 2;
+//tr &= 0x00f0;
+ break;
+ case 0x05:
+ rect = CRect((lpData[i] << 4) + (lpData[i+1] >> 4),
+ (lpData[i+3] << 4) + (lpData[i+4] >> 4),
+ ((lpData[i+1] & 0x0f) << 8) + lpData[i+2] + 1,
+ ((lpData[i+4] & 0x0f) << 8) + lpData[i+5] + 1);
+ i += 6;
+ break;
+ case 0x06:
+ nOffset[0] = (lpData[i] << 8) + lpData[i+1]; i += 2;
+ nOffset[1] = (lpData[i] << 8) + lpData[i+1]; i += 2;
+ break;
+ case 0xff: // end of ctrlblk
+ fBreak = true;
+ continue;
+ default: // skip this ctrlblk
+ fBreak = true;
+ break;
+ }
+ }
+ }
+ while(i <= nextctrlblk && i < packetsize);
+
+ for(i = 0; i < 4; i++)
+ {
+ this->pal[i].pal = (pal >> (i << 2)) & 0xf;
+ this->pal[i].tr = (tr >> (i << 2)) & 0xf;
+ }
+}
+
+BYTE CVobSubImage::GetNibble(BYTE* lpData)
+{
+ WORD& off = nOffset[nPlane];
+ BYTE ret = (lpData[off] >> (fAligned << 2)) & 0x0f;
+ fAligned = !fAligned;
+ off += fAligned;
+ return(ret);
+}
+
+void CVobSubImage::DrawPixels(CPoint p, int length, int colorid)
+{
+ if(length <= 0
+ || p.x + length < rect.left
+ || p.x >= rect.right
+ || p.y < rect.top
+ || p.y >= rect.bottom)
+ {
+ return;
+ }
+
+ if(p.x < rect.left) p.x = rect.left;
+ if(p.x + length >= rect.right) length = rect.right - p.x;
+
+ RGBQUAD* ptr = &lpPixels[rect.Width() * (p.y - rect.top) + (p.x - rect.left)];
+
+ RGBQUAD c;
+
+ if(!fCustomPal)
+ {
+ c = orgpal[pal[colorid].pal];
+ c.rgbReserved = (pal[colorid].tr<<4)|pal[colorid].tr;
+ }
+ else
+ {
+ c = cuspal[colorid];
+ }
+
+ while(length-- > 0) *ptr++ = c;
+}
+
+void CVobSubImage::TrimSubImage()
+{
+ CRect r;
+ r.left = rect.Width();
+ r.top = rect.Height();
+ r.right = 0;
+ r.bottom = 0;
+
+ RGBQUAD* ptr = lpTemp1;
+
+ for(ptrdiff_t j = 0, y = rect.Height(); j < y; j++)
+ {
+ for(ptrdiff_t i = 0, x = rect.Width(); i < x; i++, ptr++)
+ {
+ if(ptr->rgbReserved)
+ {
+ if(r.top > j) r.top = j;
+ if(r.bottom < j) r.bottom = j;
+ if(r.left > i) r.left = i;
+ if(r.right < i) r.right = i;
+ }
+ }
+ }
+
+ if(r.left > r.right || r.top > r.bottom) return;
+
+ r += CRect(0, 0, 1, 1);
+
+ r &= CRect(CPoint(0,0), rect.Size());
+
+ int w = r.Width(), h = r.Height();
+
+ DWORD offset = r.top*rect.Width() + r.left;
+
+ r += CRect(1, 1, 1, 1);
+
+ DWORD* src = (DWORD*)&lpTemp1[offset];
+ DWORD* dst = (DWORD*)&lpTemp2[1 + w + 1];
+
+ memset(lpTemp2, 0, (1 + w + 1)*sizeof(RGBQUAD));
+
+ for(ptrdiff_t height = h; height; height--, src += rect.Width())
+ {
+ *dst++ = 0;
+ memcpy(dst, src, w*sizeof(RGBQUAD)); dst += w;
+ *dst++ = 0;
+ }
+
+ memset(dst, 0, (1 + w + 1)*sizeof(RGBQUAD));
+
+ lpPixels = lpTemp2;
+
+ rect = r + rect.TopLeft();
+}
+
+////////////////////////////////
+
+#include "RTS.h"
+#include <math.h>
+
+#define GP(xx, yy) (((xx) < 0 || (yy) < 0 || (xx) >= w || (yy) >= h) ? 0 : p[(yy)*w+(xx)])
+
+CAutoPtrList<COutline>* CVobSubImage::GetOutlineList(CPoint& topleft)
+{
+ int w = rect.Width(), h = rect.Height(), len = w*h;
+ if(len <= 0) return NULL;
+
+ CAutoVectorPtr<BYTE> p;
+ if(!p.Allocate(len)) return NULL;
+
+ CAutoPtrList<COutline>* ol = DNew CAutoPtrList<COutline>();
+ if(!ol) return NULL;
+
+ BYTE* cp = p;
+ RGBQUAD* rgbp = (RGBQUAD*)lpPixels;
+
+ for(ptrdiff_t i = 0; i < len; i++, cp++, rgbp++)
+ *cp = !!rgbp->rgbReserved;
+
+ enum {UP, RIGHT, DOWN, LEFT};
+
+ topleft.x = topleft.y = INT_MAX;
+
+ while(1)
+ {
+ cp = p;
+
+ int x = 0;
+ int y = 0;
+
+ for(y = 0; y < h; y++)
+ {
+ for(x = 0; x < w-1; x++, cp++)
+ {
+ if(cp[0] == 0 && cp[1] == 1) break;
+ }
+
+ if(x < w-1) break;
+
+ cp++;
+ }
+
+ if(y == h) break;
+
+ int prevdir, dir = UP;
+
+ int ox = x, oy = y, odir = dir;
+
+ CAutoPtr<COutline> o(DNew COutline);
+ if(!o) break;
+
+ do
+ {
+ CPoint pp;
+ BYTE fl = 0;
+ BYTE fr = 0;
+ BYTE br = 0;
+
+ prevdir = dir;
+
+ switch(prevdir)
+ {
+ case UP:
+ pp = CPoint(x+1, y);
+ fl = GP(x, y-1);
+ fr = GP(x+1, y-1);
+ br = GP(x+1, y);
+ break;
+ case RIGHT:
+ pp = CPoint(x+1, y+1);
+ fl = GP(x+1, y);
+ fr = GP(x+1, y+1);
+ br = GP(x, y+1);
+ break;
+ case DOWN:
+ pp = CPoint(x, y+1);
+ fl = GP(x, y+1);
+ fr = GP(x-1, y+1);
+ br = GP(x-1, y);
+ break;
+ case LEFT:
+ pp = CPoint(x, y);
+ fl = GP(x-1, y);
+ fr = GP(x-1, y-1);
+ br = GP(x, y-1);
+ break;
+ }
+
+ // turning left if:
+ // o . | o .
+ // ^ o | < o
+ // turning right if:
+ // x x | x >
+ // ^ o | x o
+ //
+ // o set, x empty, . can be anything
+
+ if(fl==1) dir = (dir-1+4)&3;
+ else if(fl!=1 && fr!=1 && br==1) dir = (dir+1)&3;
+ else if(p[y*w+x]&16) {ASSERT(0); break;} // we are going around in one place (this must not happen if the starting conditions were correct)
+
+ p[y*w+x] = (p[y*w+x]<<1) | 2; // increase turn count (== log2(highordbit(*p)))
+
+ switch(dir)
+ {
+ case UP:
+ if(prevdir == LEFT) {x--; y--;}
+ if(prevdir == UP) y--;
+ break;
+ case RIGHT:
+ if(prevdir == UP) {x++; y--;}
+ if(prevdir == RIGHT) x++;
+ break;
+ case DOWN:
+ if(prevdir == RIGHT) {x++; y++;}
+ if(prevdir == DOWN) y++;
+ break;
+ case LEFT:
+ if(prevdir == DOWN) {x--; y++;}
+ if(prevdir == LEFT) x--;
+ break;
+ }
+
+ int d = dir - prevdir;
+ o->Add(pp, d == 3 ? -1 : d == -3 ? 1 : d);
+
+ if(topleft.x > pp.x) topleft.x = pp.x;
+ if(topleft.y > pp.y) topleft.y = pp.y;
+ }
+ while(!(x == ox && y == oy && dir == odir));
+
+ if(o->pa.GetCount() > 0 && (x == ox && y == oy && dir == odir))
+ {
+ ol->AddTail(o);
+ }
+ else
+ {
+ ASSERT(0);
+ }
+ }
+
+ return(ol);
+}
+
+static bool FitLine(COutline& o, int& start, int& end)
+{
+ int len = int(o.pa.GetCount());
+ if(len < 7) return(false); // small segments should be handled with beziers...
+
+ for(start = 0; start < len && !o.da[start]; start++);
+ for(end = len-1; end > start && !o.da[end]; end--);
+
+ if(end-start < 8 || end-start < (len-end)+(start-0)) return(false);
+
+ CUIntArray la, ra;
+
+ int i, j, k;
+
+ for(i = start+1, j = end, k = start; i <= j; i++)
+ {
+ if(!o.da[i]) continue;
+ if(o.da[i] == o.da[k]) return(false);
+ if(o.da[i] == -1) la.Add(i-k);
+ else ra.Add(i-k);
+ k = i;
+ }
+
+ bool fl = true, fr = true;
+
+ // these tests are completly heuristic and might be redundant a bit...
+
+ for(i = 0, j = la.GetSize(); i < j && fl; i++) {if(la[i] != 1) fl = false;}
+ for(i = 0, j = ra.GetSize(); i < j && fr; i++) {if(ra[i] != 1) fr = false;}
+
+ if(!fl && !fr) return(false); // can't be a line if there are bigger steps than one in both directions (lines are usually drawn by stepping one either horizontally or vertically)
+ if(fl && fr && 1.0*(end-start)/((len-end)*2+(start-0)*2) > 0.4) return(false); // if this section is relatively too small it may only be a rounded corner
+ if(!fl && la.GetSize() > 0 && la.GetSize() <= 4 && (la[0] == 1 && la[la.GetSize()-1] == 1)) return(false); // one step at both ends, doesn't sound good for a line (may be it was skewed, so only eliminate smaller sections where beziers going to look just as good)
+ if(!fr && ra.GetSize() > 0 && ra.GetSize() <= 4 && (ra[0] == 1 && ra[ra.GetSize()-1] == 1)) return(false); // -''-
+
+ CUIntArray& a = !fl ? la : ra;
+
+ len = a.GetSize();
+
+ int sum = 0;
+
+ for(i = 0, j = INT_MAX, k = 0; i < len; i++)
+ {
+ if(j > a[i]) j = a[i];
+ if(k < a[i]) k = a[i];
+ sum += a[i];
+ }
+
+ if(k - j > 2 && 1.0*sum/len < 2) return(false);
+ if(k - j > 2 && 1.0*sum/len >= 2 && len < 4) return(false);
+
+ if((la.GetSize()/2+ra.GetSize()/2)/2 <= 2)
+ {
+ if((k+j)/2 < 2 && k*j!=1) return(false);
+ }
+
+ double err = 0;
+
+ CPoint sp = o.pa[start], ep = o.pa[end];
+
+ double minerr = 0, maxerr = 0;
+
+ double vx = ep.x - sp.x, vy = ep.y - sp.y, l = sqrt(vx*vx+vy*vy);
+ vx /= l; vy /= l;
+
+ for(i = start+1, j = end-1; i <= j; i++)
+ {
+ CPoint p = o.pa[i], dp = p - sp;
+ double t = vx*dp.x+vy*dp.y, dx = vx*t + sp.x - p.x, dy = vy*t + sp.y - p.y;
+ t = dx*dx+dy*dy;
+ err += t;
+ t = sqrt(t);
+ if(vy*dx-dy*vx < 0) {if(minerr > -t) minerr = -t;}
+ else {if(maxerr < t) maxerr = t;}
+ }
+
+ return((maxerr-minerr)/l < 0.1 || err/l < 1.5 || (fabs(maxerr) < 8 && fabs(minerr) < 8));
+}
+
+static int CalcPossibleCurveDegree(COutline& o)
+{
+ int len2 = int(o.da.GetCount());
+
+ CUIntArray la;
+
+ for(ptrdiff_t i = 0, j = 0; j < len2; j++)
+ {
+ if(j == len2-1 || o.da[j])
+ {
+ la.Add(j-i);
+ i = j;
+ }
+ }
+
+ int len = la.GetCount();
+
+ int ret = 0;
+
+ // check if we can find a reason to add a penalty degree, or two :P
+ // it is mainly about looking for distant corners
+ {
+ int penalty = 0;
+
+ int ma[2] = {0, 0};
+ for(ptrdiff_t i = 0; i < len; i++) ma[i&1] += la[i];
+
+ int ca[2] = {ma[0], ma[1]};
+ for(ptrdiff_t i = 0; i < len; i++)
+ {
+ ca[i&1] -= la[i];
+
+ double c1 = 1.0*ca[0]/ma[0], c2 = 1.0*ca[1]/ma[1], c3 = 1.0*la[i]/ma[i&1];
+
+ if(len2 > 16 && (fabs(c1-c2) > 0.7 || (c3 > 0.6 && la[i] > 5)))
+ {penalty = 2; break;}
+
+ if(fabs(c1-c2) > 0.6 || (c3 > 0.4 && la[i] > 5))
+ {penalty = 1;}
+ }
+
+ ret += penalty;
+ }
+
+ la[0] <<= 1;
+ la[len-1] <<= 1;
+
+ for(ptrdiff_t i = 0; i < len; i+=2)
+ {
+ if(la[i] > 1) {ret++; i--;} // prependicular to the last chosen section and bigger then 1 -> add a degree and continue with the other dir
+ }
+
+ return(ret);
+}
+
+inline double vectlen(CPoint p)
+{
+ return(sqrt((double)(p.x*p.x+p.y*p.y)));
+}
+
+inline double vectlen(CPoint p1, CPoint p2)
+{
+ return(vectlen(p2 - p1));
+}
+
+static bool MinMaxCosfi(COutline& o, double& mincf, double& maxcf) // not really cosfi, it is weighted by the distance from the segment endpoints, and since it would be always between -1 and 0, the applied sign marks side
+{
+ CAtlArray<CPoint>& pa = o.pa;
+
+ int len = (int)pa.GetCount();
+ if(len < 6) return(false);
+
+ mincf = 1;
+ maxcf = -1;
+
+ CPoint p = pa[len-1] - pa[0];
+ double l = vectlen(p);
+ UNUSED_ALWAYS(l);
+
+ for(ptrdiff_t i = 2; i < len-2; i++) // skip the endpoints, they aren't accurate
+ {
+ CPoint p1 = pa[0] - pa[i], p2 = pa[len-1] - pa[i];
+ double l1 = vectlen(p1), l2 = vectlen(p2);
+ int sign = p1.x*p.y-p1.y*p.x >= 0 ? 1 : -1;
+
+ double c = (1.0*len/2 - fabs(i - 1.0*len/2)) / len * 2; // c: 0 -> 1 -> 0
+
+ double cosfi = (1+(p1.x*p2.x+p1.y*p2.y)/(l1*l2)) * sign * c;
+ if(mincf > cosfi) mincf = cosfi;
+ if(maxcf < cosfi) maxcf = cosfi;
+ }
+
+ return(true);
+}
+
+static bool FitBezierVH(COutline& o, CPoint& p1, CPoint& p2)
+{
+ int i;
+
+ CAtlArray<CPoint>& pa = o.pa;
+
+ int len = (int)pa.GetCount();
+
+ if(len <= 1)
+ {
+ return(false);
+ }
+ else if(len == 2)
+ {
+ CPoint mid = pa[0]+pa[1];
+ mid.x >>= 1;
+ mid.y >>= 1;
+ p1 = p2 = mid;
+ return(true);
+ }
+
+ CPoint dir1 = pa[1] - pa[0], dir2 = pa[len-2] - pa[len-1];
+ if((dir1.x&&dir1.y)||(dir2.x&&dir2.y))
+ return(false); // we are only fitting beziers with hor./ver. endings
+
+ if(CalcPossibleCurveDegree(o) > 3)
+ return(false);
+
+ double mincf, maxcf;
+ if(MinMaxCosfi(o, mincf, maxcf))
+ {
+ if(maxcf-mincf > 0.8
+ || maxcf-mincf > 0.6 && (maxcf >= 0.4 || mincf <= -0.4))
+ return(false);
+ }
+
+ CPoint p0 = p1 = pa[0];
+ CPoint p3 = p2 = pa[len-1];
+
+ CAtlArray<double> pl;
+ pl.SetCount(len);
+
+ double c10 = 0, c11 = 0, c12 = 0, c13 = 0, c1x = 0, c1y = 0;
+ double c20 = 0, c21 = 0, c22 = 0, c23 = 0, c2x = 0, c2y = 0;
+ double length = 0;
+
+ for(pl[0] = 0, i = 1; i < len; i++)
+ {
+ CPoint diff = (pa[i] - pa[i-1]);
+ pl[i] = (length += sqrt((double)(diff.x*diff.x+diff.y*diff.y)));
+ }
+
+ for(i = 0; i < len; i++)
+ {
+ double t1 = pl[i] / length;
+ double t2 = t1*t1;
+ double t3 = t2*t1;
+ double it1 = 1 - t1;
+ double it2 = it1*it1;
+ double it3 = it2*it1;
+
+ double dc1 = 3.0*it2*t1;
+ double dc2 = 3.0*it1*t2;
+
+ c10 += it3*dc1;
+ c11 += dc1*dc1;
+ c12 += dc2*dc1;
+ c13 += t3*dc1;
+ c1x += pa[i].x*dc1;
+ c1y += pa[i].y*dc1;
+
+ c20 += it3*dc2;
+ c21 += dc1*dc2;
+ c22 += dc2*dc2;
+ c23 += t3*dc2;
+ c2x += pa[i].x*dc2;
+ c2y += pa[i].y*dc2;
+ }
+
+ if(dir1.y == 0 && dir2.x == 0)
+ {
+ p1.x = (int)((c1x - c10*p0.x - c12*p3.x - c13*p3.x) / c11 + 0.5);
+ p2.y = (int)((c2y - c20*p0.y - c21*p0.y - c23*p3.y) / c22 + 0.5);
+ }
+ else if(dir1.x == 0 && dir2.y == 0)
+ {
+ p2.x = (int)((c2x - c20*p0.x - c21*p0.x - c23*p3.x) / c22 + 0.5);
+ p1.y = (int)((c1y - c10*p0.y - c12*p3.y - c13*p3.y) / c11 + 0.5);
+ }
+ else if(dir1.y == 0 && dir2.y == 0)
+ {
+ // cramer's rule
+ double D = c11*c22 - c12*c21;
+ p1.x = (int)(((c1x-c10*p0.x-c13*p3.x)*c22 - c12*(c2x-c20*p0.x-c23*p3.x)) / D + 0.5);
+ p2.x = (int)((c11*(c2x-c20*p0.x-c23*p3.x) - (c1x-c10*p0.x-c13*p3.x)*c21) / D + 0.5);
+ }
+ else if(dir1.x == 0 && dir2.x == 0)
+ {
+ // cramer's rule
+ double D = c11*c22 - c12*c21;
+ p1.y = (int)(((c1y-c10*p0.y-c13*p3.y)*c22 - c12*(c2y-c20*p0.y-c23*p3.y)) / D + 0.5);
+ p2.y = (int)((c11*(c2y-c20*p0.y-c23*p3.y) - (c1y-c10*p0.y-c13*p3.y)*c21) / D + 0.5);
+ }
+ else // must not happen
+ {
+ ASSERT(0);
+ return(false);
+ }
+
+ // check for "inside-out" beziers
+ CPoint dir3 = p1 - p0, dir4 = p2 - p3;
+ if((dir1.x*dir3.x+dir1.y*dir3.y) <= 0 || (dir2.x*dir4.x+dir2.y*dir4.y) <= 0)
+ return(false);
+
+ return(true);
+}
+
+int CVobSubImage::GrabSegment(int start, COutline& o, COutline& ret)
+{
+ ret.RemoveAll();
+
+ int len = int(o.pa.GetCount());
+
+ int cur = (start)%len, first = -1, last = -1;
+ int lastDir = 0;
+
+ for(ptrdiff_t i = 0; i < len; i++)
+ {
+ cur = (cur+1)%len;
+
+ if(o.da[cur] == 0) continue;
+
+ if(first == -1) first = cur;
+
+ if(lastDir == o.da[cur])
+ {
+ CPoint startp = o.pa[first]+o.pa[start]; startp.x >>= 1; startp.y >>= 1;
+ CPoint endp = o.pa[last]+o.pa[cur]; endp.x >>= 1; endp.y >>= 1;
+
+ if(first < start) first += len;
+ start = ((start+first)>>1)+1;
+ if(start >= len) start -= len;
+ if(cur < last) cur += len;
+ cur = ((last+cur+1)>>1);
+ if(cur >= len) cur -= len;
+
+ ret.Add(startp, 0);
+
+ while(start != cur)
+ {
+ ret.Add(o.pa[start], o.da[start]);
+
+ start++;
+ if(start >= len) start -= len;
+ }
+
+ ret.Add(endp, 0);
+
+ return(last);
+ }
+
+ lastDir = o.da[cur];
+ last = cur;
+ }
+
+ ASSERT(0);
+
+ return(start);
+}
+
+void CVobSubImage::SplitOutline(COutline& o, COutline& o1, COutline& o2)
+{
+ int len = int(o.pa.GetCount());
+ if(len < 4) return;
+
+ CAtlArray<UINT> la, sa, ea;
+
+ int i, j, k;
+
+ for(i = 0, j = 0; j < len; j++)
+ {
+ if(j == len-1 || o.da[j])
+ {
+ la.Add(j-i);
+ sa.Add(i);
+ ea.Add(j);
+ i = j;
+ }
+ }
+
+ int maxlen = 0, maxidx = -1;
+ int maxlen2 = 0, maxidx2 = -1;
+
+ for(i = 0; i < la.GetCount(); i++)
+ {
+ if(maxlen < la[i])
+ {
+ maxlen = la[i];
+ maxidx = i;
+ }
+
+ if(maxlen2 < la[i] && i > 0 && i < la.GetCount()-1)
+ {
+ maxlen2 = la[i];
+ maxidx2 = i;
+ }
+ }
+
+ if(maxlen == maxlen2) maxidx = maxidx2; // if equal choose the inner section
+
+ j = (sa[maxidx] + ea[maxidx]) >> 1, k = (sa[maxidx] + ea[maxidx] + 1) >> 1;
+
+ o1.RemoveAll();
+ o2.RemoveAll();
+
+ for(i = 0; i <= j; i++)
+ o1.Add(o.pa[i], o.da[i]);
+
+ if(j != k)
+ {
+ CPoint mid = o.pa[j]+o.pa[k]; mid.x >>= 1; mid.y >>= 1;
+ o1.Add(mid, 0);
+ o2.Add(mid, 0);
+ }
+
+ for(i = k; i < len; i++)
+ o2.Add(o.pa[i], o.da[i]);
+}
+
+void CVobSubImage::AddSegment(COutline& o, CAtlArray<BYTE>& pathTypes, CAtlArray<CPoint>& pathPoints)
+{
+ int i, len = int(o.pa.GetCount());
+ if(len < 3) return;
+
+ int nLeftTurns = 0, nRightTurns = 0;
+
+ for(i = 0; i < len; i++)
+ {
+ if(o.da[i] == -1) nLeftTurns++;
+ else if(o.da[i] == 1) nRightTurns++;
+ }
+
+ if(nLeftTurns == 0 && nRightTurns == 0) // line
+ {
+ pathTypes.Add(PT_LINETO);
+ pathPoints.Add(o.pa[len-1]);
+
+ return;
+ }
+
+ if(nLeftTurns == 0 || nRightTurns == 0) // b-spline
+ {
+ pathTypes.Add(PT_MOVETONC);
+ pathPoints.Add(o.pa[0]+(o.pa[0]-o.pa[1]));
+
+ for(i = 0; i < 3; i++)
+ {
+ pathTypes.Add(PT_BSPLINETO);
+ pathPoints.Add(o.pa[i]);
+ }
+
+ for(; i < len; i++)
+ {
+ pathTypes.Add(PT_BSPLINEPATCHTO);
+ pathPoints.Add(o.pa[i]);
+ }
+
+ pathTypes.Add(PT_BSPLINEPATCHTO);
+ pathPoints.Add(o.pa[len-1]+(o.pa[len-1]-o.pa[len-2]));
+
+ pathTypes.Add(PT_MOVETONC);
+ pathPoints.Add(o.pa[len-1]);
+
+ return;
+ }
+
+ int start, end;
+ if(FitLine(o, start, end)) // b-spline, line, b-spline
+ {
+ pathTypes.Add(PT_MOVETONC);
+ pathPoints.Add(o.pa[0]+(o.pa[0]-o.pa[1]));
+
+ pathTypes.Add(PT_BSPLINETO);
+ pathPoints.Add(o.pa[0]);
+
+ pathTypes.Add(PT_BSPLINETO);
+ pathPoints.Add(o.pa[1]);
+
+ CPoint p[4], pp, d = o.pa[end] - o.pa[start];
+ double l = sqrt((double)(d.x*d.x+d.y*d.y)), dx = 1.0 * d.x / l, dy = 1.0 * d.y / l;
+
+ pp = o.pa[start]-o.pa[start-1];
+ double l1 = abs(pp.x)+abs(pp.y);
+ pp = o.pa[end]-o.pa[end+1];
+ double l2 = abs(pp.x)+abs(pp.y);
+ p[0] = CPoint((int)(1.0 * o.pa[start].x + dx*l1 + 0.5), (int)(1.0 * o.pa[start].y + dy*l1 + 0.5));
+ p[1] = CPoint((int)(1.0 * o.pa[start].x + dx*l1*2 + 0.5), (int)(1.0 * o.pa[start].y + dy*l1*2 + 0.5));
+ p[2] = CPoint((int)(1.0 * o.pa[end].x - dx*l2*2 + 0.5), (int)(1.0 * o.pa[end].y - dy*l2*2 + 0.5));
+ p[3] = CPoint((int)(1.0 * o.pa[end].x - dx*l2 + 0.5), (int)(1.0 * o.pa[end].y - dy*l2 + 0.5));
+
+ if(start == 1)
+ {
+ pathTypes.Add(PT_BSPLINETO);
+ pathPoints.Add(p[0]);
+ }
+ else
+ {
+ pathTypes.Add(PT_BSPLINETO);
+ pathPoints.Add(o.pa[2]);
+
+ for(ptrdiff_t i = 3; i <= start; i++)
+ {
+ pathTypes.Add(PT_BSPLINEPATCHTO);
+ pathPoints.Add(o.pa[i]);
+ }
+
+ pathTypes.Add(PT_BSPLINEPATCHTO);
+ pathPoints.Add(p[0]);
+ }
+
+ pathTypes.Add(PT_BSPLINEPATCHTO);
+ pathPoints.Add(p[1]);
+
+ pathTypes.Add(PT_MOVETONC);
+ pathPoints.Add(p[0]);
+
+ pathTypes.Add(PT_LINETO);
+ pathPoints.Add(p[3]);
+
+ pathTypes.Add(PT_MOVETONC);
+ pathPoints.Add(p[2]);
+
+ pathTypes.Add(PT_BSPLINEPATCHTO);
+ pathPoints.Add(p[3]);
+
+ for(i = end; i < len; i++)
+ {
+ pathTypes.Add(PT_BSPLINEPATCHTO);
+ pathPoints.Add(o.pa[i]);
+ }
+
+ pathTypes.Add(PT_BSPLINEPATCHTO);
+ pathPoints.Add(o.pa[len-1]+(o.pa[len-1]-o.pa[len-2]));
+
+ pathTypes.Add(PT_MOVETONC);
+ pathPoints.Add(o.pa[len-1]);
+
+ return;
+ }
+
+ CPoint p1, p2;
+ if(FitBezierVH(o, p1, p2)) // bezier
+ {
+ pathTypes.Add(PT_BEZIERTO);
+ pathPoints.Add(p1);
+ pathTypes.Add(PT_BEZIERTO);
+ pathPoints.Add(p2);
+ pathTypes.Add(PT_BEZIERTO);
+ pathPoints.Add(o.pa[o.pa.GetCount()-1]);
+
+ return;
+ }
+
+ COutline o1, o2;
+ SplitOutline(o, o1, o2);
+ AddSegment(o1, pathTypes, pathPoints);
+ AddSegment(o2, pathTypes, pathPoints);
+}
+
+bool CVobSubImage::Polygonize(CAtlArray<BYTE>& pathTypes, CAtlArray<CPoint>& pathPoints, bool fSmooth, int scale)
+{
+ CPoint topleft;
+ CAutoPtr<CAutoPtrList<COutline> > ol(GetOutlineList(topleft));
+ if(!ol) return(false);
+
+ POSITION pos;
+
+ pos = ol->GetHeadPosition();
+ while(pos)
+ {
+ CAtlArray<CPoint>& pa = ol->GetNext(pos)->pa;
+ for(ptrdiff_t i = 0; i < pa.GetCount(); i++)
+ {
+ pa[i].x = (pa[i].x-topleft.x)<<scale;
+ pa[i].y = (pa[i].y-topleft.y)<<scale;
+ }
+ }
+
+ pos = ol->GetHeadPosition();
+ while(pos)
+ {
+ COutline& o = *ol->GetNext(pos), o2;
+
+ if(fSmooth)
+ {
+ int i = 0, iFirst = -1;
+
+ while(1)
+ {
+ i = GrabSegment(i, o, o2);
+
+ if(i == iFirst) break;
+
+ if(iFirst < 0)
+ {
+ iFirst = i;
+ pathTypes.Add(PT_MOVETO);
+ pathPoints.Add(o2.pa[0]);
+ }
+
+ AddSegment(o2, pathTypes, pathPoints);
+ }
+ }
+ else
+ {
+/*
+ for(ptrdiff_t i = 1, len = o.pa.GetSize(); i < len; i++)
+ {
+ if(int dir = o.da[i-1])
+ {
+ CPoint dir2 = o.pa[i] - o.pa[i-1];
+ dir2.x /= 2; dir2.y /= 2;
+ CPoint dir1 = dir > 0 ? CPoint(dir2.y, -dir2.x) : CPoint(-dir2.y, dir2.x);
+ i = i;
+ o.pa[i-1] -= dir1;
+ o.pa.InsertAt(i, o.pa[i-1] + dir2);
+ o.da.InsertAt(i, -dir);
+ o.pa.InsertAt(i+1, o.pa[i] + dir1);
+ o.da.InsertAt(i+1, dir);
+ i += 2;
+ len += 2;
+ }
+ }
+*/
+ pathTypes.Add(PT_MOVETO);
+ pathPoints.Add(o.pa[0]);
+
+ for(ptrdiff_t i = 1, len = int(o.pa.GetCount()); i < len; i++)
+ {
+ pathTypes.Add(PT_LINETO);
+ pathPoints.Add(o.pa[i]);
+ }
+ }
+ }
+
+ return !pathTypes.IsEmpty();
+}
+
+bool CVobSubImage::Polygonize(CStringW& assstr, bool fSmooth, int scale)
+{
+ CAtlArray<BYTE> pathTypes;
+ CAtlArray<CPoint> pathPoints;
+
+ if(!Polygonize(pathTypes, pathPoints, fSmooth, scale))
+ return(false);
+
+ assstr.Format(L"{\\an7\\pos(%d,%d)\\p%d}", rect.left, rect.top, 1+scale);
+// assstr.Format(L"{\\p%d}", 1+scale);
+
+ BYTE lastType = 0;
+
+ int nPoints = int(pathTypes.GetCount());
+
+ for(ptrdiff_t i = 0; i < nPoints; i++)
+ {
+ CStringW s;
+
+ switch(pathTypes[i])
+ {
+ case PT_MOVETO:
+ if(lastType != PT_MOVETO) assstr += L"m ";
+ s.Format(L"%d %d ", pathPoints[i].x, pathPoints[i].y);
+ break;
+ case PT_MOVETONC:
+ if(lastType != PT_MOVETONC) assstr += L"n ";
+ s.Format(L"%d %d ", pathPoints[i].x, pathPoints[i].y);
+ break;
+ case PT_LINETO:
+ if(lastType != PT_LINETO) assstr += L"l ";
+ s.Format(L"%d %d ", pathPoints[i].x, pathPoints[i].y);
+ break;
+ case PT_BEZIERTO:
+ if(i < nPoints-2)
+ {
+ if(lastType != PT_BEZIERTO) assstr += L"b ";
+ s.Format(L"%d %d %d %d %d %d ", pathPoints[i].x, pathPoints[i].y, pathPoints[i+1].x, pathPoints[i+1].y, pathPoints[i+2].x, pathPoints[i+2].y);
+ i+=2;
+ }
+ break;
+ case PT_BSPLINETO:
+ if(i < nPoints-2)
+ {
+ if(lastType != PT_BSPLINETO) assstr += L"s ";
+ s.Format(L"%d %d %d %d %d %d ", pathPoints[i].x, pathPoints[i].y, pathPoints[i+1].x, pathPoints[i+1].y, pathPoints[i+2].x, pathPoints[i+2].y);
+ i+=2;
+ }
+ break;
+ case PT_BSPLINEPATCHTO:
+ if(lastType != PT_BSPLINEPATCHTO) assstr += L"p ";
+ s.Format(L"%d %d ", pathPoints[i].x, pathPoints[i].y);
+ break;
+ }
+
+ lastType = pathTypes[i];
+
+ assstr += s;
+ }
+
+ assstr += L"{\\p0}";
+
+ return nPoints > 0;
+}
+
+void CVobSubImage::Scale2x()
+{
+ int w = rect.Width(), h = rect.Height();
+
+ DWORD* src = (DWORD*)lpPixels;
+ DWORD* dst = DNew DWORD[w*h];
+
+ for(ptrdiff_t y = 0; y < h; y++)
+ {
+ for(ptrdiff_t x = 0; x < w; x++, src++, dst++)
+ {
+ DWORD E = *src;
+
+ DWORD A = x > 0 && y > 0 ? src[-w-1] : E;
+ DWORD B = y > 0 ? src[-w] : E;
+ DWORD C = x < w-1 && y > 0 ? src[-w+1] : E;
+ UNUSED_ALWAYS(A);
+ UNUSED_ALWAYS(C);
+
+ DWORD D = x > 0 ? src[-1] : E;;
+ DWORD F = x < w-1 ? src[+1] : E;;
+
+ DWORD G = x > 0 && y < h-1 ? src[+w-1] : E;
+ DWORD H = y < h-1 ? src[+w] : E;
+ DWORD I = x < w-1 && y < h-1 ? src[+w+1] : E;
+ UNUSED_ALWAYS(G);
+ UNUSED_ALWAYS(I);
+
+ DWORD E0 = D == B && B != F && D != H ? D : E;
+ DWORD E1 = B == F && B != D && F != H ? F : E;
+ DWORD E2 = D == H && D != B && H != F ? D : E;
+ DWORD E3 = H == F && D != H && B != F ? F : E;
+
+ *dst = ((((E0&0x00ff00ff)+(E1&0x00ff00ff)+(E2&0x00ff00ff)+(E3&0x00ff00ff)+2)>>2)&0x00ff00ff)
+ | (((((E0>>8)&0x00ff00ff)+((E1>>8)&0x00ff00ff)+((E2>>8)&0x00ff00ff)+((E3>>8)&0x00ff00ff)+2)<<6)&0xff00ff00);
+ }
+ }
+
+ src -= w*h;
+ dst -= w*h;
+
+ memcpy(src, dst, w*h*4);
+
+ delete [] dst;
+}
diff --git a/src/Subtitles/VobSubImage.h b/src/Subtitles/VobSubImage.h
new file mode 100644
index 000000000..12b5165a0
--- /dev/null
+++ b/src/Subtitles/VobSubImage.h
@@ -0,0 +1,104 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <atlcoll.h>
+
+typedef struct
+{
+ CAtlArray<CPoint> pa;
+ CAtlArray<int> da;
+ void RemoveAll()
+ {
+ pa.RemoveAll();
+ da.RemoveAll();
+ }
+ void Add(CPoint p, int d)
+ {
+ pa.Add(p);
+ da.Add(d);
+ }
+} COutline;
+
+class CVobSubImage
+{
+ friend class CVobSubFile;
+
+private:
+ CSize org;
+ RGBQUAD* lpTemp1;
+ RGBQUAD* lpTemp2;
+
+ WORD nOffset[2], nPlane;
+ bool fCustomPal;
+ char fAligned; // we are also using this for calculations, that's why it is char instead of bool...
+ int tridx;
+ RGBQUAD* orgpal /*[16]*/,* cuspal /*[4]*/;
+
+ bool Alloc(int w, int h);
+ void Free();
+
+ BYTE GetNibble(BYTE* lpData);
+ void DrawPixels(CPoint p, int length, int colorid);
+ void TrimSubImage();
+
+public:
+ int iLang, iIdx;
+ bool fForced;
+ __int64 start, delay;
+ CRect rect;
+ typedef struct
+ {
+ BYTE pal: 4, tr: 4;
+ } SubPal;
+ SubPal pal[4];
+ RGBQUAD* lpPixels;
+
+ CVobSubImage();
+ virtual ~CVobSubImage();
+
+ void Invalidate()
+ {
+ iLang = iIdx = -1;
+ }
+
+ void GetPacketInfo(BYTE* lpData, int packetsize, int datasize);
+ bool Decode(BYTE* lpData, int packetsize, int datasize,
+ bool fCustomPal,
+ int tridx,
+ RGBQUAD* orgpal /*[16]*/, RGBQUAD* cuspal /*[4]*/,
+ bool fTrim);
+
+ /////////
+
+private:
+ CAutoPtrList<COutline>* GetOutlineList(CPoint& topleft);
+ int GrabSegment(int start, COutline& o, COutline& ret);
+ void SplitOutline(COutline& o, COutline& o1, COutline& o2);
+ void AddSegment(COutline& o, CAtlArray<BYTE>& pathTypes, CAtlArray<CPoint>& pathPoints);
+
+public:
+ bool Polygonize(CAtlArray<BYTE>& pathTypes, CAtlArray<CPoint>& pathPoints, bool fSmooth, int scale);
+ bool Polygonize(CStringW& assstr, bool fSmooth = true, int scale = 3);
+
+ void Scale2x();
+};
diff --git a/src/Subtitles/libssf/Arabic.cpp b/src/Subtitles/libssf/Arabic.cpp
new file mode 100644
index 000000000..123234018
--- /dev/null
+++ b/src/Subtitles/libssf/Arabic.cpp
@@ -0,0 +1,327 @@
+/*
+ * 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
+ *
+ * The "ArabicForms" table originates from fontforge
+ *
+ */
+
+#include "stdafx.h"
+#include "Arabic.h"
+
+namespace ssf
+{
+ static const struct arabicforms
+ {
+ unsigned short initial, medial, final, isolated;
+ unsigned int isletter: 1;
+ unsigned int joindual: 1;
+ unsigned int required_lig_with_alef: 1;
+ }
+ ArabicForms[256] =
+ {
+ { 0x0600, 0x0600, 0x0600, 0x0600, 0, 0, 0 },
+ { 0x0601, 0x0601, 0x0601, 0x0601, 0, 0, 0 },
+ { 0x0602, 0x0602, 0x0602, 0x0602, 0, 0, 0 },
+ { 0x0603, 0x0603, 0x0603, 0x0603, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x060b, 0x060b, 0x060b, 0x060b, 0, 0, 0 },
+ { 0x060c, 0x060c, 0x060c, 0x060c, 0, 0, 0 },
+ { 0x060d, 0x060d, 0x060d, 0x060d, 0, 0, 0 },
+ { 0x060e, 0x060e, 0x060e, 0x060e, 0, 0, 0 },
+ { 0x060f, 0x060f, 0x060f, 0x060f, 0, 0, 0 },
+ { 0x0610, 0x0610, 0x0610, 0x0610, 0, 0, 0 },
+ { 0x0611, 0x0611, 0x0611, 0x0611, 0, 0, 0 },
+ { 0x0612, 0x0612, 0x0612, 0x0612, 0, 0, 0 },
+ { 0x0613, 0x0613, 0x0613, 0x0613, 0, 0, 0 },
+ { 0x0614, 0x0614, 0x0614, 0x0614, 0, 0, 0 },
+ { 0x0615, 0x0615, 0x0615, 0x0615, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x061b, 0x061b, 0x061b, 0x061b, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x061e, 0x061e, 0x061e, 0x061e, 0, 0, 0 },
+ { 0x061f, 0x061f, 0x061f, 0x061f, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0621, 0x0621, 0x0621, 0xfe80, 1, 0, 0 },
+ { 0x0622, 0x0622, 0xfe82, 0xfe81, 1, 0, 0 },
+ { 0x0623, 0x0623, 0xfe84, 0xfe83, 1, 0, 0 },
+ { 0x0624, 0x0624, 0xfe86, 0xfe85, 1, 0, 0 },
+ { 0x0625, 0x0625, 0xfe88, 0xfe87, 1, 0, 0 },
+ { 0xfe8b, 0xfe8c, 0xfe8a, 0xfe89, 1, 1, 0 },
+ { 0x0627, 0x0627, 0xfe8e, 0xfe8d, 1, 0, 0 },
+ { 0xfe91, 0xfe92, 0xfe90, 0xfe8f, 1, 1, 0 },
+ { 0x0629, 0x0629, 0xfe94, 0xfe93, 1, 0, 0 },
+ { 0xfe97, 0xfe98, 0xfe96, 0xfe95, 1, 1, 0 },
+ { 0xfe9b, 0xfe9c, 0xfe9a, 0xfe99, 1, 1, 0 },
+ { 0xfe9f, 0xfea0, 0xfe9e, 0xfe9d, 1, 1, 0 },
+ { 0xfea3, 0xfea4, 0xfea2, 0xfea1, 1, 1, 0 },
+ { 0xfea7, 0xfea8, 0xfea6, 0xfea5, 1, 1, 0 },
+ { 0x062f, 0x062f, 0xfeaa, 0xfea9, 1, 0, 0 },
+ { 0x0630, 0x0630, 0xfeac, 0xfeab, 1, 0, 0 },
+ { 0x0631, 0x0631, 0xfeae, 0xfead, 1, 0, 0 },
+ { 0x0632, 0x0632, 0xfeb0, 0xfeaf, 1, 0, 0 },
+ { 0xfeb3, 0xfeb4, 0xfeb2, 0xfeb1, 1, 1, 0 },
+ { 0xfeb7, 0xfeb8, 0xfeb6, 0xfeb5, 1, 1, 0 },
+ { 0xfebb, 0xfebc, 0xfeba, 0xfeb9, 1, 1, 0 },
+ { 0xfebf, 0xfec0, 0xfebe, 0xfebd, 1, 1, 0 },
+ { 0xfec3, 0xfec4, 0xfec2, 0xfec1, 1, 1, 0 },
+ { 0xfec7, 0xfec8, 0xfec6, 0xfec5, 1, 1, 0 },
+ { 0xfecb, 0xfecc, 0xfeca, 0xfec9, 1, 1, 0 },
+ { 0xfecf, 0xfed0, 0xfece, 0xfecd, 1, 1, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0640, 0x0640, 0x0640, 0x0640, 0, 0, 0 },
+ { 0xfed3, 0xfed4, 0xfed2, 0xfed1, 1, 1, 0 },
+ { 0xfed7, 0xfed8, 0xfed6, 0xfed5, 1, 1, 0 },
+ { 0xfedb, 0xfedc, 0xfeda, 0xfed9, 1, 1, 0 },
+ { 0xfedf, 0xfee0, 0xfede, 0xfedd, 1, 1, 1 },
+ { 0xfee3, 0xfee4, 0xfee2, 0xfee1, 1, 1, 0 },
+ { 0xfee7, 0xfee8, 0xfee6, 0xfee5, 1, 1, 0 },
+ { 0xfeeb, 0xfeec, 0xfeea, 0xfee9, 1, 1, 0 },
+ { 0x0648, 0x0648, 0xfeee, 0xfeed, 1, 0, 0 },
+ { 0x0649, 0x0649, 0xfef0, 0xfeef, 1, 0, 0 },
+ { 0xfef3, 0xfef4, 0xfef2, 0xfef1, 1, 1, 0 },
+ { 0x064b, 0x064b, 0x064b, 0x064b, 0, 0, 0 },
+ { 0x064c, 0x064c, 0x064c, 0x064c, 0, 0, 0 },
+ { 0x064d, 0x064d, 0x064d, 0x064d, 0, 0, 0 },
+ { 0x064e, 0x064e, 0x064e, 0x064e, 0, 0, 0 },
+ { 0x064f, 0x064f, 0x064f, 0x064f, 0, 0, 0 },
+ { 0x0650, 0x0650, 0x0650, 0x0650, 0, 0, 0 },
+ { 0x0651, 0x0651, 0x0651, 0x0651, 0, 0, 0 },
+ { 0x0652, 0x0652, 0x0652, 0x0652, 0, 0, 0 },
+ { 0x0653, 0x0653, 0x0653, 0x0653, 0, 0, 0 },
+ { 0x0654, 0x0654, 0x0654, 0x0654, 0, 0, 0 },
+ { 0x0655, 0x0655, 0x0655, 0x0655, 0, 0, 0 },
+ { 0x0656, 0x0656, 0x0656, 0x0656, 0, 0, 0 },
+ { 0x0657, 0x0657, 0x0657, 0x0657, 0, 0, 0 },
+ { 0x0658, 0x0658, 0x0658, 0x0658, 0, 0, 0 },
+ { 0x0659, 0x0659, 0x0659, 0x0659, 0, 0, 0 },
+ { 0x065a, 0x065a, 0x065a, 0x065a, 0, 0, 0 },
+ { 0x065b, 0x065b, 0x065b, 0x065b, 0, 0, 0 },
+ { 0x065c, 0x065c, 0x065c, 0x065c, 0, 0, 0 },
+ { 0x065d, 0x065d, 0x065d, 0x065d, 0, 0, 0 },
+ { 0x065e, 0x065e, 0x065e, 0x065e, 0, 0, 0 },
+ { 0x0000, 0x0000, 0x0000, 0x0000, 0, 0, 0 },
+ { 0x0660, 0x0660, 0x0660, 0x0660, 0, 0, 0 },
+ { 0x0661, 0x0661, 0x0661, 0x0661, 0, 0, 0 },
+ { 0x0662, 0x0662, 0x0662, 0x0662, 0, 0, 0 },
+ { 0x0663, 0x0663, 0x0663, 0x0663, 0, 0, 0 },
+ { 0x0664, 0x0664, 0x0664, 0x0664, 0, 0, 0 },
+ { 0x0665, 0x0665, 0x0665, 0x0665, 0, 0, 0 },
+ { 0x0666, 0x0666, 0x0666, 0x0666, 0, 0, 0 },
+ { 0x0667, 0x0667, 0x0667, 0x0667, 0, 0, 0 },
+ { 0x0668, 0x0668, 0x0668, 0x0668, 0, 0, 0 },
+ { 0x0669, 0x0669, 0x0669, 0x0669, 0, 0, 0 },
+ { 0x066a, 0x066a, 0x066a, 0x066a, 0, 0, 0 },
+ { 0x066b, 0x066b, 0x066b, 0x066b, 0, 0, 0 },
+ { 0x066c, 0x066c, 0x066c, 0x066c, 0, 0, 0 },
+ { 0x066d, 0x066d, 0x066d, 0x066d, 0, 0, 0 },
+ { 0x066e, 0x066e, 0x066e, 0x066e, 1, 0, 0 },
+ { 0x066f, 0x066f, 0x066f, 0x066f, 1, 0, 0 },
+ { 0x0670, 0x0670, 0x0670, 0x0670, 1, 0, 0 },
+ { 0x0671, 0x0671, 0xfb51, 0xfb50, 1, 0, 0 },
+ { 0x0672, 0x0672, 0x0672, 0x0672, 1, 0, 0 },
+ { 0x0673, 0x0673, 0x0673, 0x0673, 1, 0, 0 },
+ { 0x0674, 0x0674, 0x0674, 0x0674, 1, 0, 0 },
+ { 0x0675, 0x0675, 0x0675, 0x0675, 1, 0, 0 },
+ { 0x0676, 0x0676, 0x0676, 0x0676, 1, 0, 0 },
+ { 0x0677, 0x0677, 0x0677, 0xfbdd, 1, 0, 0 },
+ { 0x0678, 0x0678, 0x0678, 0x0678, 1, 0, 0 },
+ { 0xfb68, 0xfb69, 0xfb67, 0xfb66, 1, 1, 0 },
+ { 0xfb60, 0xfb61, 0xfb5f, 0xfb5e, 1, 1, 0 },
+ { 0xfb54, 0xfb55, 0xfb53, 0xfb52, 1, 1, 0 },
+ { 0x067c, 0x067c, 0x067c, 0x067c, 1, 0, 0 },
+ { 0x067d, 0x067d, 0x067d, 0x067d, 1, 0, 0 },
+ { 0xfb58, 0xfb59, 0xfb57, 0xfb56, 1, 1, 0 },
+ { 0xfb64, 0xfb65, 0xfb63, 0xfb62, 1, 1, 0 },
+ { 0xfb5c, 0xfb5d, 0xfb5b, 0xfb5a, 1, 1, 0 },
+ { 0x0681, 0x0681, 0x0681, 0x0681, 1, 0, 0 },
+ { 0x0682, 0x0682, 0x0682, 0x0682, 1, 0, 0 },
+ { 0xfb78, 0xfb79, 0xfb77, 0xfb76, 1, 1, 0 },
+ { 0xfb74, 0xfb75, 0xfb73, 0xfb72, 1, 1, 0 },
+ { 0x0685, 0x0685, 0x0685, 0x0685, 1, 0, 0 },
+ { 0xfb7c, 0xfb7d, 0xfb7b, 0xfb7a, 1, 1, 0 },
+ { 0xfb80, 0xfb81, 0xfb7f, 0xfb7e, 1, 1, 0 },
+ { 0x0688, 0x0688, 0xfb89, 0xfb88, 1, 0, 0 },
+ { 0x0689, 0x0689, 0x0689, 0x0689, 1, 0, 0 },
+ { 0x068a, 0x068a, 0x068a, 0x068a, 1, 0, 0 },
+ { 0x068b, 0x068b, 0x068b, 0x068b, 1, 0, 0 },
+ { 0x068c, 0x068c, 0xfb85, 0xfb84, 1, 0, 0 },
+ { 0x068d, 0x068d, 0xfb83, 0xfb82, 1, 0, 0 },
+ { 0x068e, 0x068e, 0xfb87, 0xfb86, 1, 0, 0 },
+ { 0x068f, 0x068f, 0x068f, 0x068f, 1, 0, 0 },
+ { 0x0690, 0x0690, 0x0690, 0x0690, 1, 0, 0 },
+ { 0x0691, 0x0691, 0xfb8d, 0xfb8c, 1, 0, 0 },
+ { 0x0692, 0x0692, 0x0692, 0x0692, 1, 0, 0 },
+ { 0x0693, 0x0693, 0x0693, 0x0693, 1, 0, 0 },
+ { 0x0694, 0x0694, 0x0694, 0x0694, 1, 0, 0 },
+ { 0x0695, 0x0695, 0x0695, 0x0695, 1, 0, 0 },
+ { 0x0696, 0x0696, 0x0696, 0x0696, 1, 0, 0 },
+ { 0x0697, 0x0697, 0x0697, 0x0697, 1, 0, 0 },
+ { 0x0698, 0x0698, 0xfb8b, 0xfb8a, 1, 0, 0 },
+ { 0x0699, 0x0699, 0x0699, 0x0699, 1, 0, 0 },
+ { 0x069a, 0x069a, 0x069a, 0x069a, 1, 0, 0 },
+ { 0x069b, 0x069b, 0x069b, 0x069b, 1, 0, 0 },
+ { 0x069c, 0x069c, 0x069c, 0x069c, 1, 0, 0 },
+ { 0x069d, 0x069d, 0x069d, 0x069d, 1, 0, 0 },
+ { 0x069e, 0x069e, 0x069e, 0x069e, 1, 0, 0 },
+ { 0x069f, 0x069f, 0x069f, 0x069f, 1, 0, 0 },
+ { 0x06a0, 0x06a0, 0x06a0, 0x06a0, 1, 0, 0 },
+ { 0x06a1, 0x06a1, 0x06a1, 0x06a1, 1, 0, 0 },
+ { 0x06a2, 0x06a2, 0x06a2, 0x06a2, 1, 0, 0 },
+ { 0x06a3, 0x06a3, 0x06a3, 0x06a3, 1, 0, 0 },
+ { 0xfb6c, 0xfb6d, 0xfb6b, 0xfb6a, 1, 1, 0 },
+ { 0x06a5, 0x06a5, 0x06a5, 0x06a5, 1, 0, 0 },
+ { 0xfb70, 0xfb71, 0xfb6f, 0xfb6e, 1, 1, 0 },
+ { 0x06a7, 0x06a7, 0x06a7, 0x06a7, 1, 0, 0 },
+ { 0x06a8, 0x06a8, 0x06a8, 0x06a8, 1, 0, 0 },
+ { 0xfb90, 0xfb91, 0xfb8f, 0xfb8e, 1, 1, 0 },
+ { 0x06aa, 0x06aa, 0x06aa, 0x06aa, 1, 0, 0 },
+ { 0x06ab, 0x06ab, 0x06ab, 0x06ab, 1, 0, 0 },
+ { 0x06ac, 0x06ac, 0x06ac, 0x06ac, 1, 0, 0 },
+ { 0xfbd5, 0xfbd6, 0xfbd4, 0xfbd3, 1, 1, 0 },
+ { 0x06ae, 0x06ae, 0x06ae, 0x06ae, 1, 0, 0 },
+ { 0xfb94, 0xfb95, 0xfb93, 0xfb92, 1, 1, 0 },
+ { 0x06b0, 0x06b0, 0x06b0, 0x06b0, 1, 0, 0 },
+ { 0xfb9c, 0xfb9d, 0xfb9b, 0xfb9a, 1, 1, 0 },
+ { 0x06b2, 0x06b2, 0x06b2, 0x06b2, 1, 0, 0 },
+ { 0xfb98, 0xfb99, 0xfb97, 0xfb96, 1, 1, 0 },
+ { 0x06b4, 0x06b4, 0x06b4, 0x06b4, 1, 0, 0 },
+ { 0x06b5, 0x06b5, 0x06b5, 0x06b5, 1, 0, 0 },
+ { 0x06b6, 0x06b6, 0x06b6, 0x06b6, 1, 0, 0 },
+ { 0x06b7, 0x06b7, 0x06b7, 0x06b7, 1, 0, 0 },
+ { 0x06b8, 0x06b8, 0x06b8, 0x06b8, 1, 0, 0 },
+ { 0x06b9, 0x06b9, 0x06b9, 0x06b9, 1, 0, 0 },
+ { 0x06ba, 0x06ba, 0xfb9f, 0xfb9e, 1, 0, 0 },
+ { 0xfba2, 0xfba3, 0xfba1, 0xfba0, 1, 1, 0 },
+ { 0x06bc, 0x06bc, 0x06bc, 0x06bc, 1, 0, 0 },
+ { 0x06bd, 0x06bd, 0x06bd, 0x06bd, 1, 0, 0 },
+ { 0xfbac, 0xfbad, 0xfbab, 0xfbaa, 1, 1, 0 },
+ { 0x06bf, 0x06bf, 0x06bf, 0x06bf, 1, 0, 0 },
+ { 0x06c0, 0x06c0, 0xfba5, 0xfba4, 1, 0, 0 },
+ { 0xfba8, 0xfba9, 0xfba7, 0xfba6, 1, 1, 0 },
+ { 0x06c2, 0x06c2, 0x06c2, 0x06c2, 1, 0, 0 },
+ { 0x06c3, 0x06c3, 0x06c3, 0x06c3, 1, 0, 0 },
+ { 0x06c4, 0x06c4, 0x06c4, 0x06c4, 1, 0, 0 },
+ { 0x06c5, 0x06c5, 0xfbe1, 0xfbe0, 1, 0, 0 },
+ { 0x06c6, 0x06c6, 0xfbda, 0xfbd9, 1, 0, 0 },
+ { 0x06c7, 0x06c7, 0xfbd8, 0xfbd7, 1, 0, 0 },
+ { 0x06c8, 0x06c8, 0xfbdc, 0xfbdb, 1, 0, 0 },
+ { 0x06c9, 0x06c9, 0xfbe3, 0xfbe2, 1, 0, 0 },
+ { 0x06ca, 0x06ca, 0x06ca, 0x06ca, 1, 0, 0 },
+ { 0x06cb, 0x06cb, 0xfbdf, 0xfbde, 1, 0, 0 },
+ { 0xfbfe, 0xfbff, 0xfbfd, 0xfbfc, 1, 1, 0 },
+ { 0x06cd, 0x06cd, 0x06cd, 0x06cd, 1, 0, 0 },
+ { 0x06ce, 0x06ce, 0x06ce, 0x06ce, 1, 0, 0 },
+ { 0x06cf, 0x06cf, 0x06cf, 0x06cf, 1, 0, 0 },
+ { 0xfbe6, 0xfbe7, 0xfbe5, 0xfbe4, 1, 1, 0 },
+ { 0x06d1, 0x06d1, 0x06d1, 0x06d1, 1, 0, 0 },
+ { 0x06d2, 0x06d2, 0xfbaf, 0xfbae, 1, 0, 0 },
+ { 0x06d3, 0x06d3, 0xfbb1, 0xfbb0, 1, 0, 0 },
+ { 0x06d4, 0x06d4, 0x06d4, 0x06d4, 0, 0, 0 },
+ { 0x06d5, 0x06d5, 0x06d5, 0x06d5, 1, 0, 0 },
+ { 0x06d6, 0x06d6, 0x06d6, 0x06d6, 0, 0, 0 },
+ { 0x06d7, 0x06d7, 0x06d7, 0x06d7, 0, 0, 0 },
+ { 0x06d8, 0x06d8, 0x06d8, 0x06d8, 0, 0, 0 },
+ { 0x06d9, 0x06d9, 0x06d9, 0x06d9, 0, 0, 0 },
+ { 0x06da, 0x06da, 0x06da, 0x06da, 0, 0, 0 },
+ { 0x06db, 0x06db, 0x06db, 0x06db, 0, 0, 0 },
+ { 0x06dc, 0x06dc, 0x06dc, 0x06dc, 0, 0, 0 },
+ { 0x06dd, 0x06dd, 0x06dd, 0x06dd, 0, 0, 0 },
+ { 0x06de, 0x06de, 0x06de, 0x06de, 0, 0, 0 },
+ { 0x06df, 0x06df, 0x06df, 0x06df, 0, 0, 0 },
+ { 0x06e0, 0x06e0, 0x06e0, 0x06e0, 0, 0, 0 },
+ { 0x06e1, 0x06e1, 0x06e1, 0x06e1, 0, 0, 0 },
+ { 0x06e2, 0x06e2, 0x06e2, 0x06e2, 0, 0, 0 },
+ { 0x06e3, 0x06e3, 0x06e3, 0x06e3, 0, 0, 0 },
+ { 0x06e4, 0x06e4, 0x06e4, 0x06e4, 0, 0, 0 },
+ { 0x06e5, 0x06e5, 0x06e5, 0x06e5, 0, 0, 0 },
+ { 0x06e6, 0x06e6, 0x06e6, 0x06e6, 0, 0, 0 },
+ { 0x06e7, 0x06e7, 0x06e7, 0x06e7, 0, 0, 0 },
+ { 0x06e8, 0x06e8, 0x06e8, 0x06e8, 0, 0, 0 },
+ { 0x06e9, 0x06e9, 0x06e9, 0x06e9, 0, 0, 0 },
+ { 0x06ea, 0x06ea, 0x06ea, 0x06ea, 0, 0, 0 },
+ { 0x06eb, 0x06eb, 0x06eb, 0x06eb, 0, 0, 0 },
+ { 0x06ec, 0x06ec, 0x06ec, 0x06ec, 0, 0, 0 },
+ { 0x06ed, 0x06ed, 0x06ed, 0x06ed, 0, 0, 0 },
+ { 0x06ee, 0x06ee, 0x06ee, 0x06ee, 1, 0, 0 },
+ { 0x06ef, 0x06ef, 0x06ef, 0x06ef, 1, 0, 0 },
+ { 0x06f0, 0x06f0, 0x06f0, 0x06f0, 0, 0, 0 },
+ { 0x06f1, 0x06f1, 0x06f1, 0x06f1, 0, 0, 0 },
+ { 0x06f2, 0x06f2, 0x06f2, 0x06f2, 0, 0, 0 },
+ { 0x06f3, 0x06f3, 0x06f3, 0x06f3, 0, 0, 0 },
+ { 0x06f4, 0x06f4, 0x06f4, 0x06f4, 0, 0, 0 },
+ { 0x06f5, 0x06f5, 0x06f5, 0x06f5, 0, 0, 0 },
+ { 0x06f6, 0x06f6, 0x06f6, 0x06f6, 0, 0, 0 },
+ { 0x06f7, 0x06f7, 0x06f7, 0x06f7, 0, 0, 0 },
+ { 0x06f8, 0x06f8, 0x06f8, 0x06f8, 0, 0, 0 },
+ { 0x06f9, 0x06f9, 0x06f9, 0x06f9, 0, 0, 0 },
+ { 0x06fa, 0x06fa, 0x06fa, 0x06fa, 1, 0, 0 },
+ { 0x06fb, 0x06fb, 0x06fb, 0x06fb, 1, 0, 0 },
+ { 0x06fc, 0x06fc, 0x06fc, 0x06fc, 1, 0, 0 },
+ { 0x06fd, 0x06fd, 0x06fd, 0x06fd, 0, 0, 0 },
+ { 0x06fe, 0x06fe, 0x06fe, 0x06fe, 0, 0, 0 },
+ { 0x06ff, 0x06ff, 0x06ff, 0x06ff, 1, 0, 0 }
+ };
+
+ bool Arabic::IsArabic(WCHAR c)
+ {
+ return c >= 0x600 && c <= 0x6ff;
+ }
+
+ bool Arabic::Replace(WCHAR& c, pres_form_t pf)
+ {
+ if(!IsArabic(c)) return false;
+
+ const arabicforms& af = ArabicForms[c - 0x600];
+
+ switch(pf)
+ {
+ case isol: c = af.isolated; break;
+ case init: c = af.initial; break;
+ case medi: c = af.medial; break;
+ case fina: c = af.final; break;
+ }
+
+ return true;
+ }
+
+ bool Arabic::Replace(WCHAR& c, WCHAR prev, WCHAR next)
+ {
+ if(!IsArabic(c)) return false;
+
+ bool p = IsArabic(prev);
+ bool n = IsArabic(next);
+
+ return Replace(c, p ? (n ? medi : fina) : (n ? init : isol));
+ }
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/Arabic.h b/src/Subtitles/libssf/Arabic.h
new file mode 100644
index 000000000..d15d628ef
--- /dev/null
+++ b/src/Subtitles/libssf/Arabic.h
@@ -0,0 +1,34 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+namespace ssf
+{
+ class Arabic
+ {
+ public:
+ enum pres_form_t {isol, init, medi, fina};
+ static bool IsArabic(WCHAR c);
+ static bool Replace(WCHAR& c, pres_form_t pf);
+ static bool Replace(WCHAR& c, WCHAR prev, WCHAR next);
+ };
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/Array.cpp b/src/Subtitles/libssf/Array.cpp
new file mode 100644
index 000000000..2857cf6f2
--- /dev/null
+++ b/src/Subtitles/libssf/Array.cpp
@@ -0,0 +1,28 @@
+/*
+ * 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 "Array.h"
+
+namespace ssf
+{
+
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/Array.h b/src/Subtitles/libssf/Array.h
new file mode 100644
index 000000000..90d61c004
--- /dev/null
+++ b/src/Subtitles/libssf/Array.h
@@ -0,0 +1,108 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+namespace ssf
+{
+ // simple array class for simple types without constructors,
+ // and it doesn't free its reserves on SetCount(0)
+
+ template<class T>
+ class Array
+ {
+ T* m_pData;
+ size_t m_nSize;
+ size_t m_nMaxSize;
+ size_t m_nGrowBy;
+
+ public:
+ Array() {m_pData = NULL; m_nSize = m_nMaxSize = 0; m_nGrowBy = 4096;}
+ virtual ~Array() {if(m_pData) _aligned_free(m_pData);}
+
+ void SetCount(size_t nSize, size_t nGrowBy = 0)
+ {
+ if(nGrowBy > 0)
+ {
+ m_nGrowBy = nGrowBy;
+ }
+
+ if(nSize > m_nMaxSize)
+ {
+ m_nMaxSize = nSize + max(m_nGrowBy, m_nSize);
+ size_t nBytes = m_nMaxSize * sizeof(T);
+ m_pData = m_pData ? (T*)_aligned_realloc(m_pData, nBytes, 16) : (T*)_aligned_malloc(nBytes, 16);
+ }
+
+ m_nSize = nSize;
+ }
+
+ size_t GetCount() const {return m_nSize;}
+
+ void RemoveAll() {m_nSize = 0;}
+ bool IsEmpty() const {return m_nSize == 0;}
+
+ T* GetData() {return m_pData;}
+
+ void Add(const T& t)
+ {
+ size_t nPos = m_nSize;
+ SetCount(m_nSize+1);
+ m_pData[nPos] = t;
+ }
+
+ void Append(const Array& a, size_t nGrowBy = 0)
+ {
+ Append(a.m_pData, a.m_nSize, nGrowBy);
+ }
+
+ void Append(const T* ptr, size_t nSize, size_t nGrowBy = 0)
+ {
+ if(!nSize) return;
+ size_t nOldSize = m_nSize;
+ SetCount(nOldSize + nSize);
+ memcpy(m_pData + nOldSize, ptr, nSize * sizeof(T));
+ }
+
+ const T& operator [] (size_t i) const {return m_pData[i];}
+ T& operator [] (size_t i) {return m_pData[i];}
+
+ void Copy(const Array& v)
+ {
+ SetCount(v.GetCount());
+ memcpy(m_pData, v.m_pData, m_nSize * sizeof(T));
+ }
+
+ void Move(Array& v)
+ {
+ Swap(v);
+ v.SetCount(0);
+ }
+
+ void Swap(Array& v)
+ {
+ T* pData = m_pData; m_pData = v.m_pData; v.m_pData = pData;
+ size_t nSize = m_nSize; m_nSize = v.m_nSize; v.m_nSize = nSize;
+ size_t nMaxSize = m_nMaxSize; m_nMaxSize = v.m_nMaxSize; v.m_nMaxSize = nMaxSize;
+ size_t nGrowBy = m_nGrowBy; m_nGrowBy = v.m_nGrowBy; v.m_nGrowBy = nGrowBy;
+ }
+ };
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/Exception.cpp b/src/Subtitles/libssf/Exception.cpp
new file mode 100644
index 000000000..56b72d1e7
--- /dev/null
+++ b/src/Subtitles/libssf/Exception.cpp
@@ -0,0 +1,35 @@
+/*
+ * 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 "Exception.h"
+
+namespace ssf
+{
+ Exception::Exception(LPCTSTR fmt, ...)
+ {
+ va_list args;
+ va_start(args, fmt);
+ int len = _vsctprintf(fmt, args) + 1;
+ if(len > 0) _vstprintf_s(m_msg.GetBufferSetLength(len), len, fmt, args);
+ va_end(args);
+ }
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/Exception.h b/src/Subtitles/libssf/Exception.h
new file mode 100644
index 000000000..c45a06919
--- /dev/null
+++ b/src/Subtitles/libssf/Exception.h
@@ -0,0 +1,35 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+namespace ssf
+{
+ class Exception
+ {
+ CString m_msg;
+
+ public:
+ Exception(LPCTSTR fmt, ...);
+
+ CString ToString() const {return m_msg;}
+ };
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/File.cpp b/src/Subtitles/libssf/File.cpp
new file mode 100644
index 000000000..5d6724924
--- /dev/null
+++ b/src/Subtitles/libssf/File.cpp
@@ -0,0 +1,261 @@
+/*
+ * 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 "File.h"
+
+#ifndef iswcsym
+#define iswcsym(c) (iswalnum(c) || (c) == '_')
+#endif
+
+namespace ssf
+{
+ File::File()
+ {
+ }
+
+ File::~File()
+ {
+ }
+
+ void File::Parse(InputStream& s, LPCWSTR predef)
+ {
+ Reference* pRef = CreateRootRef();
+
+ SetPredefined(true);
+
+ try {ParseDefs(WCharInputStream(predef), pRef);}
+ catch(Exception& e) {ASSERT(0); TRACE(_T("%s\n"), e.ToString());}
+
+ SetPredefined(false);
+
+ ParseDefs(s, pRef);
+
+ Commit();
+
+ if(s.PeekChar() != Stream::EOS)
+ {
+ TRACE(_T("Warning: parsing ended before EOF!\n"));
+ }
+ }
+
+ void File::ParseDefs(InputStream& s, Reference* pParentRef)
+ {
+ while(s.SkipWhiteSpace(L";") != '}' && s.PeekChar() != Stream::EOS)
+ {
+ NodePriority priority = PNormal;
+ CAtlList<CStringW> types;
+ CStringW name;
+
+ int c = s.SkipWhiteSpace();
+
+ if(c == '*') {s.GetChar(); priority = PLow;}
+ else if(c == '!') {s.GetChar(); priority = PHigh;}
+
+ ParseTypes(s, types);
+
+ if(s.SkipWhiteSpace() == '#')
+ {
+ s.GetChar();
+ ParseName(s, name);
+ }
+
+ if(types.IsEmpty())
+ {
+ if(name.IsEmpty()) s.ThrowError(_T("syntax error"));
+ types.AddTail(L"?");
+ }
+
+ Reference* pRef = pParentRef;
+
+ while(types.GetCount() > 1)
+ pRef = CreateRef(CreateDef(pRef, types.RemoveHead()));
+
+ Definition* pDef = NULL;
+
+ if(!types.IsEmpty())
+ pDef = CreateDef(pRef, types.RemoveHead(), name, priority);
+
+ c = s.SkipWhiteSpace(L":=");
+
+ if(c == '"' || c == '\'') ParseQuotedString(s, pDef);
+ else if(iswdigit(c) || c == '+' || c == '-') ParseNumber(s, pDef);
+ else if(pDef->IsType(L"@")) ParseBlock(s, pDef);
+ else ParseRefs(s, pDef);
+ }
+
+ s.GetChar();
+ }
+
+ void File::ParseTypes(InputStream& s, CAtlList<CStringW>& types)
+ {
+ types.RemoveAll();
+
+ CStringW str;
+
+ for(int c = s.SkipWhiteSpace(); iswcsym(c) || c == '.' || c == '@'; c = s.PeekChar())
+ {
+ c = s.GetChar();
+
+ if(c == '.')
+ {
+ if(str.IsEmpty()) s.ThrowError(_T("'type' cannot be an empty string"));
+ if(!iswcsym(s.PeekChar())) s.ThrowError(_T("unexpected dot after type '%s'"), CString(str));
+
+ types.AddTail(str);
+ str.Empty();
+ }
+ else
+ {
+ if(str.IsEmpty() && iswdigit(c)) s.ThrowError(_T("'type' cannot start with a number"));
+ if((!str.IsEmpty() || !types.IsEmpty()) && c == '@') s.ThrowError(_T("unexpected @ in 'type'"));
+
+ str += (WCHAR)c;
+ }
+ }
+
+ if(!str.IsEmpty())
+ {
+ types.AddTail(str);
+ }
+ }
+
+ void File::ParseName(InputStream& s, CStringW& name)
+ {
+ name.Empty();
+
+ for(int c = s.SkipWhiteSpace(); iswcsym(c); c = s.PeekChar())
+ {
+ if(name.IsEmpty() && iswdigit(c)) s.ThrowError(_T("'name' cannot start with a number"));
+ name += (WCHAR)s.GetChar();
+ }
+ }
+
+ void File::ParseQuotedString(InputStream& s, Definition* pDef)
+ {
+ CStringW v;
+
+ int quote = s.SkipWhiteSpace();
+ if(quote != '"' && quote != '\'') s.ThrowError(_T("expected qouted string"));
+ s.GetChar();
+
+ for(int c = s.PeekChar(); c != Stream::EOS; c = s.PeekChar())
+ {
+ c = s.GetChar();
+ if(c == quote) {pDef->SetAsValue(Definition::string, v); return;}
+ if(c == '\n') s.ThrowError(_T("qouted string terminated unexpectedly by a new-line character"));
+ if(c == '\\') c = s.GetChar();
+ if(c == Stream::EOS) s.ThrowError(_T("qouted string terminated unexpectedly by EOS"));
+ v += (WCHAR)c;
+ }
+
+ s.ThrowError(_T("unterminated quoted string"));
+ }
+
+ void File::ParseNumber(InputStream& s, Definition* pDef)
+ {
+ CStringW v, u;
+
+ for(int c = s.SkipWhiteSpace(); iswxdigit(c) || wcschr(L"+-.x:", c); c = s.PeekChar())
+ {
+ if((c == '+' || c == '-') && !v.IsEmpty()
+ || (c == '.' && (v.IsEmpty() || v.Find('.') >= 0 || v.Find('x') >= 0))
+ || (c == 'x' && v != '0')
+ || (c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F') && v.Find(L"0x") != 0
+ || (c == ':' && v.IsEmpty()))
+ {
+ s.ThrowError(_T("unexpected character '%c' in number"), (TCHAR)c);
+ }
+
+ v += (WCHAR)s.GetChar();
+ }
+
+ if(v.IsEmpty()) s.ThrowError(_T("invalid number"));
+
+ for(int c = s.SkipWhiteSpace(); iswcsym(c); c = s.PeekChar())
+ {
+ u += (WCHAR)s.GetChar();
+ }
+
+ pDef->SetAsNumber(v, u);
+ }
+
+ void File::ParseBlock(InputStream& s, Definition* pDef)
+ {
+ CStringW v;
+
+ int c = s.SkipWhiteSpace(L":=");
+ if(c != '{') s.ThrowError(_T("expected '{'"));
+ s.GetChar();
+
+ int depth = 0;
+
+ for(int c = s.PeekChar(); c != Stream::EOS; c = s.PeekChar())
+ {
+ c = s.GetChar();
+ if(c == '}' && depth == 0) {pDef->SetAsValue(Definition::block, v); return;}
+ if(c == '\\') {v += (WCHAR)c; c = s.GetChar();}
+ else if(c == '{') depth++;
+ else if(c == '}') depth--;
+ if(c == Stream::EOS) s.ThrowError(_T("block terminated unexpectedly by EOS"));
+ v += (WCHAR)c;
+ }
+
+ s.ThrowError(_T("unterminated block"));
+ }
+
+ void File::ParseRefs(InputStream& s, Definition* pParentDef, LPCWSTR term)
+ {
+ int c = s.SkipWhiteSpace();
+
+ do
+ {
+ if(pParentDef->IsValue()) s.ThrowError(_T("cannot mix references with other values"));
+
+ if(c == '{')
+ {
+ s.GetChar();
+ ParseDefs(s, CreateRef(pParentDef));
+ }
+ else if(iswcsym(c))
+ {
+ CStringW str;
+ ParseName(s, str);
+
+ // TODO: allow spec references: parent.<type>, self.<type>, child.<type>
+
+ Definition* pDef = GetDefByName(str);
+ if(!pDef) s.ThrowError(_T("cannot find definition of '%s'"), CString(str));
+
+ if(!pParentDef->IsVisible(pDef)) s.ThrowError(_T("cannot access '%s' from here"), CString(str));
+
+ pParentDef->AddTail(pDef);
+ }
+ else if(!wcschr(term, c) && c != Stream::EOS)
+ {
+ s.ThrowError(_T("unexpected character '%c'"), (TCHAR)c);
+ }
+
+ c = s.SkipWhiteSpace();
+ }
+ while(!wcschr(term, c) && c != Stream::EOS);
+ }
+}
diff --git a/src/Subtitles/libssf/File.h b/src/Subtitles/libssf/File.h
new file mode 100644
index 000000000..9a4ad3d91
--- /dev/null
+++ b/src/Subtitles/libssf/File.h
@@ -0,0 +1,45 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include "Stream.h"
+#include "NodeFactory.h"
+
+namespace ssf
+{
+ class File : public NodeFactory
+ {
+ public:
+ File();
+ virtual ~File();
+
+ void Parse(InputStream& s, LPCWSTR predef = NULL);
+
+ void ParseDefs(InputStream& s, Reference* pParentRef);
+ void ParseTypes(InputStream& s, CAtlList<CStringW>& types);
+ void ParseName(InputStream& s, CStringW& name);
+ void ParseQuotedString(InputStream& s, Definition* pDef);
+ void ParseNumber(InputStream& s, Definition* pDef);
+ void ParseBlock(InputStream& s, Definition* pDef);
+ void ParseRefs(InputStream& s, Definition* pParentDef, LPCWSTR term = L";}]");
+ };
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/FontWrapper.cpp b/src/Subtitles/libssf/FontWrapper.cpp
new file mode 100644
index 000000000..07bc9cdcf
--- /dev/null
+++ b/src/Subtitles/libssf/FontWrapper.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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 "FontWrapper.h"
+
+namespace ssf
+{
+ FontWrapper::FontWrapper(HDC hDC, HFONT hFont, const CStringW& key)
+ : m_hFont(hFont)
+ , m_key(key)
+ {
+ HFONT hFontOld = SelectFont(hDC, hFont);
+
+ GetTextMetrics(hDC, &m_tm);
+
+ if(DWORD nNumPairs = GetKerningPairs(hDC, 0, NULL))
+ {
+ KERNINGPAIR* kp = DNew KERNINGPAIR[nNumPairs];
+ GetKerningPairs(hDC, nNumPairs, kp);
+ for(DWORD i = 0; i < nNumPairs; i++)
+ m_kerning[(kp[i].wFirst<<16)|kp[i].wSecond] = kp[i].iKernAmount;
+ delete [] kp;
+ }
+
+ SelectFont(hDC, hFontOld);
+ }
+
+ FontWrapper::~FontWrapper()
+ {
+ DeleteFont(m_hFont);
+ }
+
+ int FontWrapper::GetKernAmount(WCHAR c1, WCHAR c2)
+ {
+ int size = 0;
+ return m_kerning.Lookup((c1<<16)|c2, size) ? size : 0;
+ }
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/FontWrapper.h b/src/Subtitles/libssf/FontWrapper.h
new file mode 100644
index 000000000..86c7a601b
--- /dev/null
+++ b/src/Subtitles/libssf/FontWrapper.h
@@ -0,0 +1,41 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+namespace ssf
+{
+ class FontWrapper
+ {
+ HFONT m_hFont;
+ CStringW m_key;
+ TEXTMETRIC m_tm;
+ CAtlMap<DWORD, int> m_kerning;
+
+ public:
+ FontWrapper(HDC hDC, HFONT hFont, const CStringW& key);
+ virtual ~FontWrapper();
+ operator HFONT() const {return m_hFont;}
+ operator LPCWSTR() const {return m_key;}
+ const TEXTMETRIC& GetTextMetric() const {return m_tm;}
+ int GetKernAmount(WCHAR c1, WCHAR c2);
+ };
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/Glyph.cpp b/src/Subtitles/libssf/Glyph.cpp
new file mode 100644
index 000000000..4d43df190
--- /dev/null
+++ b/src/Subtitles/libssf/Glyph.cpp
@@ -0,0 +1,306 @@
+/*
+ * 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 "Glyph.h"
+#include "Split.h"
+
+#define deg2rad(d) (float)(M_PI/180*(d))
+
+namespace ssf
+{
+ Glyph::Glyph()
+ {
+ c = 0;
+ font = NULL;
+ ascent = descent = width = spacing = fill = 0;
+ tl.x = tl.y = tls.x = tls.y = 0;
+ }
+
+ float Glyph::GetBackgroundSize() const
+ {
+ return style.background.size * (scale.cx + scale.cy) / 2;
+ }
+
+ float Glyph::GetShadowDepth() const
+ {
+ return style.shadow.depth * (scale.cx + scale.cy) / 2;
+ }
+
+ CRect Glyph::GetClipRect() const
+ {
+ CRect r = bbox + tl;
+
+ int size = (int)(GetBackgroundSize() + 0.5);
+ int depth = (int)(GetShadowDepth() + 0.5);
+
+ r.InflateRect(size, size);
+ r.InflateRect(depth, depth);
+
+ r.left >>= 6;
+ r.top >>= 6;
+ r.right = (r.right + 32) >> 6;
+ r.bottom = (r.bottom + 32) >> 6;
+
+ return r;
+ }
+
+ void Glyph::CreateBkg()
+ {
+ path_bkg.RemoveAll();
+
+ if(style.background.type == L"enlarge" && style.background.size > 0)
+ {
+ path_bkg.Enlarge(path, GetBackgroundSize());
+ }
+ else if(style.background.type == L"box" && style.background.size >= 0)
+ {
+ if(c != ssf::Text::LSEP)
+ {
+ int s = (int)(GetBackgroundSize() + 0.5);
+ int x0 = (!vertical ? -spacing/2 : ascent - row_ascent);
+ int y0 = (!vertical ? ascent - row_ascent : -spacing/2);
+ int x1 = x0 + (!vertical ? width + spacing : row_ascent + row_descent);
+ int y1 = y0 + (!vertical ? row_ascent + row_descent : width + spacing);
+ path_bkg.types.SetCount(4);
+ path_bkg.types[0] = PT_MOVETO;
+ path_bkg.types[1] = PT_LINETO;
+ path_bkg.types[2] = PT_LINETO;
+ path_bkg.types[3] = PT_LINETO;
+ path_bkg.points.SetCount(4);
+ path_bkg.points[0] = CPoint(x0-s, y0-s);
+ path_bkg.points[1] = CPoint(x1+s, y0-s);
+ path_bkg.points[2] = CPoint(x1+s, y1+s);
+ path_bkg.points[3] = CPoint(x0-s, y1+s);
+ }
+ }
+ }
+
+ void Glyph::CreateSplineCoeffs(const CRect& spdrc)
+ {
+ spline.RemoveAll();
+
+ if(style.placement.path.IsEmpty())
+ return;
+
+ size_t i = 0, j = style.placement.path.GetCount();
+
+ CAtlArray<Point> pts;
+ pts.SetCount(j + 2);
+
+ Point p;
+
+ while(i < j)
+ {
+ p.x = style.placement.path[i].x * scale.cx + spdrc.left * 64;
+ p.y = style.placement.path[i].y * scale.cy + spdrc.top * 64;
+ pts[++i] = p;
+ }
+
+ if(pts.GetCount() >= 4)
+ {
+ if(pts[1].x == pts[j].x && pts[1].y == pts[j].y)
+ {
+ pts.SetAt(0, pts[j-1]);
+ pts.SetAt(j+1, pts[2]);
+ }
+ else
+ {
+ p.x = pts[1].x*2 - pts[2].x;
+ p.y = pts[1].y*2 - pts[2].y;
+ pts.SetAt(0, p);
+
+ p.x = pts[j].x*2 - pts[j-1].x;
+ p.y = pts[j].y*2 - pts[j-1].y;
+ pts.SetAt(j+1, p);
+ }
+
+ spline.SetCount(pts.GetCount()-3);
+
+ for(size_t i = 0, j = pts.GetCount()-4; i <= j; i++)
+ {
+ static const float _1div6 = 1.0f / 6;
+
+ SplineCoeffs sc;
+
+ sc.cx[3] = _1div6*(- pts[i+0].x + 3*pts[i+1].x - 3*pts[i+2].x + pts[i+3].x);
+ sc.cx[2] = _1div6*( 3*pts[i+0].x - 6*pts[i+1].x + 3*pts[i+2].x);
+ sc.cx[1] = _1div6*(-3*pts[i+0].x + 3*pts[i+2].x);
+ sc.cx[0] = _1div6*( pts[i+0].x + 4*pts[i+1].x + 1*pts[i+2].x);
+
+ sc.cy[3] = _1div6*(- pts[i+0].y + 3*pts[i+1].y - 3*pts[i+2].y + pts[i+3].y);
+ sc.cy[2] = _1div6*( 3*pts[i+0].y - 6*pts[i+1].y + 3*pts[i+2].y);
+ sc.cy[1] = _1div6*(-3*pts[i+0].y + 3*pts[i+2].y);
+ sc.cy[0] = _1div6*( pts[i+0].y + 4*pts[i+1].y + 1*pts[i+2].y);
+
+ spline.SetAt(i, sc);
+ }
+ }
+ }
+
+ void Glyph::Transform(GlyphPath& path, CPoint org, const CRect& subrect)
+ {
+ // TODO: add sse code path
+
+ float sx = style.font.scale.cx;
+ float sy = style.font.scale.cy;
+
+ bool brotate = style.placement.angle.x || style.placement.angle.y || style.placement.angle.z;
+ bool bspline = !spline.IsEmpty();
+ bool bscale = brotate || bspline || sx != 1 || sy != 1;
+
+ float caz = cos(deg2rad(style.placement.angle.z));
+ float saz = sin(deg2rad(style.placement.angle.z));
+ float cax = cos(deg2rad(style.placement.angle.x));
+ float sax = sin(deg2rad(style.placement.angle.x));
+ float cay = cos(deg2rad(style.placement.angle.y));
+ float say = sin(deg2rad(style.placement.angle.y));
+
+ for(size_t i = 0, j = path.types.GetCount(); i < j; i++)
+ {
+ CPoint p = path.points[i];
+
+ if(bscale)
+ {
+ float x, y, z, xx, yy, zz;
+
+ x = sx * (p.x - org.x);
+ y = sy * (p.y - org.y);
+ z = 0;
+
+ if(bspline)
+ {
+ float pos = vertical ? y + org.y + tl.y - subrect.top : x + org.x + tl.x - subrect.left;
+ float size = vertical ? subrect.Size().cy : subrect.Size().cx;
+ float dist = vertical ? x : y;
+
+ const SplineCoeffs* sc;
+ float t;
+
+ if(pos >= size)
+ {
+ sc = &spline[spline.GetCount() - 1];
+ t = 1;
+ }
+ else
+ {
+ float u = size / spline.GetCount();
+ sc = &spline[max((int)(pos / u), 0)];
+ t = fmod(pos, u) / u;
+ }
+
+ float nx = sc->cx[1] + 2*t*sc->cx[2] + 3*t*t*sc->cx[3];
+ float ny = sc->cy[1] + 2*t*sc->cy[2] + 3*t*t*sc->cy[3];
+ float nl = 1.0f / sqrt(nx*nx + ny*ny);
+
+ nx *= nl;
+ ny *= nl;
+
+ x = sc->cx[0] + t*(sc->cx[1] + t*(sc->cx[2] + t*sc->cx[3])) - ny * dist - org.x - tl.x;
+ y = sc->cy[0] + t*(sc->cy[1] + t*(sc->cy[2] + t*sc->cy[3])) + nx * dist - org.y - tl.y;
+ }
+
+ if(brotate)
+ {
+ 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 = 1.0f / (max(zz, -19000) + 20000);
+
+ x = (xx * 20000) * zz;
+ y = (yy * 20000) * zz;
+ }
+
+ p.x = (int)(x + org.x + 0.5);
+ p.y = (int)(y + org.y + 0.5);
+
+ path.points[i] = p;
+ }
+
+ if(p.x < bbox.left) bbox.left = p.x;
+ if(p.x > bbox.right) bbox.right = p.x;
+ if(p.y < bbox.top) bbox.top = p.y;
+ if(p.y > bbox.bottom) bbox.bottom = p.y;
+ }
+ }
+
+ void Glyph::Transform(CPoint org, const CRect& subrect)
+ {
+ if(!style.placement.org.auto_x) org.x = style.placement.org.x * scale.cx;
+ if(!style.placement.org.auto_y) org.y = style.placement.org.y * scale.cy;
+
+ org -= tl;
+
+ bbox.SetRect(INT_MAX, INT_MAX, INT_MIN, INT_MIN);
+
+ Transform(path_bkg, org, subrect);
+ Transform(path, org, subrect);
+
+ bbox |= CRect(0, 0, 0, 0);
+ }
+
+ void Glyph::Rasterize()
+ {
+ if(!path_bkg.IsEmpty())
+ {
+ ras_bkg.ScanConvert(path_bkg, bbox);
+ ras_bkg.Rasterize(tl.x, tl.y);
+ }
+
+ ras.ScanConvert(path, bbox);
+
+ if(style.background.type == L"outline" && style.background.size > 0)
+ {
+ ras.CreateWidenedRegion((int)(GetBackgroundSize() + 0.5));
+ }
+
+ //
+
+ Rasterizer* r = path_bkg.IsEmpty() ? &ras : &ras_bkg;
+ int plane = path_bkg.IsEmpty() ? (style.font.color.a < 255 ? 2 : 1) : 0;
+
+ ras.Rasterize(tl.x, tl.y);
+ r->Blur(style.background.blur, plane);
+
+ if(style.shadow.depth > 0)
+ {
+ ras_shadow.Reuse(*r);
+
+ float depth = GetShadowDepth();
+
+ tls.x = tl.x + (int)(depth * cos(deg2rad(style.shadow.angle)) + 0.5);
+ tls.y = tl.y + (int)(depth * -sin(deg2rad(style.shadow.angle)) + 0.5);
+
+ ras_shadow.Rasterize(tls.x, tls.y);
+ ras_shadow.Blur(style.shadow.blur, plane ? 1 : 0);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/Glyph.h b/src/Subtitles/libssf/Glyph.h
new file mode 100644
index 000000000..157bda03c
--- /dev/null
+++ b/src/Subtitles/libssf/Glyph.h
@@ -0,0 +1,63 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <atlcoll.h>
+#include "GlyphPath.h"
+#include "FontWrapper.h"
+#include "Rasterizer.h"
+
+namespace ssf
+{
+ class Glyph
+ {
+ void Transform(GlyphPath& path, CPoint org, const CRect& subrect);
+
+ struct SplineCoeffs {float cx[4], cy[4];};
+
+ public:
+ WCHAR c;
+ Style style;
+ CAtlArray<SplineCoeffs> spline;
+ Size scale;
+ bool vertical;
+ FontWrapper* font;
+ int ascent, descent, width, spacing, fill;
+ int row_ascent, row_descent;
+ GlyphPath path, path_bkg;
+ CRect bbox;
+ CPoint tl, tls;
+ Rasterizer ras, ras_bkg, ras_shadow;
+
+ public:
+ Glyph();
+
+ void CreateBkg();
+ void CreateSplineCoeffs(const CRect& spdrc);
+ void Transform(CPoint org, const CRect& subrect);
+ void Rasterize();
+
+ float GetBackgroundSize() const;
+ float GetShadowDepth() const;
+ CRect GetClipRect() const;
+ };
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/GlyphPath.cpp b/src/Subtitles/libssf/GlyphPath.cpp
new file mode 100644
index 000000000..a1947c226
--- /dev/null
+++ b/src/Subtitles/libssf/GlyphPath.cpp
@@ -0,0 +1,164 @@
+/*
+ * 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 "GlyphPath.h"
+
+namespace ssf
+{
+ GlyphPath::GlyphPath(const GlyphPath& path)
+ {
+ *this = path;
+ }
+
+ void GlyphPath::operator = (const GlyphPath& path)
+ {
+ types.Copy(path.types);
+ points.Copy(path.points);
+ }
+
+ bool GlyphPath::IsEmpty()
+ {
+ return types.IsEmpty() || points.IsEmpty();
+ }
+
+ void GlyphPath::RemoveAll()
+ {
+ types.RemoveAll();
+ points.RemoveAll();
+ }
+
+ void GlyphPath::MovePoints(const CPoint& o)
+ {
+ size_t n = points.GetCount();
+ POINT* p = points.GetData();
+
+ unsigned int i = 0;
+
+ if(!!(g_cpuid.m_flags & CCpuID::sse2) && !((DWORD_PTR)p & 7))
+ {
+ for( ; i < n && ((DWORD_PTR)&p[i] & 15); i++)
+ {
+ p[i].x += o.x;
+ p[i].y += o.y;
+ }
+
+ __m128i oo = _mm_set_epi32(o.y, o.x, o.y, o.x);
+
+ for(unsigned int j = i + ((n - i) & ~7); i < j; i += 8)
+ {
+ __m128i r0 = _mm_load_si128((__m128i*)&p[i+0]);
+ __m128i r1 = _mm_load_si128((__m128i*)&p[i+2]);
+ __m128i r2 = _mm_load_si128((__m128i*)&p[i+4]);
+ __m128i r3 = _mm_load_si128((__m128i*)&p[i+6]);
+ _mm_store_si128((__m128i*)&p[i+0], _mm_add_epi32(r0, oo));
+ _mm_store_si128((__m128i*)&p[i+2], _mm_add_epi32(r1, oo));
+ _mm_store_si128((__m128i*)&p[i+4], _mm_add_epi32(r2, oo));
+ _mm_store_si128((__m128i*)&p[i+6], _mm_add_epi32(r3, oo));
+ }
+ }
+
+ for(; i < n; i++)
+ {
+ p[i].x += o.x;
+ p[i].y += o.y;
+ }
+ }
+
+ void GlyphPath::Enlarge(const GlyphPath& src, float size)
+ {
+ types.SetCount(src.types.GetCount());
+ points.SetCount(src.points.GetCount());
+
+ memcpy(types.GetData(), src.types.GetData(), types.GetCount());
+
+ size_t start = 0, end = 0;
+
+ for(size_t i = 0, j = src.types.GetCount(); i <= j; i++)
+ {
+ if(i > 0 && (i == j || (src.types[i] & ~PT_CLOSEFIGURE) == PT_MOVETO))
+ {
+ end = i-1;
+
+ bool cw = true; // TODO: determine orientation (ttf is always cw and we are sill before Transform)
+ float rotate = cw ? -M_PI_2 : M_PI_2;
+
+ CPoint prev = src.points[end];
+ CPoint cur = src.points[start];
+
+ for(size_t k = start; k <= end; k++)
+ {
+ CPoint next = k < end ? src.points[k+1] : src.points[start];
+
+ for(int l = int(k-1); prev == cur; l--)
+ {
+ if(l < (int)start) l = int(end);
+ prev = src.points[l];
+ }
+
+ for(int l = int(k+1); next == cur; l++)
+ {
+ if(l > (int)end) l = int(start);
+ next = src.points[l];
+ }
+
+ CPoint in = cur - prev;
+ CPoint out = next - cur;
+
+ float angle_in = atan2((float)in.y, (float)in.x);
+ float angle_out = atan2((float)out.y, (float)out.x);
+ float angle_diff = angle_out - angle_in;
+ if(angle_diff < 0) angle_diff += M_PI*2;
+ if(angle_diff > M_PI) angle_diff -= M_PI*2;
+ float scale = cos(angle_diff / 2);
+
+ CPoint p;
+
+ if(angle_diff < 0)
+ {
+ if(angle_diff > -M_PI/8) {if(scale < 1) scale = 1;}
+ else {if(scale < 0.50) scale = 0.50;}
+ }
+ else
+ {
+ if(angle_diff < M_PI/8) {if(scale < 0.75) scale = 0.75;}
+ else {if(scale < 0.25) scale = 0.25;}
+ }
+
+ if(scale < 0.1) scale = 0.1;
+
+ float angle = angle_in + angle_diff / 2 - rotate;
+ float radius = -size / scale; // FIXME
+
+ p.x = (int)(radius * cos(angle) + 0.5);
+ p.y = (int)(radius * sin(angle) + 0.5);
+
+ points[k] = cur + p;
+
+ prev = cur;
+ cur = next;
+ }
+
+ start = end+1;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/GlyphPath.h b/src/Subtitles/libssf/GlyphPath.h
new file mode 100644
index 000000000..2a921758b
--- /dev/null
+++ b/src/Subtitles/libssf/GlyphPath.h
@@ -0,0 +1,45 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <atlcoll.h>
+
+namespace ssf
+{
+ class GlyphPath
+ {
+ public:
+ GlyphPath() {}
+ virtual ~GlyphPath() {}
+
+ GlyphPath(const GlyphPath& path);
+ void operator = (const GlyphPath& path);
+
+ bool IsEmpty();
+ void RemoveAll();
+ void MovePoints(const CPoint& o);
+ void Enlarge(const GlyphPath& src, float size);
+
+ CAtlArray<BYTE> types;
+ CAtlArray<POINT> points;
+ };
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/Node.cpp b/src/Subtitles/libssf/Node.cpp
new file mode 100644
index 000000000..f959d9ca6
--- /dev/null
+++ b/src/Subtitles/libssf/Node.cpp
@@ -0,0 +1,551 @@
+/*
+ * 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 "Node.h"
+#include "NodeFactory.h"
+#include "Exception.h"
+#include "Split.h"
+
+#include <math.h>
+
+namespace ssf
+{
+ Node::Node(NodeFactory* pnf, CStringW name)
+ : m_pnf(pnf)
+ , m_type('?')
+ , m_name(name)
+ , m_priority(PNormal)
+ , m_predefined(false)
+ , m_parent(NULL)
+ {
+ ASSERT(m_pnf);
+ }
+
+ void Node::AddTail(Node* pNode)
+ {
+ if(POSITION pos = m_nodes.Find(pNode)) // TODO: slow
+ {
+ m_nodes.MoveToTail(pos);
+ return;
+ }
+
+ m_nodes.AddTail(pNode);
+ m_name2node[pNode->m_name] = pNode;
+ }
+
+ bool Node::IsNameUnknown()
+ {
+ return m_name.IsEmpty() || !!iswdigit(m_name[0]);
+ }
+
+ bool Node::IsTypeUnknown()
+ {
+ return m_type.IsEmpty() || m_type == '?';
+ }
+
+ bool Node::IsType(CStringW type)
+ {
+ return m_type == type;
+ }
+
+ void Node::GetChildDefs(CAtlList<Definition*>& l, LPCWSTR type, bool fFirst)
+ {
+ CAtlList<Definition*> rdl[3];
+
+ if(fFirst)
+ {
+ if(Definition* pDef = m_pnf->GetDefByName(m_type))
+ {
+ pDef->GetChildDefs(rdl[pDef->m_priority], type, false);
+ }
+ }
+
+ POSITION pos = m_nodes.GetHeadPosition();
+ while(pos)
+ {
+ if(Node* pNode = m_nodes.GetNext(pos))
+ {
+ pNode->GetChildDefs(rdl[pNode->m_priority], type, false);
+ }
+ }
+
+ for(int i = 0; i < sizeof(rdl)/sizeof(rdl[0]); i++)
+ {
+ l.AddTailList(&rdl[i]);
+ }
+ }
+
+ // Reference
+
+ Reference::Reference(NodeFactory* pnf, CStringW name)
+ : Node(pnf, name)
+ {
+ }
+
+ Reference::~Reference()
+ {
+ }
+
+ void Reference::GetChildDefs(CAtlList<Definition*>& l, LPCWSTR type, bool fFirst)
+ {
+ CAtlList<Definition*> rdl[3];
+
+ POSITION pos = m_nodes.GetHeadPosition();
+ while(pos)
+ {
+ if(Definition* pDef = dynamic_cast<Definition*>(m_nodes.GetNext(pos)))
+ {
+ if(!type || pDef->m_type == type) // TODO: faster lookup
+ {
+ rdl[pDef->m_priority].AddTail(pDef);
+ }
+ }
+ }
+
+ for(int i = 0; i < sizeof(rdl)/sizeof(rdl[0]); i++)
+ {
+ l.AddTailList(&rdl[i]);
+ }
+ }
+
+ void Reference::Dump(OutputStream& s, int level, bool fLast)
+ {
+ if(m_predefined) return;
+
+ CStringW tabs(' ', level*4);
+
+ // s.PutString(tabs + '\n' + tabs + L" {\n");
+ s.PutString(L" {\n");
+
+ POSITION pos = m_nodes.GetHeadPosition();
+ while(pos)
+ {
+ Node* pNode = m_nodes.GetNext(pos);
+
+ if(Definition* pDef = dynamic_cast<Definition*>(pNode))
+ {
+ pDef->Dump(s, level + 1, pos == NULL);
+ }
+ }
+
+ s.PutString(tabs + '}');
+ }
+
+ // Definition
+
+ Definition::Definition(NodeFactory* pnf, CStringW name)
+ : Node(pnf, name)
+ , m_status(node)
+ , m_autotype(false)
+ {
+ }
+
+ Definition::~Definition()
+ {
+ RemoveFromCache();
+ }
+
+ bool Definition::IsVisible(Definition* pDef)
+ {
+ Node* pNode = m_parent;
+
+ while(pNode)
+ {
+ if(pNode->m_name2node.Lookup(pDef->m_name))
+ {
+ return true;
+ }
+
+ pNode = pNode->m_parent;
+ }
+
+ return false;
+ }
+
+ void Definition::AddTail(Node* pNode)
+ {
+// if(Reference* pRef = dynamic_cast<Reference*>(pNode))
+ {
+ ASSERT(m_status == node);
+
+ m_status = node;
+
+ if(IsTypeUnknown() && !pNode->IsTypeUnknown())
+ {
+ m_type = pNode->m_type;
+ m_autotype = true;
+ }
+
+ RemoveFromCache(pNode->m_type);
+ }
+
+ __super::AddTail(pNode);
+ }
+
+ Definition& Definition::operator[] (LPCWSTR type)
+ {
+ Definition* pRetDef = NULL;
+ if(m_type2def.Lookup(type, pRetDef))
+ return *pRetDef;
+
+ pRetDef = DNew Definition(m_pnf, L"");
+ pRetDef->m_priority = PLow;
+ pRetDef->m_type = type;
+ m_type2def[type] = pRetDef;
+
+ CAtlList<Definition*> l;
+ GetChildDefs(l, type);
+
+ while(!l.IsEmpty())
+ {
+ Definition* pDef = l.RemoveHead();
+
+ pRetDef->m_priority = pDef->m_priority;
+ pRetDef->m_parent = pDef->m_parent;
+
+ if(pDef->IsValue())
+ {
+ pRetDef->SetAsValue(pDef->m_status, pDef->m_value, pDef->m_unit);
+ }
+ else
+ {
+ pRetDef->m_status = node;
+ pRetDef->m_nodes.AddTailList(&pDef->m_nodes);
+ }
+ }
+
+ return *pRetDef;
+ }
+
+ void Definition::RemoveFromCache(LPCWSTR type)
+ {
+ if(!type)
+ {
+ POSITION pos = m_type2def.GetStartPosition();
+ while(pos) delete m_type2def.GetNextValue(pos);
+ }
+ else if(StringMapW<Definition*>::CPair* p = m_type2def.Lookup(type))
+ {
+ delete p->m_value;
+ m_type2def.RemoveKey(type);
+ }
+ }
+
+ bool Definition::IsValue(status_t s)
+ {
+ return s ? m_status == s : m_status != node;
+ }
+
+ void Definition::SetAsValue(status_t s, CStringW v, CStringW u)
+ {
+ ASSERT(s != node);
+
+ m_nodes.RemoveAll();
+ m_name2node.RemoveAll();
+
+ m_status = s;
+
+ m_value = v;
+ m_unit = u;
+ }
+
+ void Definition::SetAsNumber(CStringW v, CStringW u)
+ {
+ SetAsValue(number, v, u);
+
+ Number<float> n;
+ GetAsNumber(n); // will throw an exception if not a number
+ }
+
+ template<class T>
+ void Definition::GetAsNumber(Number<T>& n, StringMapW<T>* n2n)
+ {
+ CStringW str = m_value;
+ str.Replace(L" ", L"");
+
+ n.value = 0;
+ n.unit = m_unit;
+ n.sign = 0;
+
+ if(n2n)
+ {
+ if(m_status == node) throw Exception(_T("expected value type"));
+
+ if(StringMapW<T>::CPair* p = n2n->Lookup(str))
+ {
+ n.value = p->m_value;
+ return;
+ }
+ }
+
+ if(m_status != number) throw Exception(_T("expected number"));
+
+ n.sign = str.Find('+') == 0 ? 1 : str.Find('-') == 0 ? -1 : 0;
+ str.TrimLeft(L"+-");
+
+ if(str.Find(L"0x") == 0)
+ {
+ if(n.sign) throw Exception(_T("hex values must be unsigned"));
+
+ n.value = (T)wcstoul(str.Mid(2), NULL, 16);
+ }
+ else
+ {
+ CStringW num_string = m_value + m_unit;
+
+ if(m_num_string != num_string)
+ {
+ Split sa(':', str);
+ Split sa2('.', sa ? sa[sa-1] : L"");
+
+ if(sa == 0 || sa2 == 0 || sa2 > 2) throw Exception(_T("invalid number"));
+
+ float f = 0;
+ for(size_t i = 0; i < sa; i++) {f *= 60; f += wcstoul(sa[i], NULL, 10);}
+ if(sa2 > 1) f += (float)wcstoul(sa2[1], NULL, 10) / pow((float)10, sa2[1].GetLength());
+
+ if(n.unit == L"ms") {f /= 1000; n.unit = L"s";}
+ else if(n.unit == L"m") {f *= 60; n.unit = L"s";}
+ else if(n.unit == L"h") {f *= 3600; n.unit = L"s";}
+
+ m_num.value = f;
+ m_num.unit = n.unit;
+ m_num_string = num_string;
+
+ n.value = (T)f;
+ }
+ else
+ {
+ n.value = (T)m_num.value;
+ n.unit = m_num.unit;
+ }
+
+ if(n.sign) n.value *= n.sign;
+ }
+ }
+
+ void Definition::GetAsString(CStringW& str)
+ {
+ if(m_status == node) throw Exception(_T("expected value type"));
+
+ str = m_value;
+ }
+
+ void Definition::GetAsNumber(Number<int>& n, StringMapW<int>* n2n) {return GetAsNumber<int>(n, n2n);}
+ void Definition::GetAsNumber(Number<DWORD>& n, StringMapW<DWORD>* n2n) {return GetAsNumber<DWORD>(n, n2n);}
+ void Definition::GetAsNumber(Number<float>& n, StringMapW<float>* n2n) {return GetAsNumber<float>(n, n2n);}
+
+ void Definition::GetAsBoolean(bool& b)
+ {
+ static StringMapW<bool> s2b;
+
+ if(s2b.IsEmpty())
+ {
+ s2b[L"true"] = true;
+ s2b[L"on"] = true;
+ s2b[L"yes"] = true;
+ s2b[L"1"] = true;
+ s2b[L"false"] = false;
+ s2b[L"off"] = false;
+ s2b[L"no"] = false;
+ s2b[L"0"] = false;
+ }
+
+ if(!s2b.Lookup(m_value, b)) // m_status != boolean && m_status != number ||
+ {
+ throw Exception(_T("expected boolean"));
+ }
+ }
+
+ bool Definition::GetAsTime(Time& t, StringMapW<float>& offset, StringMapW<float>* n2n, int default_id)
+ {
+ Definition& time = (*this)[L"time"];
+
+ CStringW id;
+ if(time[L"id"].IsValue()) id = time[L"id"];
+ else id.Format(L"%d", default_id);
+
+ float scale = time[L"scale"].IsValue() ? time[L"scale"] : 1.0f;
+
+ if(time[L"start"].IsValue() && time[L"stop"].IsValue())
+ {
+ time[L"start"].GetAsNumber(t.start, n2n);
+ time[L"stop"].GetAsNumber(t.stop, n2n);
+
+ if(t.start.unit.IsEmpty()) t.start.value *= scale;
+ if(t.stop.unit.IsEmpty()) t.stop.value *= scale;
+
+ float o = 0;
+ offset.Lookup(id, o);
+
+ if(t.start.sign != 0) t.start.value = o + t.start.value;
+ if(t.stop.sign != 0) t.stop.value = t.start.value + t.stop.value;
+
+ offset[id] = t.stop.value;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ Definition::operator LPCWSTR()
+ {
+ CStringW str;
+ GetAsString(str);
+ return str;
+ }
+
+ Definition::operator float()
+ {
+ float d;
+ GetAsNumber(d);
+ return d;
+ }
+
+ Definition::operator bool()
+ {
+ bool b;
+ GetAsBoolean(b);
+ return b;
+ }
+
+ Definition* Definition::SetChildAsValue(CStringW path, status_t s, CStringW v, CStringW u)
+ {
+ Definition* pDef = this;
+
+ Split split('.', path);
+
+ for(size_t i = 0, j = split-1; i <= j; i++)
+ {
+ CStringW type = split[i];
+
+ if(pDef->m_nodes.IsEmpty() || !dynamic_cast<Reference*>(pDef->m_nodes.GetTail()))
+ {
+ EXECUTE_ASSERT(m_pnf->CreateRef(pDef) != NULL);
+ }
+
+ if(Reference* pRef = dynamic_cast<Reference*>(pDef->m_nodes.GetTail()))
+ {
+ pDef = NULL;
+
+ POSITION pos = pRef->m_nodes.GetTailPosition();
+ while(pos)
+ {
+ Definition* pChildDef = dynamic_cast<Definition*>(pRef->m_nodes.GetPrev(pos));
+
+ if(pChildDef->IsType(type))
+ {
+ if(pChildDef->IsNameUnknown()) pDef = pChildDef;
+ break;
+ }
+ }
+
+ if(!pDef)
+ {
+ pDef = m_pnf->CreateDef(pRef, type);
+ }
+
+ if(i == j)
+ {
+ pDef->SetAsValue(s, v, u);
+ return pDef;
+ }
+ }
+ }
+
+ return NULL;
+ }
+
+ Definition* Definition::SetChildAsNumber(CStringW path, CStringW v, CStringW u)
+ {
+ Definition* pDef = SetChildAsValue(path, number, v, u);
+
+ Number<float> n;
+ pDef->GetAsNumber(n); // will throw an exception if not a number
+
+ return pDef;
+ }
+
+ void Definition::Dump(OutputStream& s, int level, bool fLast)
+ {
+ if(m_predefined) return;
+
+ CStringW tabs(' ', level*4);
+
+ CStringW str = tabs;
+ if(m_predefined) str += '?';
+ if(m_priority == PLow) str += '*';
+ else if(m_priority == PHigh) str += '!';
+ if(!IsTypeUnknown() && !m_autotype) str += m_type;
+ if(!IsNameUnknown()) str += '#' + m_name;
+ str += ':';
+ s.PutString(L"%s", str);
+
+ if(!m_nodes.IsEmpty())
+ {
+ POSITION pos = m_nodes.GetHeadPosition();
+ while(pos)
+ {
+ Node* pNode = m_nodes.GetNext(pos);
+
+ if(Reference* pRef = dynamic_cast<Reference*>(pNode))
+ {
+ pRef->Dump(s, level, fLast);
+ }
+ else
+ {
+ ASSERT(!pNode->IsNameUnknown());
+ s.PutString(L" %s", pNode->m_name);
+ }
+ }
+
+ s.PutString(L";\n");
+
+ if(!fLast && (!m_nodes.IsEmpty() || level == 0)) s.PutString(L"\n");
+ }
+ else if(m_status == string)
+ {
+ CStringW str = m_value;
+ str.Replace(L"\"", L"\\\"");
+ s.PutString(L" \"%s\";\n", str);
+ }
+ else if(m_status == number)
+ {
+ CStringW str = m_value;
+ if(!m_unit.IsEmpty()) str += m_unit;
+ s.PutString(L" %s;\n", str);
+ }
+ else if(m_status == boolean)
+ {
+ s.PutString(L" %s;\n", m_value);
+ }
+ else if(m_status == block)
+ {
+ s.PutString(L" {%s};\n", m_value);
+ }
+ else
+ {
+ s.PutString(L" null;\n");
+ }
+ }
+}
diff --git a/src/Subtitles/libssf/Node.h b/src/Subtitles/libssf/Node.h
new file mode 100644
index 000000000..af48390f1
--- /dev/null
+++ b/src/Subtitles/libssf/Node.h
@@ -0,0 +1,122 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include "Stream.h"
+#include "StringMap.h"
+
+namespace ssf
+{
+ class Definition;
+ class NodeFactory;
+
+ enum NodePriority {PLow, PNormal, PHigh};
+
+ class Node
+ {
+ protected:
+ NodeFactory* m_pnf;
+
+ public:
+ Node* m_parent;
+ CAtlList<Node*> m_nodes;
+ StringMapW<Node*> m_name2node;
+ CStringW m_type, m_name;
+ NodePriority m_priority;
+ bool m_predefined;
+
+ Node(NodeFactory* pnf, CStringW name);
+ virtual ~Node() {}
+
+ bool IsNameUnknown();
+ bool IsTypeUnknown();
+ bool IsType(CStringW type);
+
+ virtual void AddTail(Node* pNode);
+ virtual void GetChildDefs(CAtlList<Definition*>& l, LPCWSTR type = NULL, bool fFirst = true);
+ virtual void Dump(OutputStream& s, int level = 0, bool fLast = false) = 0;
+ };
+
+ class Reference : public Node
+ {
+ public:
+ Reference(NodeFactory* pnf, CStringW name);
+ virtual ~Reference();
+
+ void GetChildDefs(CAtlList<Definition*>& l, LPCWSTR type = NULL, bool fFirst = true);
+ void Dump(OutputStream& s, int level = 0, bool fLast = false);
+ };
+
+ class Definition : public Node
+ {
+ public:
+ template<typename T> struct Number {T value; int sign; CStringW unit;};
+ struct Time {Number<float> start, stop;};
+
+ enum status_t {node, string, number, boolean, block};
+
+ private:
+ status_t m_status;
+ bool m_autotype;
+ CStringW m_value, m_unit;
+ Number<float> m_num;
+ CStringW m_num_string;
+
+ StringMapW<Definition*> m_type2def;
+ void RemoveFromCache(LPCWSTR type = NULL);
+
+ template<typename T>
+ void GetAsNumber(Number<T>& n, StringMapW<T>* n2n = NULL);
+
+ public:
+ Definition(NodeFactory* pnf, CStringW name);
+ virtual ~Definition();
+
+ bool IsVisible(Definition* pDef);
+
+ void AddTail(Node* pNode);
+ void Dump(OutputStream& s, int level = 0, bool fLast = false);
+
+ Definition& operator[] (LPCWSTR type);
+
+ bool IsValue(status_t s = (status_t)0);
+
+ void SetAsValue(status_t s, CStringW v, CStringW u = L"");
+ void SetAsNumber(CStringW v, CStringW u = L"");
+
+ void GetAsString(CStringW& str);
+ void GetAsNumber(Number<int>& n, StringMapW<int>* n2n = NULL);
+ void GetAsNumber(Number<DWORD>& n, StringMapW<DWORD>* n2n = NULL);
+ void GetAsNumber(Number<float>& n, StringMapW<float>* n2n = NULL);
+ template<typename T>
+ void GetAsNumber(T& t, StringMapW<T>* n2n = NULL) {Number<T> n; GetAsNumber(n, n2n); t = n.value;}
+ void GetAsBoolean(bool& b);
+ bool GetAsTime(Time& t, StringMapW<float>& offset, StringMapW<float>* n2n = NULL, int default_id = 0);
+
+ operator LPCWSTR();
+ operator float();
+ operator bool();
+
+ Definition* SetChildAsValue(CStringW path, status_t s, CStringW v, CStringW u = L"");
+ Definition* SetChildAsNumber(CStringW path, CStringW v, CStringW u = L"");
+ };
+}
diff --git a/src/Subtitles/libssf/NodeFactory.cpp b/src/Subtitles/libssf/NodeFactory.cpp
new file mode 100644
index 000000000..a20da1ad4
--- /dev/null
+++ b/src/Subtitles/libssf/NodeFactory.cpp
@@ -0,0 +1,182 @@
+/*
+ * 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 "NodeFactory.h"
+#include "Exception.h"
+
+namespace ssf
+{
+ NodeFactory::NodeFactory()
+ : m_counter(0)
+ , m_root(NULL)
+ , m_predefined(false)
+ {
+ }
+
+ NodeFactory::~NodeFactory()
+ {
+ RemoveAll();
+ }
+
+ CStringW NodeFactory::GenName()
+ {
+ CStringW name;
+ name.Format(L"%I64d", m_counter++);
+ return name;
+ }
+
+ void NodeFactory::RemoveAll()
+ {
+ m_root = NULL;
+
+ POSITION pos = m_nodes.GetStartPosition();
+ while(pos) delete m_nodes.GetNextValue(pos);
+ m_nodes.RemoveAll();
+
+ m_newnodes.RemoveAll();
+ }
+
+ void NodeFactory::Commit()
+ {
+ m_newnodes.RemoveAll();
+ }
+
+ void NodeFactory::Rollback()
+ {
+ POSITION pos = m_newnodes.GetTailPosition();
+ while(pos)
+ {
+ if(StringMap<Node*, CStringW>::CPair* p = m_nodes.Lookup(m_newnodes.GetPrev(pos)))
+ {
+ delete p->m_value; // TODO: remove it from "parent"->m_nodes too
+ m_nodes.RemoveKey(p->m_key);
+ }
+ }
+ }
+
+ Reference* NodeFactory::CreateRootRef()
+ {
+ RemoveAll();
+ m_root = CreateRef(NULL);
+ return m_root;
+ }
+
+ Reference* NodeFactory::GetRootRef() const
+ {
+ ASSERT(m_root);
+ return m_root;
+ }
+
+ Reference* NodeFactory::CreateRef(Definition* pParentDef)
+ {
+ CStringW name = GenName();
+
+ Reference* pRef = DNew Reference(this, name);
+
+ m_nodes.SetAt(name, pRef);
+ m_newnodes.AddTail(name);
+
+ if(pParentDef)
+ {
+ pParentDef->AddTail(pRef);
+ pRef->m_parent = pParentDef;
+ }
+
+ return pRef;
+ }
+
+ Definition* NodeFactory::CreateDef(Reference* pParentRef, CStringW type, CStringW name, NodePriority priority)
+ {
+ Definition* pDef = NULL;
+
+ if(name.IsEmpty())
+ {
+ name = GenName();
+ }
+ else
+ {
+ pDef = GetDefByName(name);
+
+ if(pDef)
+ {
+ if(!pDef->m_predefined)
+ {
+ throw Exception(_T("redefinition of '%s' is not allowed"), CString(name));
+ }
+
+ if(!pDef->IsTypeUnknown() && !pDef->IsType(type))
+ {
+ throw Exception(_T("cannot redefine type of %s to %s"), CString(name), CString(type));
+ }
+ }
+ }
+
+ if(!pDef)
+ {
+ pDef = DNew Definition(this, name);
+
+ m_nodes.SetAt(name, pDef);
+ m_newnodes.AddTail(name);
+
+ if(pParentRef)
+ {
+ pParentRef->AddTail(pDef);
+ pDef->m_parent = pParentRef;
+ }
+ }
+
+ pDef->m_type = type;
+ pDef->m_priority = priority;
+ pDef->m_predefined = m_predefined;
+
+ return pDef;
+ }
+
+ Definition* NodeFactory::GetDefByName(CStringW name) const
+ {
+ Node* pNode = NULL;
+ m_nodes.Lookup(name, pNode);
+ return dynamic_cast<Definition*>(pNode);
+ }
+
+ void NodeFactory::GetNewDefs(CAtlList<Definition*>& defs)
+ {
+ defs.RemoveAll();
+
+ POSITION pos = m_newnodes.GetHeadPosition();
+ while(pos)
+ {
+ if(Definition* pDef = GetDefByName(m_newnodes.GetNext(pos)))
+ {
+ defs.AddTail(pDef);
+ }
+ }
+ }
+
+ void NodeFactory::Dump(OutputStream& s) const
+ {
+ if(!m_root) return;
+
+ POSITION pos = m_root->m_nodes.GetHeadPosition();
+ while(pos) m_root->m_nodes.GetNext(pos)->Dump(s);
+ }
+}
diff --git a/src/Subtitles/libssf/NodeFactory.h b/src/Subtitles/libssf/NodeFactory.h
new file mode 100644
index 000000000..9629485e8
--- /dev/null
+++ b/src/Subtitles/libssf/NodeFactory.h
@@ -0,0 +1,58 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include "Node.h"
+
+namespace ssf
+{
+ class NodeFactory
+ {
+ Reference* m_root;
+ StringMapW<Node*> m_nodes;
+ CAtlList<CStringW> m_newnodes;
+ bool m_predefined;
+
+ unsigned __int64 m_counter;
+ CStringW GenName();
+
+ public:
+ NodeFactory();
+ virtual ~NodeFactory();
+
+ virtual void RemoveAll();
+
+ void SetPredefined(bool predefined) {m_predefined = predefined;}
+
+ void Commit();
+ void Rollback();
+
+ Reference* CreateRootRef();
+ Reference* GetRootRef() const;
+ Reference* CreateRef(Definition* pParentDef);
+ Definition* CreateDef(Reference* pParentRef = NULL, CStringW type = L"", CStringW name = L"", NodePriority priority = PNormal);
+ Definition* GetDefByName(CStringW name) const;
+ void GetNewDefs(CAtlList<Definition*>& defs);
+
+ void Dump(OutputStream& s) const;
+ };
+}
diff --git a/src/Subtitles/libssf/Rasterizer.cpp b/src/Subtitles/libssf/Rasterizer.cpp
new file mode 100644
index 000000000..5081b1553
--- /dev/null
+++ b/src/Subtitles/libssf/Rasterizer.cpp
@@ -0,0 +1,706 @@
+/*
+ * 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
+ *
+ * Based on the rasterizer of virtualdub's subtitler plugin
+ */
+
+#include "stdafx.h"
+#include <vector>
+#include <algorithm>
+#include "Rasterizer.h"
+#include "Glyph.h"
+
+#define FONT_AA 3
+#define FONT_SCALE (6-FONT_AA)
+
+// FONT_AA: 0 - 3
+
+
+namespace ssf
+{
+ template<class T> T mymax(T a, T b) {return a > b ? a : b;}
+ template<class T> T mymin(T a, T b) {return a < b ? a : b;}
+
+ Rasterizer::Rasterizer()
+ {
+ mpOverlayBuffer = NULL;
+ mOverlayWidth = mOverlayHeight = 0;
+ mPathOffsetX = mPathOffsetY = 0;
+ mOffsetX = mOffsetY = 0;
+ }
+
+ Rasterizer::~Rasterizer()
+ {
+ _TrashOverlay();
+ }
+
+ void Rasterizer::_TrashOverlay()
+ {
+ if(mpOverlayBuffer) delete [] mpOverlayBuffer;
+ mpOverlayBuffer = NULL;
+ }
+
+ void Rasterizer::_ReallocEdgeBuffer(int edges)
+ {
+ mEdgeHeapSize = edges;
+ mpEdgeBuffer = (Edge*)realloc(mpEdgeBuffer, sizeof(Edge)*edges);
+ }
+
+ void Rasterizer::_EvaluateBezier(const CPoint& p0, const CPoint& p1, const CPoint& p2, const CPoint& p3)
+ {
+ if(abs(p0.x + p2.x - p1.x*2) +
+ abs(p0.y + p2.y - p1.y*2) +
+ abs(p1.x + p3.x - p2.x*2) +
+ abs(p1.y + p3.y - p2.y*2) <= max(2, 1<<FONT_AA))
+ {
+ _EvaluateLine(p0, p3);
+ }
+ else
+ {
+ CPoint p01, p12, p23, p012, p123, p0123;
+
+ p01.x = (p0.x + p1.x + 1) >> 1;
+ p01.y = (p0.y + p1.y + 1) >> 1;
+ p12.x = (p1.x + p2.x + 1) >> 1;
+ p12.y = (p1.y + p2.y + 1) >> 1;
+ p23.x = (p2.x + p3.x + 1) >> 1;
+ p23.y = (p2.y + p3.y + 1) >> 1;
+ p012.x = (p01.x + p12.x + 1) >> 1;
+ p012.y = (p01.y + p12.y + 1) >> 1;
+ p123.x = (p12.x + p23.x + 1) >> 1;
+ p123.y = (p12.y + p23.y + 1) >> 1;
+ p0123.x = (p012.x + p123.x + 1) >> 1;
+ p0123.y = (p012.y + p123.y + 1) >> 1;
+
+ _EvaluateBezier(p0, p01, p012, p0123);
+ _EvaluateBezier(p0123, p123, p23, p3);
+ }
+ }
+
+ void Rasterizer::_EvaluateLine(CPoint p0, CPoint p1)
+ {
+ if(lastp != p0)
+ {
+ _EvaluateLine(lastp, p0);
+ }
+
+ if(!fFirstSet)
+ {
+ firstp = p0;
+ fFirstSet = true;
+ }
+
+ lastp = p1;
+
+ // TODO: ((1<<FONT_SCALE)/2+-1)
+
+ if(p1.y > p0.y) // down
+ {
+ int xacc = p0.x << (8 - FONT_SCALE);
+
+ // prestep p0.y down
+
+ int dy = p1.y - p0.y;
+ int y = ((p0.y + ((1<<FONT_SCALE)/2-1)) & ~((1<<FONT_SCALE)-1)) + (1<<FONT_SCALE)/2;
+ int iy = y >> FONT_SCALE;
+
+ p1.y = (p1.y - ((1<<FONT_SCALE)/2+1)) >> FONT_SCALE;
+
+ if(iy <= p1.y)
+ {
+ int invslope = ((p1.x - p0.x) << 8) / dy;
+
+ while(mEdgeNext + p1.y + 1 - iy > mEdgeHeapSize)
+ _ReallocEdgeBuffer(mEdgeHeapSize*2);
+
+ xacc += (invslope * (y - p0.y)) >> FONT_SCALE;
+
+ while(iy <= p1.y)
+ {
+ int ix = (xacc + 128) >> 8;
+
+ mpEdgeBuffer[mEdgeNext].next = mpScanBuffer[iy];
+ mpEdgeBuffer[mEdgeNext].posandflag = ix*2 + 1;
+
+ mpScanBuffer[iy] = mEdgeNext++;
+
+ ++iy;
+ xacc += invslope;
+ }
+ }
+ }
+ else if(p1.y < p0.y) // up
+ {
+ int xacc = p1.x << (8 - FONT_SCALE);
+
+ // prestep p1.y down
+
+ int dy = p0.y - p1.y;
+ int y = ((p1.y + ((1<<FONT_SCALE)/2-1)) & ~((1<<FONT_SCALE)-1)) + (1<<FONT_SCALE)/2;
+ int iy = y >> FONT_SCALE;
+
+ p0.y = (p0.y - ((1<<FONT_SCALE)/2+1)) >> FONT_SCALE;
+
+ if(iy <= p0.y)
+ {
+ int invslope = ((p0.x - p1.x) << 8) / dy;
+
+ while(mEdgeNext + p0.y + 1 - iy > mEdgeHeapSize)
+ _ReallocEdgeBuffer(mEdgeHeapSize*2);
+
+ xacc += (invslope * (y - p1.y)) >> FONT_SCALE;
+
+ while(iy <= p0.y)
+ {
+ int ix = (xacc + 128) >> 8;
+
+ mpEdgeBuffer[mEdgeNext].next = mpScanBuffer[iy];
+ mpEdgeBuffer[mEdgeNext].posandflag = ix*2;
+
+ mpScanBuffer[iy] = mEdgeNext++;
+
+ ++iy;
+ xacc += invslope;
+ }
+ }
+ }
+ }
+
+ bool Rasterizer::ScanConvert(GlyphPath& path, const CRect& bbox)
+ {
+ // Drop any outlines we may have.
+
+ mOutline.RemoveAll();
+ mWideOutline.RemoveAll();
+
+ if(path.types.IsEmpty() || path.points.IsEmpty() || bbox.IsRectEmpty())
+ {
+ mPathOffsetX = mPathOffsetY = 0;
+ mWidth = mHeight = 0;
+ return 0;
+ }
+
+ int minx = (bbox.left >> FONT_SCALE) & ~((1<<FONT_SCALE)-1);
+ int miny = (bbox.top >> FONT_SCALE) & ~((1<<FONT_SCALE)-1);
+ int maxx = (bbox.right + ((1<<FONT_SCALE)-1)) >> FONT_SCALE;
+ int maxy = (bbox.bottom + ((1<<FONT_SCALE)-1)) >> FONT_SCALE;
+
+ path.MovePoints(CPoint(-minx*(1<<FONT_SCALE), -miny*(1<<FONT_SCALE)));
+
+ if(minx > maxx || miny > maxy)
+ {
+ mWidth = mHeight = 0;
+ mPathOffsetX = mPathOffsetY = 0;
+ return true;
+ }
+
+ mWidth = maxx + 1 - minx;
+ mHeight = maxy + 1 - miny;
+
+ mPathOffsetX = minx;
+ mPathOffsetY = miny;
+
+ // Initialize edge buffer. We use edge 0 as a sentinel.
+
+ mEdgeNext = 1;
+ mEdgeHeapSize = 0x10000;
+ mpEdgeBuffer = (Edge*)malloc(sizeof(Edge)*mEdgeHeapSize);
+
+ // Initialize scanline list.
+
+ mpScanBuffer = DNew unsigned int[mHeight];
+ memset(mpScanBuffer, 0, mHeight*sizeof(unsigned int));
+
+ // Scan convert the outline. Yuck, Bezier curves....
+
+ // Unfortunately, Windows 95/98 GDI has a bad habit of giving us text
+ // paths with all but the first figure left open, so we can't rely
+ // on the PT_CLOSEFIGURE flag being used appropriately.
+
+ fFirstSet = false;
+ firstp.x = firstp.y = 0;
+ lastp.x = lastp.y = 0;
+
+ int lastmoveto = -1;
+
+ BYTE* type = path.types.GetData();
+ POINT* pt = path.points.GetData();
+
+ for(size_t i = 0, j = path.types.GetCount(); i < j; i++)
+ {
+ switch(type[i] & ~PT_CLOSEFIGURE)
+ {
+ case PT_MOVETO:
+ if(lastmoveto >= 0 && firstp != lastp) _EvaluateLine(lastp, firstp);
+ lastmoveto = int(i);
+ fFirstSet = false;
+ lastp = pt[i];
+ break;
+ case PT_LINETO:
+ if(j - (i-1) >= 2) _EvaluateLine(pt[i-1], pt[i]);
+ break;
+ case PT_BEZIERTO:
+ if(j - (i-1) >= 4) _EvaluateBezier(pt[i-1], pt[i], pt[i+1], pt[i+2]);
+ i += 2;
+ break;
+ }
+ }
+
+ if(lastmoveto >= 0 && firstp != lastp) _EvaluateLine(lastp, firstp);
+
+ // Convert the edges to spans. We couldn't do this before because some of
+ // the regions may have winding numbers >+1 and it would have been a pain
+ // to try to adjust the spans on the fly. We use one heap to detangle
+ // a scanline's worth of edges from the singly-linked lists, and another
+ // to collect the actual scans.
+
+ std::vector<int> heap;
+
+ mOutline.SetCount(0, mEdgeNext / 2);
+
+ for(int y = 0; y < mHeight; y++)
+ {
+ int count = 0;
+
+ // Detangle scanline into edge heap.
+
+ for(unsigned int ptr = mpScanBuffer[y]; ptr; ptr = mpEdgeBuffer[ptr].next)
+ {
+ heap.push_back(mpEdgeBuffer[ptr].posandflag);
+ }
+
+ // Sort edge heap. Note that we conveniently made the opening edges
+ // one more than closing edges at the same spot, so we won't have any
+ // problems with abutting spans.
+
+ std::sort(heap.begin(), heap.end());
+
+ // Process edges and add spans. Since we only check for a non-zero
+ // winding number, it doesn't matter which way the outlines go!
+
+ std::vector<int>::iterator itX1 = heap.begin();
+ std::vector<int>::iterator itX2 = heap.end();
+
+ int x1 = 0;
+
+ for(; itX1 != itX2; ++itX1)
+ {
+ int x = *itX1;
+
+ if(!count)
+ x1 = x >> 1;
+
+ if(x&1) ++count;
+ else --count;
+
+ if(!count)
+ {
+ int x2 = x >> 1;
+
+ if(x2 > x1)
+ {
+ Span s(x1, y, x2, y);
+ s.first += 0x4000000040000000i64;
+ s.second += 0x4000000040000000i64;
+ mOutline.Add(s);
+ }
+ }
+ }
+
+ heap.clear();
+ }
+
+ // Dump the edge and scan buffers, since we no longer need them.
+
+ free(mpEdgeBuffer);
+ delete [] mpScanBuffer;
+
+ // All done!
+
+ return true;
+ }
+
+ void Rasterizer::_OverlapRegion(Array<Span>& dst, Array<Span>& src, int dx, int dy)
+ {
+ mWideOutlineTmp.Move(dst);
+
+ Span* a = mWideOutlineTmp.GetData();
+ Span* ae = a + mWideOutlineTmp.GetCount();
+ Span* b = src.GetData();
+ Span* be = b + src.GetCount();
+
+ Span o(0, dy, 0, dy);
+ o.first -= dx;
+ o.second += dx;
+
+ while(a != ae && b != be)
+ {
+ Span x;
+
+ if(b->first + o.first < a->first)
+ {
+ // B span is earlier. Use it.
+
+ x.first = b->first + o.first;
+ x.second = b->second + o.second;
+
+ b++;
+
+ // B spans don't overlap, so begin merge loop with A first.
+
+ for(;;)
+ {
+ // If we run out of A spans or the A span doesn't overlap,
+ // then the next B span can't either (because B spans don't
+ // overlap) and we exit.
+
+ if(a == ae || a->first > x.second)
+ break;
+
+ do {x.second = mymax(x.second, a->second);}
+ while(++a != ae && a->first <= x.second);
+
+ // If we run out of B spans or the B span doesn't overlap,
+ // then the next A span can't either (because A spans don't
+ // overlap) and we exit.
+
+ if(b == be || b->first + o.first > x.second)
+ break;
+
+ do {x.second = mymax(x.second, b->second + o.second);}
+ while(++b != be && b->first + o.first <= x.second);
+ }
+ }
+ else
+ {
+ // A span is earlier. Use it.
+
+ x = *a;
+
+ a++;
+
+ // A spans don't overlap, so begin merge loop with B first.
+
+ for(;;)
+ {
+ // If we run out of B spans or the B span doesn't overlap,
+ // then the next A span can't either (because A spans don't
+ // overlap) and we exit.
+
+ if(b == be || b->first + o.first > x.second)
+ break;
+
+ do {x.second = mymax(x.second, b->second + o.second);}
+ while(++b != be && b->first + o.first <= x.second);
+
+ // If we run out of A spans or the A span doesn't overlap,
+ // then the next B span can't either (because B spans don't
+ // overlap) and we exit.
+
+ if(a == ae || a->first > x.second)
+ break;
+
+ do {x.second = mymax(x.second, a->second);}
+ while(++a != ae && a->first <= x.second);
+ }
+ }
+
+ // Flush span.
+
+ dst.Add(x);
+ }
+
+ // Copy over leftover spans.
+
+ dst.Append(a, ae - a);
+
+ for(; b != be; b++)
+ {
+ dst.Add(Span(b->first + o.first, b->second + o.second));
+ }
+ }
+
+ bool Rasterizer::CreateWidenedRegion(int r)
+ {
+ if(r < 0) r = 0;
+
+ r >>= FONT_SCALE;
+
+ for(int y = -r; y <= r; ++y)
+ {
+ int x = (int)(0.5f + sqrt(float(r*r - y*y)));
+
+ _OverlapRegion(mWideOutline, mOutline, x, y);
+ }
+
+ mWideBorder = r;
+
+ return true;
+ }
+
+ bool Rasterizer::Rasterize(int xsub, int ysub)
+ {
+ _TrashOverlay();
+
+ if(!mWidth || !mHeight)
+ {
+ mOverlayWidth = mOverlayHeight = 0;
+ return true;
+ }
+
+ xsub >>= FONT_SCALE;
+ ysub >>= FONT_SCALE;
+
+ xsub &= (1<<FONT_AA)-1;
+ ysub &= (1<<FONT_AA)-1;
+
+ int width = mWidth + xsub;
+ int height = mHeight + ysub;
+
+ mOffsetX = mPathOffsetX - xsub;
+ mOffsetY = mPathOffsetY - ysub;
+
+ int border = ((mWideBorder + ((1<<FONT_AA)-1)) & ~((1<<FONT_AA)-1)) + (1<<FONT_AA)*4;
+
+ if(!mWideOutline.IsEmpty())
+ {
+ width += 2*border;
+ height += 2*border;
+
+ xsub += border;
+ ysub += border;
+
+ mOffsetX -= border;
+ mOffsetY -= border;
+ }
+
+ mOverlayWidth = ((width + ((1<<FONT_AA)-1)) >> FONT_AA) + 1;
+ mOverlayHeight = ((height + ((1<<FONT_AA)-1)) >> FONT_AA) + 1;
+
+ mpOverlayBuffer = DNew BYTE[4 * mOverlayWidth * mOverlayHeight];
+ memset(mpOverlayBuffer, 0, 4 * mOverlayWidth * mOverlayHeight);
+
+ Array<Span>* pOutline[2] = {&mOutline, &mWideOutline};
+
+ for(int i = 0; i < countof(pOutline); i++)
+ {
+ const Span* s = pOutline[i]->GetData();
+
+ for(size_t j = 0, k = pOutline[i]->GetCount(); j < k; j++)
+ {
+ int y = s[j].y1 - 0x40000000 + ysub;
+ int x1 = s[j].x1 - 0x40000000 + xsub;
+ int x2 = s[j].x2 - 0x40000000 + xsub;
+
+ if(x2 > x1)
+ {
+ int first = x1 >> FONT_AA;
+ int last = (x2-1) >> FONT_AA;
+
+ BYTE* dst = mpOverlayBuffer + 4*(mOverlayWidth * (y >> FONT_AA) + first) + i;
+
+ if(first == last)
+ {
+ *dst += x2 - x1;
+ }
+ else
+ {
+ *dst += (((first+1) << FONT_AA) - x1) << (6 - FONT_AA*2);
+ dst += 4;
+
+ while(++first < last)
+ {
+ *dst += (1 << FONT_AA) << (6 - FONT_AA*2);
+ dst += 4;
+ }
+
+ *dst += (x2 - (last << FONT_AA)) << (6 - FONT_AA*2);
+ }
+ }
+ }
+ }
+
+ if(!mWideOutline.IsEmpty())
+ {
+ BYTE* p = mpOverlayBuffer;
+
+ for(int j = 0; j < mOverlayHeight; j++, p += mOverlayWidth*4)
+ {
+ for(int i = 0; i < mOverlayWidth; i++)
+ {
+ p[i*4+2] = min(p[i*4+1], (1<<6) - p[i*4]); // TODO: sse2
+ }
+ }
+ }
+
+ return true;
+ }
+
+ void Rasterizer::Blur(float n, int plane)
+ {
+ if(n <= 0 || !mOverlayWidth || !mOverlayHeight || !mpOverlayBuffer)
+ return;
+
+ int w = mOverlayWidth;
+ int h = mOverlayHeight;
+ int pitch = w*4;
+ BYTE* q0 = DNew BYTE[w*h];
+
+ for(int pass = 0; pass < n; pass++)
+ {
+ BYTE* p = mpOverlayBuffer + plane;
+ BYTE* q = q0;
+
+ for(int y = 0; y < h; y++, p += pitch, q += w)
+ {
+ q[0] = (2*p[0] + p[4]) >> 2;
+ int x = 0;
+ for(x = 1; x < w-1; x++)
+ q[x] = (p[(x-1)*4] + 2*p[x*4] + p[(x+1)*4]) >> 2;
+ q[x] = (p[(x-1)*4] + 2*p[x*4]) >> 2;
+ }
+
+ p = mpOverlayBuffer + plane;
+ q = q0;
+
+ for(int x = 0; x < w; x++, p += 4, q++)
+ {
+ p[0] = (2*q[0] + q[w]) >> 2;
+ int y = 0, yp, yq;
+ for(y = 1, yp = y*pitch, yq = y*w; y < h-1; y++, yp += pitch, yq += w)
+ p[yp] = (q[yq-w] + 2*q[yq] + q[yq+w]) >> 2;
+ p[yp] = (q[yq-w] + 2*q[yq]) >> 2;
+ }
+ }
+
+ delete [] q0;
+ }
+
+ void Rasterizer::Reuse(Rasterizer& r)
+ {
+ mWidth = r.mWidth;
+ mHeight = r.mHeight;
+ mPathOffsetX = r.mPathOffsetX;
+ mPathOffsetY = r.mPathOffsetY;
+ mWideBorder = r.mWideBorder;
+ mOutline.Move(r.mOutline);
+ mWideOutline.Move(r.mWideOutline);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ static __forceinline void pixmix_c(DWORD* dst, DWORD color, DWORD alpha)
+ {
+ int a = ((alpha * (color>>24)) >> 6) & 0xff;
+ int ia = 0xff - a;
+
+ *dst = ((((*dst & 0x00ff00ff)*ia + (color & 0x00ff00ff)*a) & 0xff00ff00) >> 8)
+ | ((((*dst>>8) & 0x00ff00ff)*ia + ((color>>8) & 0x000000ff)*a) & 0xff00ff00);
+ }
+
+ static __forceinline void pixmix_sse2(DWORD* dst, DWORD color, DWORD alpha)
+ {
+ alpha = ((alpha * (color>>24)) >> 6) & 0xff;
+ color &= 0xffffff;
+
+ __m128i zero = _mm_setzero_si128();
+ __m128i a = _mm_set1_epi32((alpha << 16) | (0xff - alpha));
+ __m128i d = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*dst), zero);
+ __m128i s = _mm_unpacklo_epi8(_mm_cvtsi32_si128(color), zero);
+ __m128i r = _mm_unpacklo_epi16(d, s);
+
+ r = _mm_madd_epi16(r, a);
+ r = _mm_srli_epi32(r, 8);
+ r = _mm_packs_epi32(r, r);
+ r = _mm_packus_epi16(r, r);
+
+ *dst = (DWORD)_mm_cvtsi128_si32(r);
+ }
+
+ CRect Rasterizer::Draw(const SubPicDesc& spd, const CRect& clip, int xsub, int ysub, const DWORD* switchpts, int plane)
+ {
+ CRect bbox(0, 0, 0, 0);
+
+ if(!switchpts) return bbox;
+
+ // clip
+
+ CRect r(0, 0, spd.w, spd.h);
+ r &= clip;
+
+ xsub >>= FONT_SCALE;
+ ysub >>= FONT_SCALE;
+
+ int x = (xsub + mOffsetX + (1<<FONT_AA)/2) >> FONT_AA;
+ int y = (ysub + mOffsetY + (1<<FONT_AA)/2) >> FONT_AA;
+ int w = mOverlayWidth;
+ int h = mOverlayHeight;
+ int xo = 0, yo = 0;
+
+ if(x < r.left) {xo = r.left - x; w -= r.left - x; x = r.left;}
+ if(y < r.top) {yo = r.top - y; h -= r.top - y; y = r.top;}
+ if(x+w > r.right) w = r.right - x;
+ if(y+h > r.bottom) h = r.bottom - y;
+
+ if(w <= 0 || h <= 0) return bbox;
+
+ bbox.SetRect(x, y, x + w, y + h);
+ bbox &= CRect(0, 0, spd.w, spd.h);
+
+ // draw
+
+ const BYTE* src = mpOverlayBuffer + 4*(mOverlayWidth * yo + xo) + plane;
+ DWORD* dst = (DWORD*)((BYTE*)spd.bits + spd.pitch * y) + x;
+
+ DWORD color = switchpts[0];
+
+ bool fSSE2 = !!(g_cpuid.m_flags & CCpuID::sse2);
+
+ while(h--)
+ {
+ if(switchpts[1] == 0xffffffff)
+ {
+ if(fSSE2) for(int wt=0; wt<w; ++wt) pixmix_sse2(&dst[wt], color, src[wt*4]);
+ else for(int wt=0; wt<w; ++wt) pixmix_c(&dst[wt], color, src[wt*4]);
+ }
+ else
+ {
+ const DWORD* sw = switchpts;
+
+ if(fSSE2)
+ for(int wt=0; wt<w; ++wt)
+ {
+ if(wt+xo >= sw[1]) {while(wt+xo >= sw[1]) sw += 2; color = sw[-2];}
+ pixmix_sse2(&dst[wt], color, src[wt*4]);
+ }
+ else
+ for(int wt=0; wt<w; ++wt)
+ {
+ if(wt+xo >= sw[1]) {while(wt+xo >= sw[1]) sw += 2; color = sw[-2];}
+ pixmix_c(&dst[wt], color, src[wt*4]);
+ }
+ }
+
+ src += 4*mOverlayWidth;
+ dst = (DWORD*)((BYTE*)dst + spd.pitch);
+ }
+
+ return bbox;
+ }
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/Rasterizer.h b/src/Subtitles/libssf/Rasterizer.h
new file mode 100644
index 000000000..9c6815944
--- /dev/null
+++ b/src/Subtitles/libssf/Rasterizer.h
@@ -0,0 +1,82 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include <atlcoll.h>
+#include "SubtitleFile.h"
+#include "Array.h"
+#include "GlyphPath.h"
+#include "../../SubPic/ISubPic.h"
+
+namespace ssf
+{
+ class Rasterizer
+ {
+ bool fFirstSet;
+ CPoint firstp, lastp;
+
+ private:
+ int mWidth, mHeight;
+
+ union Span
+ {
+ struct {int x1, y1, x2, y2;};
+ struct {unsigned __int64 first, second;};
+ union Span() {}
+ union Span(int _x1, int _y1, int _x2, int _y2) {x1 = _x1; y1 = _y1; x2 = _x2; y2 = _y2;}
+ union Span(unsigned __int64 _first, unsigned __int64 _second) {first = _first; second = _second;}
+ };
+
+ Array<Span> mOutline, mWideOutline, mWideOutlineTmp;
+ int mWideBorder;
+
+ struct Edge {int next, posandflag;}* mpEdgeBuffer;
+ unsigned int mEdgeHeapSize;
+ unsigned int mEdgeNext;
+ unsigned int* mpScanBuffer;
+
+ protected:
+ BYTE* mpOverlayBuffer;
+ int mOverlayWidth, mOverlayHeight;
+ int mPathOffsetX, mPathOffsetY;
+ int mOffsetX, mOffsetY;
+
+ private:
+ void _TrashOverlay();
+ void _ReallocEdgeBuffer(int edges);
+ void _EvaluateBezier(const CPoint& p0, const CPoint& p1, const CPoint& p2, const CPoint& p3);
+ void _EvaluateLine(CPoint p0, CPoint p1);
+ void _OverlapRegion(Array<Span>& dst, Array<Span>& src, int dx, int dy);
+
+ public:
+ Rasterizer();
+ virtual ~Rasterizer();
+
+ bool ScanConvert(GlyphPath& path, const CRect& bbox);
+ bool CreateWidenedRegion(int r);
+ bool Rasterize(int xsub, int ysub);
+ void Reuse(Rasterizer& r);
+
+ void Blur(float n, int plane);
+ CRect Draw(const SubPicDesc& spd, const CRect& clip, int xsub, int ysub, const DWORD* switchpts, int plane);
+ };
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/Renderer.cpp b/src/Subtitles/libssf/Renderer.cpp
new file mode 100644
index 000000000..7597b5b4f
--- /dev/null
+++ b/src/Subtitles/libssf/Renderer.cpp
@@ -0,0 +1,820 @@
+/*
+ * 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
+ *
+ * TODO: do something about bidi (at least handle these ranges: 0590-07BF, FB1D-FDFF, FE70-FEFF)
+ *
+ */
+
+#include "stdafx.h"
+#include "Renderer.h"
+#include "Arabic.h"
+
+namespace ssf
+{
+ template <class T>
+ void ReverseList(T& l)
+ {
+ POSITION pos = l.GetHeadPosition();
+ while(pos)
+ {
+ POSITION cur = pos;
+ l.GetNext(pos);
+ l.AddHead(l.GetAt(cur));
+ l.RemoveAt(cur);
+ }
+ }
+
+ static CPoint GetAlignPoint(const Placement& placement, const Size& scale, const CRect& frame, const CSize& size)
+ {
+ CPoint p;
+
+ p.x = frame.left;
+ p.x += placement.pos.auto_x
+ ? placement.align.h * (frame.Width() - size.cx)
+ : placement.pos.x * scale.cx - placement.align.h * size.cx;
+
+ p.y = frame.top;
+ p.y += placement.pos.auto_y
+ ? placement.align.v * (frame.Height() - size.cy)
+ : placement.pos.y * scale.cy - placement.align.v * size.cy;
+
+ return p;
+ }
+
+ static CPoint GetAlignPoint(const Placement& placement, const Size& scale, const CRect& frame)
+ {
+ CSize size(0, 0);
+ return GetAlignPoint(placement, scale, frame, size);
+ }
+
+ //
+
+ Renderer::Renderer()
+ {
+ m_hDC = CreateCompatibleDC(NULL);
+ SetBkMode(m_hDC, TRANSPARENT);
+ SetTextColor(m_hDC, 0xffffff);
+ SetMapMode(m_hDC, MM_TEXT);
+ }
+
+ Renderer::~Renderer()
+ {
+ DeleteDC(m_hDC);
+ }
+
+ void Renderer::NextSegment(const CAutoPtrList<Subtitle>& subs)
+ {
+ StringMapW<bool> names;
+ POSITION pos = subs.GetHeadPosition();
+ while(pos) names[subs.GetNext(pos)->m_name] = true;
+
+ pos = m_sra.GetStartPosition();
+ while(pos)
+ {
+ POSITION cur = pos;
+ const CStringW& name = m_sra.GetNextKey(pos);
+ if(!names.Lookup(name)) m_sra.RemoveAtPos(cur);
+ }
+ }
+
+ RenderedSubtitle* Renderer::Lookup(const Subtitle* s, const CSize& vs, const CRect& vr)
+ {
+ m_sra.UpdateTarget(vs, vr);
+
+ if(s->m_text.IsEmpty())
+ return NULL;
+
+ CRect spdrc = s->m_frame.reference == _T("video") ? vr : CRect(CPoint(0, 0), vs);
+
+ if(spdrc.IsRectEmpty())
+ return NULL;
+
+ RenderedSubtitle* rs = NULL;
+
+ if(m_rsc.Lookup(s->m_name, rs))
+ {
+ if(!s->m_animated && rs->m_spdrc == spdrc)
+ return rs;
+
+ m_rsc.Invalidate(s->m_name);
+ }
+
+ const Style& style = s->m_text.GetHead().style;
+
+ Size scale;
+
+ scale.cx = (float)spdrc.Width() / s->m_frame.resolution.cx;
+ scale.cy = (float)spdrc.Height() / s->m_frame.resolution.cy;
+
+ CRect frame;
+
+ frame.left = (int)(64.0f * (spdrc.left + style.placement.margin.l * scale.cx) + 0.5);
+ frame.top = (int)(64.0f * (spdrc.top + style.placement.margin.t * scale.cy) + 0.5);
+ frame.right = (int)(64.0f * (spdrc.right - style.placement.margin.r * scale.cx) + 0.5);
+ frame.bottom = (int)(64.0f * (spdrc.bottom - style.placement.margin.b * scale.cy) + 0.5);
+
+ CRect clip;
+
+ if(style.placement.clip.l == -1) clip.left = 0;
+ else clip.left = (int)(spdrc.left + style.placement.clip.l * scale.cx);
+ if(style.placement.clip.t == -1) clip.top = 0;
+ else clip.top = (int)(spdrc.top + style.placement.clip.t * scale.cy);
+ if(style.placement.clip.r == -1) clip.right = vs.cx;
+ else clip.right = (int)(spdrc.left + style.placement.clip.r * scale.cx);
+ if(style.placement.clip.b == -1) clip.bottom = vs.cy;
+ else clip.bottom = (int)(spdrc.top + style.placement.clip.b * scale.cy);
+
+ clip.left = max(clip.left, 0);
+ clip.top = max(clip.top, 0);
+ clip.right = min(clip.right, vs.cx);
+ clip.bottom = min(clip.bottom, vs.cy);
+
+ scale.cx *= 64;
+ scale.cy *= 64;
+
+ bool vertical = s->m_direction.primary == _T("down") || s->m_direction.primary == _T("up");
+
+ // create glyph paths
+
+ WCHAR c_prev = 0, c_next;
+
+ CAutoPtrList<Glyph> glyphs;
+
+ POSITION pos = s->m_text.GetHeadPosition();
+ while(pos)
+ {
+ const Text& t = s->m_text.GetNext(pos);
+
+ LOGFONT lf;
+ memset(&lf, 0, sizeof(lf));
+ lf.lfCharSet = DEFAULT_CHARSET;
+ _tcscpy_s(lf.lfFaceName, CString(t.style.font.face));
+ lf.lfHeight = (LONG)(t.style.font.size * scale.cy + 0.5);
+ lf.lfWeight = (LONG)(t.style.font.weight + 0.5);
+ lf.lfItalic = !!t.style.font.italic;
+ lf.lfUnderline = !!t.style.font.underline;
+ lf.lfStrikeOut = !!t.style.font.strikethrough;
+ lf.lfOutPrecision = OUT_TT_PRECIS;
+ lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
+ lf.lfQuality = ANTIALIASED_QUALITY;
+ lf.lfPitchAndFamily = DEFAULT_PITCH|FF_DONTCARE;
+
+ FontWrapper* font = m_fc.Create(m_hDC, lf);
+
+ if(!font)
+ {
+ _tcscpy_s(lf.lfFaceName, _T("Arial"));
+
+ font = m_fc.Create(m_hDC, lf);
+ if(!font)
+ {
+ ASSERT(0);
+ continue;
+ }
+ }
+
+ HFONT hOldFont = SelectFont(m_hDC, *font);
+
+ const TEXTMETRIC& tm = font->GetTextMetric();
+
+ for(LPCWSTR c = t.str; *c; c++)
+ {
+ CAutoPtr<Glyph> g(DNew Glyph());
+
+ g->c = *c;
+ g->style = t.style;
+ g->scale = scale;
+ g->vertical = vertical;
+ g->font = font;
+
+ c_next = !c[1] && pos ? c_next = s->m_text.GetAt(pos).str[0] : c[1];
+ Arabic::Replace(g->c, c_prev, c_next);
+ c_prev = c[0];
+
+ CSize extent;
+ GetTextExtentPoint32W(m_hDC, &g->c, 1, &extent);
+ ASSERT(extent.cx >= 0 && extent.cy >= 0);
+
+ if(vertical)
+ {
+ g->spacing = (int)(t.style.font.spacing * scale.cy + 0.5);
+ g->ascent = extent.cx / 2;
+ g->descent = extent.cx - g->ascent;
+ g->width = extent.cy;
+
+ // TESTME
+ if(g->c == Text::SP)
+ {
+ g->width /= 2;
+ }
+ }
+ else
+ {
+ g->spacing = (int)(t.style.font.spacing * scale.cx + 0.5);
+ g->ascent = tm.tmAscent;
+ g->descent = tm.tmDescent;
+ g->width = extent.cx;
+ }
+
+ if(g->c == Text::LSEP)
+ {
+ g->spacing = 0;
+ g->width = 0;
+ g->ascent /= 2;
+ g->descent /= 2;
+ }
+ else
+ {
+ GlyphPath* path = m_gpc.Create(m_hDC, font, g->c);
+ if(!path) {ASSERT(0); continue;}
+ g->path = *path;
+ }
+
+ glyphs.AddTail(g);
+ }
+
+ SelectFont(m_hDC, hOldFont);
+ }
+
+ // break glyphs into rows
+
+ CAutoPtrList<Row> rows;
+ CAutoPtr<Row> row;
+
+ pos = glyphs.GetHeadPosition();
+ while(pos)
+ {
+ CAutoPtr<Glyph> g = glyphs.GetNext(pos);
+ if(!row) row.Attach(DNew Row());
+ WCHAR c = g->c;
+ row->AddTail(g);
+ if(c == Text::LSEP || !pos) rows.AddTail(row);
+ }
+
+ // kerning
+
+ if(s->m_direction.primary == _T("right")) // || s->m_direction.primary == _T("left")
+ {
+ for(POSITION rpos = rows.GetHeadPosition(); rpos; rows.GetNext(rpos))
+ {
+ Row* r = rows.GetAt(rpos);
+
+ POSITION gpos = r->GetHeadPosition();
+ while(gpos)
+ {
+ Glyph* g1 = r->GetNext(gpos);
+ if(!gpos) break;
+
+ Glyph* g2 = r->GetAt(gpos);
+ if(g1->font != g2->font || !g1->style.font.kerning || !g2->style.font.kerning)
+ continue;
+
+ if(int size = g1->font->GetKernAmount(g1->c, g2->c))
+ {
+ g2->path.MovePoints(CPoint(size, 0));
+ g2->width += size;
+ }
+ }
+ }
+ }
+
+ // wrap rows
+
+ if(s->m_wrap == _T("normal") || s->m_wrap == _T("even"))
+ {
+ int maxwidth = abs((int)(vertical ? frame.Height() : frame.Width()));
+ int minwidth = 0;
+
+ for(POSITION rpos = rows.GetHeadPosition(); rpos; rows.GetNext(rpos))
+ {
+ Row* r = rows.GetAt(rpos);
+
+ POSITION brpos = NULL;
+
+ if(s->m_wrap == _T("even"))
+ {
+ int fullwidth = 0;
+
+ for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
+ {
+ const Glyph* g = r->GetAt(gpos);
+
+ fullwidth += g->width + g->spacing;
+ }
+
+ fullwidth = abs(fullwidth);
+
+ if(fullwidth > maxwidth)
+ {
+ maxwidth = fullwidth / ((fullwidth / maxwidth) + 1);
+ minwidth = maxwidth;
+ }
+ }
+
+ int width = 0;
+
+ for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
+ {
+ const Glyph* g = r->GetAt(gpos);
+
+ width += g->width + g->spacing;
+
+ if(brpos && abs(width) > maxwidth && g->c != Text::SP)
+ {
+ row.Attach(DNew Row());
+ POSITION next = brpos;
+ r->GetNext(next);
+ do {row->AddHead(r->GetPrev(brpos));} while(brpos);
+ rows.InsertBefore(rpos, row);
+ while(!r->IsEmpty() && r->GetHeadPosition() != next) r->RemoveHeadNoReturn();
+ g = r->GetAt(gpos = next);
+ width = g->width + g->spacing;
+ }
+
+ if(abs(width) >= minwidth)
+ {
+ if(g->style.linebreak == _T("char")
+ || g->style.linebreak == _T("word") && g->c == Text::SP)
+ {
+ brpos = gpos;
+ }
+ }
+ }
+ }
+ }
+
+ // trim rows
+
+ for(POSITION pos = rows.GetHeadPosition(); pos; rows.GetNext(pos))
+ {
+ Row* r = rows.GetAt(pos);
+
+ while(!r->IsEmpty() && r->GetHead()->c == Text::SP)
+ r->RemoveHead();
+
+ while(!r->IsEmpty() && r->GetTail()->c == Text::SP)
+ r->RemoveTail();
+ }
+
+ // calc fill width for each glyph
+
+ CAtlList<Glyph*> glypsh2fill;
+ int fill_id = 0;
+ int fill_width = 0;
+
+ for(POSITION pos = rows.GetHeadPosition(); pos; rows.GetNext(pos))
+ {
+ Row* r = rows.GetAt(pos);
+
+ POSITION gpos = r->GetHeadPosition();
+ while(gpos)
+ {
+ Glyph* g = r->GetNext(gpos);
+
+ if(!glypsh2fill.IsEmpty() && fill_id && (g->style.fill.id != fill_id || !pos && !gpos))
+ {
+ int w = (int)(g->style.fill.width * fill_width + 0.5);
+
+ while(!glypsh2fill.IsEmpty())
+ {
+ Glyph* g = glypsh2fill.RemoveTail();
+ fill_width -= g->width;
+ g->fill = w - fill_width;
+ }
+
+ ASSERT(glypsh2fill.IsEmpty());
+ ASSERT(fill_width == 0);
+
+ glypsh2fill.RemoveAll();
+ fill_width = 0;
+ }
+
+ fill_id = g->style.fill.id;
+
+ if(g->style.fill.id)
+ {
+ glypsh2fill.AddTail(g);
+ fill_width += g->width;
+ }
+ }
+ }
+
+ // calc row sizes and total subtitle size
+
+ CSize size(0, 0);
+
+ if(s->m_direction.secondary == _T("left") || s->m_direction.secondary == _T("up"))
+ ReverseList(rows);
+
+ for(POSITION pos = rows.GetHeadPosition(); pos; rows.GetNext(pos))
+ {
+ Row* r = rows.GetAt(pos);
+
+ if(s->m_direction.primary == _T("left") || s->m_direction.primary == _T("up"))
+ ReverseList(*r);
+
+ int w = 0, h = 0;
+
+ r->width = 0;
+
+ for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
+ {
+ const Glyph* g = r->GetAt(gpos);
+
+ w += g->width;
+ if(gpos) w += g->spacing;
+ h = max(h, g->ascent + g->descent);
+
+ r->width += g->width;
+ if(gpos) r->width += g->spacing;
+ r->ascent = max(r->ascent, g->ascent);
+ r->descent = max(r->descent, g->descent);
+ r->border = max(r->border, g->GetBackgroundSize());
+ }
+
+ for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
+ {
+ Glyph* g = r->GetAt(gpos);
+ g->row_ascent = r->ascent;
+ g->row_descent = r->descent;
+ }
+
+ if(vertical)
+ {
+ size.cx += h;
+ size.cy = max(size.cy, w);
+ }
+ else
+ {
+ size.cx = max(size.cx, w);
+ size.cy += h;
+ }
+ }
+
+ // align rows and calc glyph positions
+
+ rs = DNew RenderedSubtitle(spdrc, clip);
+
+ CPoint p = GetAlignPoint(style.placement, scale, frame, size);
+ CPoint org = GetAlignPoint(style.placement, scale, frame);
+
+ // collision detection
+
+ if(!s->m_animated)
+ {
+ int tlb = !rows.IsEmpty() ? rows.GetHead()->border : 0;
+ int brb = !rows.IsEmpty() ? rows.GetTail()->border : 0;
+
+ CRect r(p, size);
+ m_sra.GetRect(r, s, style.placement.align, tlb, brb);
+ org += r.TopLeft() - p;
+ p = r.TopLeft();
+ }
+
+ CRect subrect(p, size);
+
+ // continue positioning
+
+ for(POSITION pos = rows.GetHeadPosition(); pos; rows.GetNext(pos))
+ {
+ Row* r = rows.GetAt(pos);
+
+ CSize rsize;
+ rsize.cx = rsize.cy = r->width;
+
+ if(vertical)
+ {
+ p.y = GetAlignPoint(style.placement, scale, frame, rsize).y;
+
+ for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
+ {
+ CAutoPtr<Glyph> g = r->GetAt(gpos);
+ g->tl.x = p.x + (int)(g->style.placement.offset.x * scale.cx + 0.5) + r->ascent - g->ascent;
+ g->tl.y = p.y + (int)(g->style.placement.offset.y * scale.cy + 0.5);
+ p.y += g->width + g->spacing;
+ rs->m_glyphs.AddTail(g);
+ }
+
+ p.x += r->ascent + r->descent;
+ }
+ else
+ {
+ p.x = GetAlignPoint(style.placement, scale, frame, rsize).x;
+
+ for(POSITION gpos = r->GetHeadPosition(); gpos; r->GetNext(gpos))
+ {
+ CAutoPtr<Glyph> g = r->GetAt(gpos);
+ g->tl.x = p.x + (int)(g->style.placement.offset.x * scale.cx + 0.5);
+ g->tl.y = p.y + (int)(g->style.placement.offset.y * scale.cy + 0.5) + r->ascent - g->ascent;
+ p.x += g->width + g->spacing;
+ rs->m_glyphs.AddTail(g);
+ }
+
+ p.y += r->ascent + r->descent;
+ }
+ }
+
+ // bkg, precalc style.placement.path, transform
+
+ pos = rs->m_glyphs.GetHeadPosition();
+ while(pos)
+ {
+ Glyph* g = rs->m_glyphs.GetNext(pos);
+ g->CreateBkg();
+ g->CreateSplineCoeffs(spdrc);
+ g->Transform(org, subrect);
+ }
+
+ // merge glyphs (TODO: merge 'fill' too)
+
+ Glyph* g0 = NULL;
+
+ pos = rs->m_glyphs.GetHeadPosition();
+ while(pos)
+ {
+ POSITION cur = pos;
+
+ Glyph* g = rs->m_glyphs.GetNext(pos);
+
+ CRect r = g->bbox + g->tl;
+
+ int size = (int)(g->GetBackgroundSize() + 0.5);
+ int depth = (int)(g->GetShadowDepth() + 0.5);
+
+ r.InflateRect(size, size);
+ r.InflateRect(depth, depth);
+
+ r.left >>= 6;
+ r.top >>= 6;
+ r.right = (r.right + 32) >> 6;
+ r.bottom = (r.bottom + 32) >> 6;
+
+ if((r & clip).IsRectEmpty()) // clip
+ {
+ rs->m_glyphs.RemoveAt(cur);
+ }
+ else if(g0 && g0->style.IsSimilar(g->style)) // append
+ {
+ CPoint o = g->tl - g0->tl;
+
+ g->path.MovePoints(o);
+
+ g0->path.types.Append(g->path.types);
+ g0->path.points.Append(g->path.points);
+
+ g->path_bkg.MovePoints(o);
+
+ g0->path_bkg.types.Append(g->path_bkg.types);
+ g0->path_bkg.points.Append(g->path_bkg.points);
+
+ g0->bbox |= g->bbox + o;
+
+ rs->m_glyphs.RemoveAt(cur);
+ }
+ else // leave alone
+ {
+ g0 = g;
+ }
+ }
+
+ // rasterize
+
+ pos = rs->m_glyphs.GetHeadPosition();
+ while(pos) rs->m_glyphs.GetNext(pos)->Rasterize();
+
+ // cache
+
+ m_rsc.Add(s->m_name, rs);
+
+ m_fc.Flush();
+
+ return rs;
+ }
+
+ //
+
+ CRect RenderedSubtitle::Draw(SubPicDesc& spd) const
+ {
+ CRect bbox;
+ bbox.SetRectEmpty();
+
+ // shadow
+
+ POSITION pos = m_glyphs.GetHeadPosition();
+ while(pos)
+ {
+ Glyph* g = m_glyphs.GetNext(pos);
+
+ if(g->style.shadow.depth <= 0) continue;
+
+ DWORD c = g->style.shadow.color;
+ DWORD sw[6] = {c, (DWORD)-1};
+
+ bool outline = g->style.background.type == L"outline" && g->style.background.size > 0;
+
+ bbox |= g->ras_shadow.Draw(spd, m_clip, g->tls.x, g->tls.y, sw, outline ? 1 : 0);
+ }
+
+ // background
+
+ pos = m_glyphs.GetHeadPosition();
+ while(pos)
+ {
+ Glyph* g = m_glyphs.GetNext(pos);
+
+ DWORD c = g->style.background.color;
+ DWORD sw[6] = {c, (DWORD)-1};
+
+ if(g->style.background.type == L"outline" && g->style.background.size > 0)
+ {
+ bbox |= g->ras.Draw(spd, m_clip, g->tl.x, g->tl.y, sw, g->style.font.color.a < 255 ? 2 : 1);
+ }
+ else if(g->style.background.type == L"enlarge" && g->style.background.size > 0
+ || g->style.background.type == L"box" && g->style.background.size >= 0)
+ {
+ bbox |= g->ras_bkg.Draw(spd, m_clip, g->tl.x, g->tl.y, sw, 0);
+ }
+ }
+
+ // body
+
+ pos = m_glyphs.GetHeadPosition();
+ while(pos)
+ {
+ Glyph* g = m_glyphs.GetNext(pos);
+
+ DWORD c = g->style.font.color;
+ DWORD sw[6] = {c, (DWORD)-1}; // TODO: fill
+
+ bbox |= g->ras.Draw(spd, m_clip, g->tl.x, g->tl.y, sw, 0);
+ }
+
+ return bbox;
+ }
+
+ //
+
+ void SubRectAllocator::UpdateTarget(const CSize& s, const CRect& r)
+ {
+ if(vs != s || vr != r) RemoveAll();
+ vs = s;
+ vr = r;
+ }
+
+ void SubRectAllocator::GetRect(CRect& rect, const Subtitle* s, const Align& align, int tlb, int brb)
+ {
+ SubRect sr(rect, s->m_layer);
+ sr.rect.InflateRect(tlb, tlb, brb, brb);
+
+ StringMapW<SubRect>::CPair* pPair = Lookup(s->m_name);
+
+ if(pPair && pPair->m_value.rect != sr.rect)
+ {
+ RemoveKey(s->m_name);
+ pPair = NULL;
+ }
+
+ if(!pPair)
+ {
+ bool vertical = s->m_direction.primary == _T("down") || s->m_direction.primary == _T("up");
+
+ bool fOK = false;
+
+ while(!fOK)
+ {
+ fOK = true;
+
+ POSITION pos = GetStartPosition();
+ while(pos)
+ {
+ const SubRect& sr2 = GetNextValue(pos);
+
+ if(sr.layer == sr2.layer && !(sr.rect & sr2.rect).IsRectEmpty())
+ {
+ if(vertical)
+ {
+ if(align.h < 0.5)
+ {
+ sr.rect.right = sr2.rect.right + sr.rect.Width();
+ sr.rect.left = sr2.rect.right;
+ }
+ else
+ {
+ sr.rect.left = sr2.rect.left - sr.rect.Width();
+ sr.rect.right = sr2.rect.left;
+ }
+ }
+ else
+ {
+ if(align.v < 0.5)
+ {
+ sr.rect.bottom = sr2.rect.bottom + sr.rect.Height();
+ sr.rect.top = sr2.rect.bottom;
+ }
+ else
+ {
+ sr.rect.top = sr2.rect.top - sr.rect.Height();
+ sr.rect.bottom = sr2.rect.top;
+ }
+ }
+
+ fOK = false;
+ }
+ }
+ }
+
+ SetAt(s->m_name, sr);
+
+ rect = sr.rect;
+ rect.DeflateRect(tlb, tlb, brb, brb);
+ }
+ }
+
+ //
+
+ FontWrapper* FontCache::Create(HDC hDC, const LOGFONT& lf)
+ {
+ CStringW key;
+
+ key.Format(L"%s,%d,%d,%d",
+ CStringW(lf.lfFaceName), lf.lfHeight, lf.lfWeight,
+ ((lf.lfItalic&1)<<2) | ((lf.lfUnderline&1)<<1) | ((lf.lfStrikeOut&1)<<0));
+
+ FontWrapper* pFW = NULL;
+
+ if(m_key2obj.Lookup(key, pFW))
+ {
+ return pFW;
+ }
+
+ HFONT hFont = CreateFontIndirect(&lf);
+
+ if(!hFont)
+ {
+ ASSERT(0);
+ return NULL;
+ }
+
+ pFW = DNew FontWrapper(hDC, hFont, key);
+
+ Add(key, pFW, false);
+
+ return pFW;
+ }
+
+ //
+
+ GlyphPath* GlyphPathCache::Create(HDC hDC, const FontWrapper* f, WCHAR c)
+ {
+ CStringW key = CStringW((LPCWSTR)*f) + c;
+
+ GlyphPath* path = NULL;
+
+ if(m_key2obj.Lookup(key, path))
+ {
+ return path;
+ }
+
+ BeginPath(hDC);
+ TextOutW(hDC, 0, 0, &c, 1);
+ CloseFigure(hDC);
+ if(!EndPath(hDC)) {AbortPath(hDC); ASSERT(0); return NULL;}
+
+ path = DNew GlyphPath();
+
+ int count = GetPath(hDC, NULL, NULL, 0);
+
+ if(count > 0)
+ {
+ path->points.SetCount(count);
+ path->types.SetCount(count);
+
+ if(count != GetPath(hDC, path->points.GetData(), path->types.GetData(), count))
+ {
+ ASSERT(0);
+ delete path;
+ return NULL;
+ }
+ }
+
+ Add(key, path);
+
+ return path;
+ }
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/Renderer.h b/src/Subtitles/libssf/Renderer.h
new file mode 100644
index 000000000..b126feaad
--- /dev/null
+++ b/src/Subtitles/libssf/Renderer.h
@@ -0,0 +1,160 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include "StringMap.h"
+#include "Glyph.h"
+
+namespace ssf
+{
+ template<class T>
+ class Cache
+ {
+ protected:
+ StringMapW<T> m_key2obj;
+ CAtlList<CStringW> m_objs;
+ size_t m_limit;
+
+ public:
+ Cache(size_t limit) {m_limit = max(1, limit);}
+ virtual ~Cache() {RemoveAll();}
+
+ void RemoveAll()
+ {
+ POSITION pos = m_key2obj.GetStartPosition();
+ while(pos) delete m_key2obj.GetNextValue(pos);
+ m_key2obj.RemoveAll();
+ m_objs.RemoveAll();
+ }
+
+ void Add(const CStringW& key, T& obj, bool fFlush = true)
+ {
+ if(StringMapW<T>::CPair* p = m_key2obj.Lookup(key)) delete p->m_value;
+ else m_objs.AddTail(key);
+
+ m_key2obj[key] = obj;
+
+ if(fFlush) Flush();
+ }
+
+ void Flush()
+ {
+ while(m_objs.GetCount() > m_limit)
+ {
+ CStringW key = m_objs.RemoveHead();
+ ASSERT(m_key2obj.Lookup(key));
+ delete m_key2obj[key];
+ m_key2obj.RemoveKey(key);
+ }
+ }
+
+ bool Lookup(const CStringW& key, T& val)
+ {
+ return m_key2obj.Lookup(key, val) && val;
+ }
+
+ void Invalidate(const CStringW& key)
+ {
+ T val;
+ if(m_key2obj.Lookup(key, val))
+ {
+ delete val;
+ m_key2obj[key] = NULL;
+ }
+ }
+ };
+
+ class FontCache : public Cache<FontWrapper*>
+ {
+ public:
+ FontCache() : Cache(20) {}
+ FontWrapper* Create(HDC hDC, const LOGFONT& lf);
+ };
+
+ class GlyphPathCache : public Cache<GlyphPath*>
+ {
+ public:
+ GlyphPathCache() : Cache(100) {}
+ GlyphPath* Create(HDC hDC, const FontWrapper* f, WCHAR c);
+ };
+
+ class Row : public CAutoPtrList<Glyph>
+ {
+ public:
+ int ascent, descent, border, width;
+ Row() {ascent = descent = border = width = 0;}
+ };
+
+ class RenderedSubtitle
+ {
+ public:
+ CRect m_spdrc;
+ CRect m_clip;
+ CAutoPtrList<Glyph> m_glyphs;
+
+ RenderedSubtitle(const CRect& spdrc, const CRect& clip) : m_spdrc(spdrc), m_clip(clip) {}
+ virtual ~RenderedSubtitle() {}
+
+ CRect Draw(SubPicDesc& spd) const;
+ };
+
+ class RenderedSubtitleCache : public Cache<RenderedSubtitle*>
+ {
+ public:
+ RenderedSubtitleCache() : Cache(10) {}
+ };
+
+ class SubRect
+ {
+ public:
+ CRect rect;
+ float layer;
+ SubRect() {}
+ SubRect(const CRect& r, float l) : rect(r), layer(l) {}
+ };
+
+ class SubRectAllocator : public StringMapW<SubRect>
+ {
+ CSize vs;
+ CRect vr;
+ public:
+ void UpdateTarget(const CSize& vs, const CRect& vr);
+ void GetRect(CRect& rect, const Subtitle* s, const Align& align, int tlb, int brb);
+ };
+
+ class Renderer
+ {
+ HDC m_hDC;
+
+ FontCache m_fc;
+ GlyphPathCache m_gpc;
+ RenderedSubtitleCache m_rsc;
+ SubRectAllocator m_sra;
+
+ public:
+ Renderer();
+ virtual ~Renderer();
+
+ void NextSegment(const CAutoPtrList<Subtitle>& subs);
+ RenderedSubtitle* Lookup(const Subtitle* s, const CSize& vs, const CRect& vr);
+ };
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/Split.cpp b/src/Subtitles/libssf/Split.cpp
new file mode 100644
index 000000000..c7868eaa0
--- /dev/null
+++ b/src/Subtitles/libssf/Split.cpp
@@ -0,0 +1,74 @@
+/*
+ * 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 "Split.h"
+#include "Exception.h"
+
+namespace ssf
+{
+ Split::Split(LPCWSTR sep, CStringW str, size_t limit, SplitType type)
+ {
+ DoSplit(sep, str, limit, type);
+ }
+
+ Split::Split(WCHAR sep, CStringW str, size_t limit, SplitType type)
+ {
+ DoSplit(CStringW(sep), str, limit, type);
+ }
+
+ void Split::DoSplit(LPCWSTR sep, CStringW str, size_t limit, SplitType type)
+ {
+ RemoveAll();
+
+ if(size_t seplen = wcslen(sep))
+ {
+ for(int i = 0, j = 0, len = str.GetLength();
+ i <= len && (limit == 0 || GetCount() < limit);
+ i = j + (int)seplen)
+ {
+ j = str.Find(sep, i);
+ if(j < 0) j = len;
+
+ CStringW s = i < j ? str.Mid(i, j - i) : L"";
+
+ switch(type)
+ {
+ case Min: s.Trim(); // fall through
+ case Def: if(s.IsEmpty()) break; // else fall through
+ case Max: Add(s); break;
+ }
+ }
+ }
+ }
+
+ int Split::GetAtInt(size_t i)
+ {
+ if(i >= GetCount()) throw Exception(_T("Index out of bounds"));
+ return _wtoi(GetAt(i));
+ }
+
+ float Split::GetAtFloat(size_t i)
+ {
+ if(i >= GetCount()) throw Exception(_T("Index out of bounds"));
+ return (float)_wtof(GetAt(i));
+ }
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/Split.h b/src/Subtitles/libssf/Split.h
new file mode 100644
index 000000000..5347eb25c
--- /dev/null
+++ b/src/Subtitles/libssf/Split.h
@@ -0,0 +1,38 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+namespace ssf
+{
+ class Split : public CAtlArray<CStringW>
+ {
+ public:
+ enum SplitType {Min, Def, Max};
+ Split();
+ Split(LPCWSTR sep, CStringW str, size_t limit = 0, SplitType type = Def);
+ Split(WCHAR sep, CStringW str, size_t limit = 0, SplitType type = Def);
+ operator size_t() {return GetCount();}
+ void DoSplit(LPCWSTR sep, CStringW str, size_t limit, SplitType type);
+ int GetAtInt(size_t i);
+ float GetAtFloat(size_t i);
+ };
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/Stream.cpp b/src/Subtitles/libssf/Stream.cpp
new file mode 100644
index 000000000..94043b272
--- /dev/null
+++ b/src/Subtitles/libssf/Stream.cpp
@@ -0,0 +1,360 @@
+/*
+ * 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 "Stream.h"
+#include <stdio.h>
+
+namespace ssf
+{
+ Stream::Stream()
+ : m_encoding(none)
+ , m_line(0)
+ , m_col(-1)
+ {
+
+ }
+
+ Stream::~Stream()
+ {
+ }
+
+ void Stream::ThrowError(LPCTSTR fmt, ...)
+ {
+ va_list args;
+ va_start(args, fmt);
+ int len = _vsctprintf(fmt, args) + 1;
+ CString str;
+ if(len > 0) _vstprintf_s(str.GetBufferSetLength(len), len, fmt, args);
+ va_end(args);
+
+ throw Exception(_T("Error (Ln %d Col %d): %s"), m_line+1, m_col+1, str);
+ }
+
+ bool Stream::IsWhiteSpace(int c, LPCWSTR morechars)
+ {
+ return c != 0xa0 && iswspace(c) || morechars && wcschr(morechars, (WCHAR)c);
+ }
+
+ //
+
+ InputStream::InputStream()
+ {
+
+ }
+
+ InputStream::~InputStream()
+ {
+ }
+
+ int InputStream::NextChar()
+ {
+ if(m_encoding == none)
+ {
+ m_encoding = unknown;
+
+ switch(NextByte())
+ {
+ case 0xef:
+ if(NextByte() == 0xbb && NextByte() == 0xbf) m_encoding = utf8;
+ break;
+ case 0xff:
+ if(NextByte() == 0xfe) m_encoding = utf16le;
+ break;
+ case 0xfe:
+ if(NextByte() == 0xff) m_encoding = utf16be;
+ break;
+ }
+ }
+
+ if(m_encoding == unknown)
+ {
+ throw Exception(_T("unknown character encoding, missing BOM"));
+ }
+
+ int i, c;
+
+ int cur = NextByte();
+
+ switch(m_encoding)
+ {
+ case utf8:
+ for(i = 7; i >= 0 && (cur & (1 << i)); i--);
+ cur &= (1 << i) - 1;
+ while(++i < 7) {c = NextByte(); if(c == EOS) {cur = EOS; break;} cur = (cur << 6) | (c & 0x3f);}
+ break;
+ case utf16le:
+ c = NextByte();
+ if(c == EOS) {cur = EOS; break;}
+ cur = (c << 8) | cur;
+ break;
+ case utf16be:
+ c = NextByte();
+ if(c == EOS) {cur = EOS; break;}
+ cur = cur | (c << 8);
+ break;
+ case wchar:
+ break;
+ }
+
+ return cur;
+ }
+
+ int InputStream::PushChar()
+ {
+ int c = NextChar();
+ m_queue.AddTail(c);
+ return c;
+ }
+
+ int InputStream::PopChar()
+ {
+ if(m_queue.IsEmpty()) ThrowError(_T("fatal stream error"));
+
+ int c = m_queue.RemoveHead();
+
+ if(c != EOS)
+ {
+ if(c == '\n') {m_line++; m_col = -1;}
+ m_col++;
+ }
+
+ return c;
+ }
+
+ int InputStream::PeekChar()
+ {
+ while(m_queue.GetCount() < 2) PushChar();
+
+ ASSERT(m_queue.GetCount() == 2);
+
+ if(m_queue.GetHead() == '/' && m_queue.GetTail() == '/')
+ {
+ while(!m_queue.IsEmpty()) PopChar();
+ int c;
+ do {PushChar(); c = PopChar();} while(!(c == '\n' || c == EOS));
+ return PeekChar();
+ }
+ else if(m_queue.GetHead() == '/' && m_queue.GetTail() == '*')
+ {
+ while(!m_queue.IsEmpty()) PopChar();
+ int c1, c2;
+ PushChar();
+ do {c2 = PushChar(); c1 = PopChar();} while(!((c1 == '*' && c2 == '/') || c1 == EOS));
+ PopChar();
+ return PeekChar();
+ }
+
+ return m_queue.GetHead();
+ }
+
+ int InputStream::GetChar()
+ {
+ if(m_queue.GetCount() < 2) PeekChar();
+ return PopChar();
+ }
+
+ int InputStream::SkipWhiteSpace(LPCWSTR morechars)
+ {
+ int c = PeekChar();
+ for(; IsWhiteSpace(c, morechars); c = PeekChar())
+ GetChar();
+ return c;
+ }
+
+ // FileInputStream
+
+ FileInputStream::FileInputStream(const TCHAR* fn)
+ : m_file(NULL)
+ {
+ if(_tfopen_s(&m_file, fn, _T("r")) != 0) ThrowError(_T("cannot open file '%s'"), fn);
+ }
+
+ FileInputStream::~FileInputStream()
+ {
+ if(m_file) {fclose(m_file); m_file = NULL;}
+ }
+
+ int FileInputStream::NextByte()
+ {
+ if(!m_file) ThrowError(_T("file pointer is NULL"));
+ return fgetc(m_file);
+ }
+
+ // MemoryInputStream
+
+ MemoryInputStream::MemoryInputStream(BYTE* pBytes, int len, bool fCopy, bool fFree)
+ : m_pBytes(NULL)
+ , m_pos(0)
+ , m_len(len)
+ {
+ if(fCopy)
+ {
+ m_pBytes = DNew BYTE[len];
+ if(m_pBytes) memcpy(m_pBytes, pBytes, len);
+ m_fFree = true;
+ }
+ else
+ {
+ m_pBytes = pBytes;
+ m_fFree = fFree;
+ }
+
+ if(!m_pBytes) ThrowError(_T("memory stream pointer is NULL"));
+ }
+
+ MemoryInputStream::~MemoryInputStream()
+ {
+ if(m_fFree) delete [] m_pBytes;
+ m_pBytes = NULL;
+ }
+
+ int MemoryInputStream::NextByte()
+ {
+ if(!m_pBytes) ThrowError(_T("memory stream pointer is NULL"));
+ if(m_pos >= m_len) return Stream::EOS;
+ return (int)m_pBytes[m_pos++];
+ }
+
+ // WCharInputStream
+
+ WCharInputStream::WCharInputStream(CStringW str)
+ : m_str(str)
+ , m_pos(0)
+ {
+ m_encoding = Stream::wchar; // HACK: it should return real bytes from NextByte (two per wchar_t), but this way it's a lot more simple...
+ }
+
+ int WCharInputStream::NextByte()
+ {
+ if(m_pos >= m_str.GetLength()) return Stream::EOS;
+ return m_str[m_pos++];
+ }
+
+ // OutputStream
+
+ OutputStream::OutputStream(encoding_t e)
+ {
+ m_encoding = e;
+ m_bof = true;
+ }
+
+ OutputStream::~OutputStream()
+ {
+ }
+
+ void OutputStream::PutChar(WCHAR c)
+ {
+ if(m_bof)
+ {
+ m_bof = false;
+
+ switch(m_encoding)
+ {
+ case utf8:
+ case utf16le:
+ case utf16be:
+ PutChar(0xfeff);
+ break;
+ }
+ }
+
+ switch(m_encoding)
+ {
+ case utf8:
+ if(0 <= c && c < 0x80) // 0xxxxxxx
+ {
+ NextByte(c);
+ }
+ else if(0x80 <= c && c < 0x800) // 110xxxxx 10xxxxxx
+ {
+ NextByte(0xc0 | ((c<<2)&0x1f));
+ NextByte(0x80 | ((c<<0)&0x3f));
+ }
+ else if(0x800 <= c && c < 0xFFFF) // 1110xxxx 10xxxxxx 10xxxxxx
+ {
+ NextByte(0xe0 | ((c<<4)&0x0f));
+ NextByte(0x80 | ((c<<2)&0x3f));
+ NextByte(0x80 | ((c<<0)&0x3f));
+ }
+ else
+ {
+ NextByte('?');
+ }
+ break;
+ case utf16le:
+ NextByte(c & 0xff);
+ NextByte((c >> 8) & 0xff);
+ break;
+ case utf16be:
+ NextByte((c >> 8) & 0xff);
+ NextByte(c & 0xff);
+ break;
+ case wchar:
+ NextByte(c);
+ break;
+ }
+ }
+
+ void OutputStream::PutString(LPCWSTR fmt, ...)
+ {
+ CStringW str;
+
+ va_list args;
+ va_start(args, fmt);
+ int len = _vscwprintf(fmt, args) + 1;
+ if(len > 0) vswprintf_s(str.GetBufferSetLength(len), len, fmt, args);
+ va_end(args);
+
+ LPCWSTR s = str;
+ while(*s) PutChar(*s++);
+ }
+
+ // WCharOutputStream
+
+ WCharOutputStream::WCharOutputStream()
+ : OutputStream(wchar)
+ {
+ }
+
+ void WCharOutputStream::NextByte(int b)
+ {
+ m_str += (WCHAR)b;
+ }
+
+ // DebugOutputStream
+
+ DebugOutputStream::DebugOutputStream()
+ : OutputStream(wchar)
+ {
+ }
+
+ DebugOutputStream::~DebugOutputStream()
+ {
+ TRACE(_T("%s\n"), m_str);
+ }
+
+ void DebugOutputStream::NextByte(int b)
+ {
+ if(b == '\n') {TRACE(_T("%s\n"), m_str); m_str.Empty();}
+ else if(b != '\r') m_str += (WCHAR)b;
+ }
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/Stream.h b/src/Subtitles/libssf/Stream.h
new file mode 100644
index 000000000..abfcbf9d6
--- /dev/null
+++ b/src/Subtitles/libssf/Stream.h
@@ -0,0 +1,143 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include "Exception.h"
+
+namespace ssf
+{
+ class Stream
+ {
+ public:
+ enum {EOS = -1};
+ enum encoding_t {none, unknown, utf8, utf16le, utf16be, wchar};
+
+ protected:
+ int m_line, m_col;
+ encoding_t m_encoding;
+
+ public:
+ Stream();
+ virtual ~Stream();
+
+ static bool IsWhiteSpace(int c, LPCWSTR morechars = NULL);
+
+ void ThrowError(LPCTSTR fmt, ...);
+ };
+
+ class InputStream : public Stream
+ {
+ CAtlList<int> m_queue;
+ int PushChar(), PopChar();
+
+ int NextChar();
+
+ protected:
+ virtual int NextByte() = 0;
+
+ public:
+ InputStream();
+ ~InputStream();
+
+ int PeekChar(), GetChar();
+
+ int SkipWhiteSpace(LPCWSTR morechars = NULL);
+ };
+
+ class FileInputStream : public InputStream
+ {
+ FILE* m_file;
+
+ protected:
+ int NextByte();
+
+ public:
+ FileInputStream(const TCHAR* fn);
+ ~FileInputStream();
+ };
+
+ class MemoryInputStream : public InputStream
+ {
+ BYTE* m_pBytes;
+ int m_pos, m_len;
+ bool m_fFree;
+
+ protected:
+ int NextByte();
+
+ public:
+ MemoryInputStream(BYTE* pBytes, int len, bool fCopy, bool fFree);
+ ~MemoryInputStream();
+ };
+
+ class WCharInputStream : public InputStream
+ {
+ CStringW m_str;
+ int m_pos;
+
+ protected:
+ int NextByte();
+
+ public:
+ WCharInputStream(CStringW str);
+ };
+
+ class OutputStream : public Stream
+ {
+ bool m_bof;
+
+ protected:
+ virtual void NextByte(int b) = 0;
+
+ public:
+ OutputStream(encoding_t e);
+ virtual ~OutputStream();
+
+ void PutChar(WCHAR c);
+ void PutString(LPCWSTR fmt, ...);
+ };
+
+ class WCharOutputStream : public OutputStream
+ {
+ CStringW m_str;
+
+ protected:
+ void NextByte(int b);
+
+ public:
+ WCharOutputStream();
+
+ const CStringW& GetString() {return m_str;}
+ };
+
+ class DebugOutputStream : public OutputStream
+ {
+ CStringW m_str;
+
+ protected:
+ void NextByte(int b);
+
+ public:
+ DebugOutputStream();
+ ~DebugOutputStream();
+ };
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/StringMap.cpp b/src/Subtitles/libssf/StringMap.cpp
new file mode 100644
index 000000000..aa9909528
--- /dev/null
+++ b/src/Subtitles/libssf/StringMap.cpp
@@ -0,0 +1,28 @@
+/*
+ * 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 "Exception.h"
+
+namespace ssf
+{
+
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/StringMap.h b/src/Subtitles/libssf/StringMap.h
new file mode 100644
index 000000000..ff788541a
--- /dev/null
+++ b/src/Subtitles/libssf/StringMap.h
@@ -0,0 +1,46 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+namespace ssf
+{
+ template <class T = CString, class S = CString>
+ class StringMap : public CAtlMap<S, T, CStringElementTraits<S> >
+ {
+ public:
+ StringMap() {}
+ StringMap(const StringMap& s2t) {*this = s2t;}
+ StringMap& operator = (const StringMap& s2t)
+ {
+ RemoveAll();
+ POSITION pos = s2t.GetStartPosition();
+ while(pos) {const StringMap::CPair* p = s2t.GetNext(pos); SetAt(p->m_key, p->m_value);}
+ return *this;
+ }
+ };
+
+ template <class T = CStringA, class S = CStringA>
+ class StringMapA : public StringMap<T, S> {};
+
+ template <class T = CStringW, class S = CStringW>
+ class StringMapW : public StringMap<T, S> {};
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/Subtitle.cpp b/src/Subtitles/libssf/Subtitle.cpp
new file mode 100644
index 000000000..673e92fd4
--- /dev/null
+++ b/src/Subtitles/libssf/Subtitle.cpp
@@ -0,0 +1,702 @@
+/*
+ * 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 "Subtitle.h"
+#include "Split.h"
+#include <math.h>
+
+namespace ssf
+{
+ struct Subtitle::n2n_t Subtitle::m_n2n;
+
+ Subtitle::Subtitle(File* pFile)
+ : m_pFile(pFile)
+ , m_animated(false)
+ {
+ if(m_n2n.align[0].IsEmpty())
+ {
+ m_n2n.align[0][L"left"] = 0;
+ m_n2n.align[0][L"center"] = 0.5;
+ m_n2n.align[0][L"middle"] = 0.5;
+ m_n2n.align[0][L"right"] = 1;
+ }
+
+ if(m_n2n.align[1].IsEmpty())
+ {
+ m_n2n.align[1][L"top"] = 0;
+ m_n2n.align[1][L"middle"] = 0.5;
+ m_n2n.align[1][L"center"] = 0.5;
+ m_n2n.align[1][L"bottom"] = 1;
+ }
+
+ if(m_n2n.weight.IsEmpty())
+ {
+ m_n2n.weight[L"thin"] = FW_THIN;
+ m_n2n.weight[L"normal"] = FW_NORMAL;
+ m_n2n.weight[L"bold"] = FW_BOLD;
+ }
+
+ if(m_n2n.transition.IsEmpty())
+ {
+ m_n2n.transition[L"start"] = 0;
+ m_n2n.transition[L"stop"] = 1e10;
+ m_n2n.transition[L"linear"] = 1;
+ }
+ }
+
+ Subtitle::~Subtitle()
+ {
+ }
+
+ bool Subtitle::Parse(Definition* pDef, float start, float stop, float at)
+ {
+ ASSERT(m_pFile && pDef);
+
+ m_name = pDef->m_name;
+
+ m_text.RemoveAll();
+
+ m_time.start = start;
+ m_time.stop = stop;
+
+ at -= start;
+
+ Fill::gen_id = 0;
+
+ m_pFile->Commit();
+
+ try
+ {
+ Definition& frame = (*pDef)[L"frame"];
+
+ m_frame.reference = frame[L"reference"];
+ m_frame.resolution.cx = frame[L"resolution"][L"cx"];
+ m_frame.resolution.cy = frame[L"resolution"][L"cy"];
+
+ Definition& direction = (*pDef)[L"direction"];
+
+ m_direction.primary = direction[L"primary"];
+ m_direction.secondary = direction[L"secondary"];
+
+ m_wrap = (*pDef)[L"wrap"];
+
+ m_layer = (*pDef)[L"layer"];
+
+ Style style;
+ GetStyle(&(*pDef)[L"style"], style);
+
+ StringMapW<float> offset;
+ Definition& block = (*pDef)[L"@"];
+ Parse(WCharInputStream((LPCWSTR)block), style, at, offset, dynamic_cast<Reference*>(block.m_parent));
+
+ // TODO: trimming should be done by the renderer later, after breaking the words into lines
+
+ while(!m_text.IsEmpty() && (m_text.GetHead().str == Text::SP || m_text.GetHead().str == Text::LSEP))
+ m_text.RemoveHead();
+
+ while(!m_text.IsEmpty() && (m_text.GetTail().str == Text::SP || m_text.GetTail().str == Text::LSEP))
+ m_text.RemoveTail();
+
+ for(POSITION pos = m_text.GetHeadPosition(); pos; m_text.GetNext(pos))
+ {
+ if(m_text.GetAt(pos).str == Text::LSEP)
+ {
+ POSITION prev = pos;
+ m_text.GetPrev(prev);
+
+ while(prev && m_text.GetAt(prev).str == Text::SP)
+ {
+ POSITION tmp = prev;
+ m_text.GetPrev(prev);
+ m_text.RemoveAt(tmp);
+ }
+
+ POSITION next = pos;
+ m_text.GetNext(next);
+
+ while(next && m_text.GetAt(next).str == Text::SP)
+ {
+ POSITION tmp = next;
+ m_text.GetNext(next);
+ m_text.RemoveAt(tmp);
+ }
+ }
+ }
+ }
+ catch(Exception& e)
+ {
+ UNREFERENCED_PARAMETER(e);
+ TRACE(_T("%s"), e.ToString());
+ return false;
+ }
+
+ m_pFile->Rollback();
+
+ return true;
+ }
+
+ void Subtitle::GetStyle(Definition* pDef, Style& style)
+ {
+ style.placement.pos.x = 0;
+ style.placement.pos.y = 0;
+ style.placement.pos.auto_x = true;
+ style.placement.pos.auto_y = true;
+
+ style.placement.org.x = 0;
+ style.placement.org.y = 0;
+ style.placement.org.auto_x = true;
+ style.placement.org.auto_y = true;
+
+ Rect frame = {0, m_frame.resolution.cx, m_frame.resolution.cy, 0};
+
+ style.placement.clip.t = -1;
+ style.placement.clip.r = -1;
+ style.placement.clip.b = -1;
+ style.placement.clip.l = -1;
+
+ //
+
+ style.linebreak = (*pDef)[L"linebreak"];
+
+ Definition& placement = (*pDef)[L"placement"];
+
+ Definition& clip = placement[L"clip"];
+
+ if(clip.IsValue(Definition::string))
+ {
+ CStringW str = clip;
+
+ if(str == L"frame") style.placement.clip = frame;
+ // else ?
+ }
+ else
+ {
+ if(clip[L"t"].IsValue()) style.placement.clip.t = clip[L"t"];
+ if(clip[L"r"].IsValue()) style.placement.clip.r = clip[L"r"];
+ if(clip[L"b"].IsValue()) style.placement.clip.b = clip[L"b"];
+ if(clip[L"l"].IsValue()) style.placement.clip.l = clip[L"l"];
+ }
+
+ StringMapW<float> n2n_margin[2];
+
+ n2n_margin[0][L"top"] = 0;
+ n2n_margin[0][L"right"] = 0;
+ n2n_margin[0][L"bottom"] = frame.b - frame.t;
+ n2n_margin[0][L"left"] = frame.r - frame.l;
+ n2n_margin[1][L"top"] = frame.b - frame.t;
+ n2n_margin[1][L"right"] = frame.r - frame.l;
+ n2n_margin[1][L"bottom"] = 0;
+ n2n_margin[1][L"left"] = 0;
+
+ placement[L"margin"][L"t"].GetAsNumber(style.placement.margin.t, &n2n_margin[0]);
+ placement[L"margin"][L"r"].GetAsNumber(style.placement.margin.r, &n2n_margin[0]);
+ placement[L"margin"][L"b"].GetAsNumber(style.placement.margin.b, &n2n_margin[1]);
+ placement[L"margin"][L"l"].GetAsNumber(style.placement.margin.l, &n2n_margin[1]);
+
+ placement[L"align"][L"h"].GetAsNumber(style.placement.align.h, &m_n2n.align[0]);
+ placement[L"align"][L"v"].GetAsNumber(style.placement.align.v, &m_n2n.align[1]);
+
+ if(placement[L"pos"][L"x"].IsValue()) {style.placement.pos.x = placement[L"pos"][L"x"]; style.placement.pos.auto_x = false;}
+ if(placement[L"pos"][L"y"].IsValue()) {style.placement.pos.y = placement[L"pos"][L"y"]; style.placement.pos.auto_y = false;}
+
+ placement[L"offset"][L"x"].GetAsNumber(style.placement.offset.x);
+ placement[L"offset"][L"y"].GetAsNumber(style.placement.offset.y);
+
+ style.placement.angle.x = placement[L"angle"][L"x"];
+ style.placement.angle.y = placement[L"angle"][L"y"];
+ style.placement.angle.z = placement[L"angle"][L"z"];
+
+ if(placement[L"org"][L"x"].IsValue()) {style.placement.org.x = placement[L"org"][L"x"]; style.placement.org.auto_x = false;}
+ if(placement[L"org"][L"y"].IsValue()) {style.placement.org.y = placement[L"org"][L"y"]; style.placement.org.auto_y = false;}
+
+ style.placement.path = placement[L"path"];
+
+ Definition& font = (*pDef)[L"font"];
+
+ style.font.face = font[L"face"];
+ style.font.size = font[L"size"];
+ font[L"weight"].GetAsNumber(style.font.weight, &m_n2n.weight);
+ style.font.color.a = font[L"color"][L"a"];
+ style.font.color.r = font[L"color"][L"r"];
+ style.font.color.g = font[L"color"][L"g"];
+ style.font.color.b = font[L"color"][L"b"];
+ style.font.underline = font[L"underline"];
+ style.font.strikethrough = font[L"strikethrough"];
+ style.font.italic = font[L"italic"];
+ style.font.spacing = font[L"spacing"];
+ style.font.scale.cx = font[L"scale"][L"cx"];
+ style.font.scale.cy = font[L"scale"][L"cy"];
+ style.font.kerning = font[L"kerning"];
+
+ Definition& background = (*pDef)[L"background"];
+
+ style.background.color.a = background[L"color"][L"a"];
+ style.background.color.r = background[L"color"][L"r"];
+ style.background.color.g = background[L"color"][L"g"];
+ style.background.color.b = background[L"color"][L"b"];
+ style.background.size = background[L"size"];
+ style.background.type = background[L"type"];
+ style.background.blur = background[L"blur"];
+
+ Definition& shadow = (*pDef)[L"shadow"];
+
+ style.shadow.color.a = shadow[L"color"][L"a"];
+ style.shadow.color.r = shadow[L"color"][L"r"];
+ style.shadow.color.g = shadow[L"color"][L"g"];
+ style.shadow.color.b = shadow[L"color"][L"b"];
+ style.shadow.depth = shadow[L"depth"];
+ style.shadow.angle = shadow[L"angle"];
+ style.shadow.blur = shadow[L"blur"];
+
+ Definition& fill = (*pDef)[L"fill"];
+
+ style.fill.color.a = fill[L"color"][L"a"];
+ style.fill.color.r = fill[L"color"][L"r"];
+ style.fill.color.g = fill[L"color"][L"g"];
+ style.fill.color.b = fill[L"color"][L"b"];
+ style.fill.width = fill[L"width"];
+ }
+
+ float Subtitle::GetMixWeight(Definition* pDef, float at, StringMapW<float>& offset, int default_id)
+ {
+ float t = 1;
+
+ try
+ {
+ StringMapW<float> n2n;
+
+ n2n[L"start"] = 0;
+ n2n[L"stop"] = m_time.stop - m_time.start;
+
+ Definition::Time time;
+ if(pDef->GetAsTime(time, offset, &n2n, default_id) && time.start.value < time.stop.value)
+ {
+ t = (at - time.start.value) / (time.stop.value - time.start.value);
+
+ float u = t;
+
+ if(t < 0) t = 0;
+ else if(t >= 1) t = 0.99999f; // doh
+
+ if((*pDef)[L"loop"].IsValue()) t *= (float)(*pDef)[L"loop"];
+
+ CStringW direction = (*pDef)[L"direction"].IsValue() ? (*pDef)[L"direction"] : L"fw";
+ if(direction == L"fwbw" || direction == L"bwfw") t *= 2;
+
+ float n;
+ t = modf(t, &n);
+
+ if(direction == L"bw"
+ || direction == L"fwbw" && ((int)n & 1)
+ || direction == L"bwfw" && !((int)n & 1))
+ t = 1 - t;
+
+ float accel = 1;
+
+ if((*pDef)[L"transition"].IsValue())
+ {
+ Definition::Number<float> n;
+ (*pDef)[L"transition"].GetAsNumber(n, &m_n2n.transition);
+ if(n.value >= 0) accel = n.value;
+ }
+
+ if(t == 0.99999f) t = 1;
+
+ if(u >= 0 && u < 1)
+ {
+ t = accel == 0 ? 1 :
+ accel == 1 ? t :
+ accel >= 1e10 ? 0 :
+ pow(t, accel);
+ }
+ }
+ }
+ catch(Exception&)
+ {
+ }
+
+ return t;
+ }
+
+ template<class T>
+ bool Subtitle::MixValue(Definition& def, T& value, float t)
+ {
+ StringMapW<T> n2n;
+ return MixValue(def, value, t, &n2n);
+ }
+
+ template<>
+ bool Subtitle::MixValue(Definition& def, float& value, float t)
+ {
+ StringMapW<float> n2n;
+ return MixValue(def, value, t, &n2n);
+ }
+
+ template<class T>
+ bool Subtitle::MixValue(Definition& def, T& value, float t, StringMapW<T>* n2n)
+ {
+ if(!def.IsValue()) return false;
+
+ if(t >= 0.5)
+ {
+ if(n2n && def.IsValue(Definition::string))
+ {
+ if(StringMapW<T>::CPair* p = n2n->Lookup(def))
+ {
+ value = p->m_value;
+ return true;
+ }
+ }
+
+ value = (T)def;
+ }
+
+ return true;
+ }
+
+ template<>
+ bool Subtitle::MixValue(Definition& def, float& value, float t, StringMapW<float>* n2n)
+ {
+ if(!def.IsValue()) return false;
+
+ if(t > 0)
+ {
+ if(n2n && def.IsValue(Definition::string))
+ {
+ if(StringMap<float, CStringW>::CPair* p = n2n->Lookup(def))
+ {
+ value += (p->m_value - value) * t;
+ return true;
+ }
+ }
+
+ value += ((float)def - value) * t;
+ }
+
+ return true;
+ }
+
+ template<>
+ bool Subtitle::MixValue(Definition& def, Path& src, float t)
+ {
+ if(!def.IsValue(Definition::string)) return false;
+
+ if(t >= 1)
+ {
+ src = (LPCWSTR)def;
+ }
+ else if(t > 0)
+ {
+ Path dst = (LPCWSTR)def;
+
+ if(src.GetCount() == dst.GetCount())
+ {
+ for(size_t i = 0, j = src.GetCount(); i < j; i++)
+ {
+ Point& s = src[i];
+ const Point& d = dst[i];
+ s.x += (d.x - s.x) * t;
+ s.y += (d.y - s.y) * t;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ void Subtitle::MixStyle(Definition* pDef, Style& dst, float t)
+ {
+ const Style src = dst;
+
+ if(t <= 0) return;
+ else if(t > 1) t = 1;
+
+ MixValue((*pDef)[L"linebreak"], dst.linebreak, t);
+
+ Definition& placement = (*pDef)[L"placement"];
+
+ MixValue(placement[L"clip"][L"t"], dst.placement.clip.t, t);
+ MixValue(placement[L"clip"][L"r"], dst.placement.clip.r, t);
+ MixValue(placement[L"clip"][L"b"], dst.placement.clip.b, t);
+ MixValue(placement[L"clip"][L"l"], dst.placement.clip.l, t);
+ MixValue(placement[L"align"][L"h"], dst.placement.align.h, t, &m_n2n.align[0]);
+ MixValue(placement[L"align"][L"v"], dst.placement.align.v, t, &m_n2n.align[1]);
+ dst.placement.pos.auto_x = !MixValue(placement[L"pos"][L"x"], dst.placement.pos.x, dst.placement.pos.auto_x ? 1 : t);
+ dst.placement.pos.auto_y = !MixValue(placement[L"pos"][L"y"], dst.placement.pos.y, dst.placement.pos.auto_y ? 1 : t);
+ MixValue(placement[L"offset"][L"x"], dst.placement.offset.x, t);
+ MixValue(placement[L"offset"][L"y"], dst.placement.offset.y, t);
+ MixValue(placement[L"angle"][L"x"], dst.placement.angle.x, t);
+ MixValue(placement[L"angle"][L"y"], dst.placement.angle.y, t);
+ MixValue(placement[L"angle"][L"z"], dst.placement.angle.z, t);
+ dst.placement.org.auto_x = !MixValue(placement[L"org"][L"x"], dst.placement.org.x, dst.placement.org.auto_x ? 1 : t);
+ dst.placement.org.auto_y = !MixValue(placement[L"org"][L"y"], dst.placement.org.y, dst.placement.org.auto_y ? 1 : t);
+ MixValue(placement[L"path"], dst.placement.path, t);
+
+ Definition& font = (*pDef)[L"font"];
+
+ MixValue(font[L"face"], dst.font.face, t);
+ MixValue(font[L"size"], dst.font.size, t);
+ MixValue(font[L"weight"], dst.font.weight, t, &m_n2n.weight);
+ MixValue(font[L"color"][L"a"], dst.font.color.a, t);
+ MixValue(font[L"color"][L"r"], dst.font.color.r, t);
+ MixValue(font[L"color"][L"g"], dst.font.color.g, t);
+ MixValue(font[L"color"][L"b"], dst.font.color.b, t);
+ MixValue(font[L"underline"], dst.font.underline, t);
+ MixValue(font[L"strikethrough"], dst.font.strikethrough, t);
+ MixValue(font[L"italic"], dst.font.italic, t);
+ MixValue(font[L"spacing"], dst.font.spacing, t);
+ MixValue(font[L"scale"][L"cx"], dst.font.scale.cx, t);
+ MixValue(font[L"scale"][L"cy"], dst.font.scale.cy, t);
+ MixValue(font[L"kerning"], dst.font.kerning, t);
+
+ Definition& background = (*pDef)[L"background"];
+
+ MixValue(background[L"color"][L"a"], dst.background.color.a, t);
+ MixValue(background[L"color"][L"r"], dst.background.color.r, t);
+ MixValue(background[L"color"][L"g"], dst.background.color.g, t);
+ MixValue(background[L"color"][L"b"], dst.background.color.b, t);
+ MixValue(background[L"size"], dst.background.size, t);
+ MixValue(background[L"type"], dst.background.type, t);
+ MixValue(background[L"blur"], dst.background.blur, t);
+
+ Definition& shadow = (*pDef)[L"shadow"];
+
+ MixValue(shadow[L"color"][L"a"], dst.shadow.color.a, t);
+ MixValue(shadow[L"color"][L"r"], dst.shadow.color.r, t);
+ MixValue(shadow[L"color"][L"g"], dst.shadow.color.g, t);
+ MixValue(shadow[L"color"][L"b"], dst.shadow.color.b, t);
+ MixValue(shadow[L"depth"], dst.shadow.depth, t);
+ MixValue(shadow[L"angle"], dst.shadow.angle, t);
+ MixValue(shadow[L"blur"], dst.shadow.blur, t);
+
+ Definition& fill = (*pDef)[L"fill"];
+
+ MixValue(fill[L"color"][L"a"], dst.fill.color.a, t);
+ MixValue(fill[L"color"][L"r"], dst.fill.color.r, t);
+ MixValue(fill[L"color"][L"g"], dst.fill.color.g, t);
+ MixValue(fill[L"color"][L"b"], dst.fill.color.b, t);
+ MixValue(fill[L"width"], dst.fill.width, t);
+
+ if(fill.m_priority >= PNormal) // this assumes there is no way to set low priority inline overrides
+ {
+ if(dst.fill.id > 0) throw Exception(_T("cannot apply fill more than once on the same text"));
+ dst.fill.id = ++Fill::gen_id;
+ }
+ }
+
+ void Subtitle::Parse(InputStream& s, Style style, float at, StringMapW<float> offset, Reference* pParentRef)
+ {
+ Text text;
+ text.style = style;
+
+ for(int c = s.PeekChar(); c != Stream::EOS; c = s.PeekChar())
+ {
+ s.GetChar();
+
+ if(c == '[')
+ {
+ AddText(text);
+
+ style = text.style;
+
+ StringMapW<float> inneroffset = offset;
+
+ int default_id = 0;
+
+ do
+ {
+ Definition* pDef = m_pFile->CreateDef(pParentRef);
+
+ m_pFile->ParseRefs(s, pDef, L",;]");
+
+ ASSERT(pDef->IsType(L"style") || pDef->IsTypeUnknown());
+
+ if((*pDef)[L"time"][L"start"].IsValue() && (*pDef)[L"time"][L"stop"].IsValue())
+ {
+ m_animated = true;
+ }
+
+ float t = GetMixWeight(pDef, at, offset, ++default_id);
+ MixStyle(pDef, style, t);
+
+ if((*pDef)[L"@"].IsValue())
+ {
+ Parse(WCharInputStream((LPCWSTR)(*pDef)[L"@"]), style, at, offset, pParentRef);
+ }
+
+ s.SkipWhiteSpace();
+ c = s.GetChar();
+ }
+ while(c == ',' || c == ';');
+
+ if(c != ']') s.ThrowError(_T("unterminated override"));
+
+ bool fWhiteSpaceNext = s.IsWhiteSpace(s.PeekChar());
+ c = s.SkipWhiteSpace();
+
+ if(c == '{')
+ {
+ s.GetChar();
+ Parse(s, style, at, inneroffset, pParentRef);
+ }
+ else
+ {
+ if(fWhiteSpaceNext) text.str += (WCHAR)Text::SP;
+ text.style = style;
+ }
+ }
+ else if(c == ']')
+ {
+ s.ThrowError(_T("unexpected ] found"));
+ }
+ else if(c == '{')
+ {
+ AddText(text);
+ Parse(s, text.style, at, offset, pParentRef);
+ }
+ else if(c == '}')
+ {
+ break;
+ }
+ else
+ {
+ if(c == '\\')
+ {
+ c = s.GetChar();
+ if(c == Stream::EOS) break;
+ else if(c == 'n') {AddText(text); text.str = (WCHAR)Text::LSEP; AddText(text); continue;}
+ else if(c == 'h') c = Text::NBSP;
+ }
+
+ AddChar(text, (WCHAR)c);
+ }
+ }
+
+ AddText(text);
+ }
+
+ void Subtitle::AddChar(Text& t, WCHAR c)
+ {
+ bool f1 = !t.str.IsEmpty() && Stream::IsWhiteSpace(t.str[t.str.GetLength()-1]);
+ bool f2 = Stream::IsWhiteSpace(c);
+ if(f2) c = Text::SP;
+ if(!f1 || !f2) t.str += (WCHAR)c;
+ }
+
+ void Subtitle::AddText(Text& t)
+ {
+ if(t.str.IsEmpty()) return;
+
+ Split sa(' ', t.str, 0, Split::Max);
+
+ for(size_t i = 0, n = sa; i < n; i++)
+ {
+ CStringW str = sa[i];
+
+ if(!str.IsEmpty())
+ {
+ t.str = str;
+ m_text.AddTail(t);
+ }
+
+ if(i < n-1 && (m_text.IsEmpty() || m_text.GetTail().str != Text::SP))
+ {
+ t.str = (WCHAR)Text::SP;
+ m_text.AddTail(t);
+ }
+ }
+
+ t.str.Empty();
+ }
+
+ //
+
+ unsigned int Fill::gen_id = 0;
+
+ Color::operator DWORD()
+ {
+ DWORD c =
+ (min(max((DWORD)b, 0), 255) << 0) |
+ (min(max((DWORD)g, 0), 255) << 8) |
+ (min(max((DWORD)r, 0), 255) << 16) |
+ (min(max((DWORD)a, 0), 255) << 24);
+
+ return c;
+ }
+
+ Path& Path::operator = (LPCWSTR str)
+ {
+ Split s(' ', str);
+
+ SetCount(s/2);
+
+ for(size_t i = 0, j = GetCount(); i < j; i++)
+ {
+ Point p;
+ p.x = s.GetAtFloat(i*2+0);
+ p.y = s.GetAtFloat(i*2+1);
+ SetAt(i, p);
+ }
+
+ return *this;
+ }
+
+ CStringW Path::ToString()
+ {
+ CStringW ret;
+
+ for(size_t i = 0, j = GetCount(); i < j; i++)
+ {
+ const Point& p = GetAt(i);
+
+ CStringW str;
+ str.Format(L"%f %f ", p.x, p.y);
+ ret += str;
+ }
+
+ return ret;
+ }
+
+ bool Style::IsSimilar(const Style& s)
+ {
+ return
+ font.color.r == s.font.color.r
+ && font.color.g == s.font.color.g
+ && font.color.b == s.font.color.b
+ && font.color.a == s.font.color.a
+ && background.color.r == s.background.color.r
+ && background.color.g == s.background.color.g
+ && background.color.b == s.background.color.b
+ && background.color.a == s.background.color.a
+ && background.size == s.background.size
+ && background.type == s.background.type
+ && background.blur == s.background.blur
+ && shadow.color.r == s.shadow.color.r
+ && shadow.color.g == s.shadow.color.g
+ && shadow.color.b == s.shadow.color.b
+ && shadow.color.a == s.shadow.color.a
+ && shadow.depth == s.shadow.depth
+ && shadow.angle == s.shadow.angle
+ && shadow.blur == s.shadow.blur
+ && fill.id == s.fill.id;
+ }
+}
diff --git a/src/Subtitles/libssf/Subtitle.h b/src/Subtitles/libssf/Subtitle.h
new file mode 100644
index 000000000..a1cedc3c0
--- /dev/null
+++ b/src/Subtitles/libssf/Subtitle.h
@@ -0,0 +1,138 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include "File.h"
+
+namespace ssf
+{
+ struct Point {float x, y;};
+ struct PointAuto : public Point {bool auto_x, auto_y;};
+ struct Size {float cx, cy;};
+ struct Rect {float t, r, b, l;};
+ struct Align {float v, h;};
+ struct Angle {float x, y, z;};
+ struct Color {float a, r, g, b; operator DWORD(); void operator = (DWORD c);};
+ struct Frame {CStringW reference; Size resolution;};
+ struct Direction {CStringW primary, secondary;};
+ struct Time {float start, stop;};
+ struct Background {Color color; float size, blur; CStringW type;};
+ struct Shadow {Color color; float depth, angle, blur;};
+
+ class Path : public CAtlArray<Point>
+ {
+ public:
+ Path() {}
+ Path(const Path& path) {*this = path;}
+ Path& operator = (const Path& path) {Copy(path); return *this;}
+ Path(LPCWSTR str) {*this = str;}
+ Path& operator = (LPCWSTR str);
+ CStringW ToString();
+ };
+
+ struct Placement
+ {
+ Rect clip, margin;
+ Align align;
+ PointAuto pos;
+ Point offset;
+ Angle angle;
+ PointAuto org;
+ Path path;
+ };
+
+ struct Font
+ {
+ CStringW face;
+ float size, weight;
+ Color color;
+ bool underline, strikethrough, italic;
+ float spacing;
+ Size scale;
+ bool kerning;
+ };
+
+ struct Fill
+ {
+ static unsigned int gen_id;
+ int id;
+ Color color;
+ float width;
+ struct Fill() : id(0) {}
+ };
+
+ struct Style
+ {
+ CStringW linebreak;
+ Placement placement;
+ Font font;
+ Background background;
+ Shadow shadow;
+ Fill fill;
+
+ bool IsSimilar(const Style& s);
+ };
+
+ struct Text
+ {
+ enum {SP = 0x20, NBSP = 0xa0, LSEP = 0x0a};
+ Style style;
+ CStringW str;
+ };
+
+ class Subtitle
+ {
+ static struct n2n_t {StringMapW<float> align[2], weight, transition;} m_n2n;
+
+ File* m_pFile;
+
+ void GetStyle(Definition* pDef, Style& style);
+ float GetMixWeight(Definition* pDef, float at, StringMapW<float>& offset, int default_id);
+ template<class T> bool MixValue(Definition& def, T& value, float t);
+ template<> bool MixValue(Definition& def, float& value, float t);
+ template<class T> bool MixValue(Definition& def, T& value, float t, StringMapW<T>* n2n);
+ template<> bool MixValue(Definition& def, float& value, float t, StringMapW<float>* n2n);
+ template<> bool MixValue(Definition& def, Path& value, float t);
+ void MixStyle(Definition* pDef, Style& dst, float t);
+
+ void Parse(InputStream& s, Style style, float at, StringMapW<float> offset, Reference* pParentRef);
+
+ void AddChar(Text& t, WCHAR c);
+ void AddText(Text& t);
+
+ public:
+ CStringW m_name;
+ bool m_animated;
+ Frame m_frame;
+ Direction m_direction;
+ CStringW m_wrap;
+ float m_layer;
+ Time m_time;
+ CAtlList<Text> m_text;
+
+ public:
+ Subtitle(File* pFile);
+ virtual ~Subtitle();
+
+ bool Parse(Definition* pDef, float start, float stop, float at);
+ };
+}; \ No newline at end of file
diff --git a/src/Subtitles/libssf/SubtitleFile.cpp b/src/Subtitles/libssf/SubtitleFile.cpp
new file mode 100644
index 000000000..38736efec
--- /dev/null
+++ b/src/Subtitles/libssf/SubtitleFile.cpp
@@ -0,0 +1,403 @@
+/*
+ * 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 "SubtitleFile.h"
+
+namespace ssf
+{
+ SubtitleFile::SubtitleFile()
+ {
+ }
+
+ SubtitleFile::~SubtitleFile()
+ {
+ }
+
+ void SubtitleFile::Parse(InputStream& s)
+ {
+ m_segments.RemoveAll();
+
+ __super::Parse(s, s_predef);
+
+ // TODO: check file.format == "ssf" and file.version == 1
+
+ CAtlList<Definition*> defs;
+ GetRootRef()->GetChildDefs(defs, L"subtitle");
+
+ StringMapW<float> offset;
+
+ POSITION pos = defs.GetHeadPosition();
+ while(pos)
+ {
+ Definition* pDef = defs.GetNext(pos);
+
+ try
+ {
+ Definition::Time time;
+
+ if(pDef->GetAsTime(time, offset) && (*pDef)[L"@"].IsValue())
+ {
+ m_segments.Insert(time.start.value, time.stop.value, pDef);
+ }
+ }
+ catch(Exception&)
+ {
+ }
+ }
+ }
+
+ void SubtitleFile::Append(InputStream& s, float start, float stop, bool fSetTime)
+ {
+ Reference* pRootRef = GetRootRef();
+
+ ParseDefs(s, pRootRef);
+
+ CAtlList<Definition*> defs;
+ GetNewDefs(defs);
+
+ POSITION pos = defs.GetHeadPosition();
+ while(pos)
+ {
+ Definition* pDef = defs.GetNext(pos);
+
+ if(pDef->m_parent == pRootRef && pDef->m_type == L"subtitle" && (*pDef)[L"@"].IsValue())
+ {
+ m_segments.Insert(start, stop, pDef);
+
+ if(fSetTime)
+ {
+ try
+ {
+ Definition::Time time;
+ StringMapW<float> offset;
+ pDef->GetAsTime(time, offset);
+ if(time.start.value == start && time.stop.value == stop)
+ continue;
+ }
+ catch(Exception&)
+ {
+ }
+
+ CStringW str;
+ str.Format(L"%.3f", start);
+ pDef->SetChildAsNumber(L"time.start", str, L"s");
+ str.Format(L"%.3f", stop);
+ pDef->SetChildAsNumber(L"time.stop", str, L"s");
+ }
+ }
+ }
+
+ Commit();
+ }
+
+ bool SubtitleFile::Lookup(float at, CAutoPtrList<Subtitle>& subs)
+ {
+ if(!subs.IsEmpty()) {ASSERT(0); return false;}
+
+ CAtlList<SegmentItem> sis;
+ m_segments.Lookup(at, sis);
+
+ POSITION pos = sis.GetHeadPosition();
+ while(pos)
+ {
+ SegmentItem& si = sis.GetNext(pos);
+
+ CAutoPtr<Subtitle> s(DNew Subtitle(this));
+
+ if(s->Parse(si.pDef, si.start, si.stop, at))
+ {
+ for(POSITION pos = subs.GetHeadPosition(); pos; subs.GetNext(pos))
+ {
+ if(s->m_layer < subs.GetAt(pos)->m_layer)
+ {
+ subs.InsertBefore(pos, s);
+ break;
+ }
+ }
+
+ if(s)
+ {
+ subs.AddTail(s);
+ }
+ }
+ }
+
+ return !subs.IsEmpty();
+ }
+
+ //
+
+ SubtitleFile::Segment::Segment(float start, float stop, const SegmentItem* si)
+ {
+ m_start = start;
+ m_stop = stop;
+ if(si) AddTail(*si);
+ }
+
+ SubtitleFile::Segment::Segment(const Segment& s)
+ {
+ *this = s;
+ }
+
+ void SubtitleFile::Segment::operator = (const Segment& s)
+ {
+ m_start = s.m_start;
+ m_stop = s.m_stop;
+ RemoveAll();
+ AddTailList(&s);
+ }
+
+ //
+
+ void SubtitleFile::SegmentList::RemoveAll()
+ {
+ __super::RemoveAll();
+ m_index.RemoveAll();
+ }
+
+ void SubtitleFile::SegmentList::Insert(float start, float stop, Definition* pDef)
+ {
+ if(start >= stop) {ASSERT(0); return;}
+
+ m_index.RemoveAll();
+
+ SegmentItem si = {pDef, start, stop};
+
+ if(IsEmpty())
+ {
+ AddTail(Segment(start, stop, &si));
+ return;
+ }
+
+ Segment& head = GetHead();
+ Segment& tail = GetTail();
+
+ if(start >= tail.m_stop && stop > tail.m_stop)
+ {
+ if(start > tail.m_stop) AddTail(Segment(tail.m_stop, start));
+ AddTail(Segment(start, stop, &si));
+ }
+ else if(start < head.m_start && stop <= head.m_start)
+ {
+ if(stop < head.m_start) AddHead(Segment(stop, head.m_start));
+ AddHead(Segment(start, stop, &si));
+ }
+ else
+ {
+ if(start < head.m_start)
+ {
+ AddHead(Segment(start, head.m_start, &si));
+ start = head.m_start;
+ }
+
+ if(stop > tail.m_stop)
+ {
+ AddTail(Segment(tail.m_stop, stop, &si));
+ stop = tail.m_stop;
+ }
+
+ for(POSITION pos = GetHeadPosition(); pos; GetNext(pos))
+ {
+ Segment& s = GetAt(pos);
+
+ if(start >= s.m_stop) continue;
+ if(stop <= s.m_start) break;
+
+ if(s.m_start < start && start < s.m_stop)
+ {
+ Segment s2 = s;
+ s2.m_start = start;
+ InsertAfter(pos, s2);
+ s.m_stop = start;
+ }
+ else if(s.m_start == start)
+ {
+ if(stop > s.m_stop)
+ {
+ start = s.m_stop;
+ }
+ else if(stop < s.m_stop)
+ {
+ Segment s2 = s;
+ s2.m_start = stop;
+ InsertAfter(pos, s2);
+ s.m_stop = stop;
+ }
+
+ s.AddTail(si);
+ }
+ }
+ }
+ }
+
+ size_t SubtitleFile::SegmentList::Index(bool fForce)
+ {
+ if(m_index.IsEmpty() || fForce)
+ {
+ m_index.RemoveAll();
+ POSITION pos = GetHeadPosition();
+ while(pos) m_index.Add(&GetNext(pos));
+ }
+
+ return m_index.GetCount();
+ }
+
+ void SubtitleFile::SegmentList::Lookup(float at, CAtlList<SegmentItem>& sis)
+ {
+ sis.RemoveAll();
+
+ size_t k;
+ if(Lookup(at, k))
+ {
+ sis.AddTailList(GetSegment(k));
+ }
+ }
+
+ bool SubtitleFile::SegmentList::Lookup(float at, size_t& k)
+ {
+ if(!Index()) return false;
+
+ size_t i = 0, j = m_index.GetCount()-1;
+
+ if(m_index[i]->m_start <= at && at < m_index[j]->m_stop)
+ do
+ {
+ k = (i+j)/2;
+ if(m_index[k]->m_start <= at && at < m_index[k]->m_stop) {return true;}
+ else if(at < m_index[k]->m_start) {if(j == k) k--; j = k;}
+ else if(at >= m_index[k]->m_stop) {if(i == k) k++; i = k;}
+ }
+ while(i <= j);
+
+ return false;
+ }
+
+ const SubtitleFile::Segment* SubtitleFile::SegmentList::GetSegment(size_t k)
+ {
+ return 0 <= k && k < m_index.GetCount() ? m_index[k] : NULL;
+ }
+
+ // TODO: this should be overridable from outside
+
+ LPCWSTR SubtitleFile::s_predef =
+ L"color#white {a: 255; r: 255; g: 255; b: 255;}; \n"
+ L"color#black {a: 255; r: 0; g: 0; b: 0;}; \n"
+ L"color#gray {a: 255; r: 128; g: 128; b: 128;}; \n"
+ L"color#red {a: 255; r: 255; g: 0; b: 0;}; \n"
+ L"color#green {a: 255; r: 0; g: 255; b: 0;}; \n"
+ L"color#blue {a: 255; r: 0; g: 0; b: 255;}; \n"
+ L"color#cyan {a: 255; r: 0; g: 255; b: 255;}; \n"
+ L"color#yellow {a: 255; r: 255; g: 255; b: 0;}; \n"
+ L"color#magenta {a: 255; r: 255; g: 0; b: 255;}; \n"
+ L" \n"
+ L"align#topleft {v: \"top\"; h: \"left\";}; \n"
+ L"align#topcenter {v: \"top\"; h: \"center\";}; \n"
+ L"align#topright {v: \"top\"; h: \"right\";}; \n"
+ L"align#middleleft {v: \"middle\"; h: \"left\";}; \n"
+ L"align#middlecenter {v: \"middle\"; h: \"center\";}; \n"
+ L"align#middleright {v: \"middle\"; h: \"right\";}; \n"
+ L"align#bottomleft {v: \"bottom\"; h: \"left\";}; \n"
+ L"align#bottomcenter {v: \"bottom\"; h: \"center\";}; \n"
+ L"align#bottomright {v: \"bottom\"; h: \"right\";}; \n"
+ L" \n"
+ L"time#time {scale: 1;}; \n"
+ L"time#startstop {start: \"start\"; stop: \"stop\";}; \n"
+ L" \n"
+ L"#b {font.weight: \"bold\"}; \n"
+ L"#i {font.italic: \"true\"}; \n"
+ L"#u {font.underline: \"true\"}; \n"
+ L"#s {font.strikethrough: \"true\"}; \n"
+ L" \n"
+ L"#nobr {linebreak: \"none\"}; \n"
+ L" \n"
+ L"subtitle#subtitle \n"
+ L"{ \n"
+ L" frame \n"
+ L" { \n"
+ L" reference: \"video\"; \n"
+ L" resolution: {cx: 640; cy: 480;}; \n"
+ L" }; \n"
+ L" \n"
+ L" direction \n"
+ L" { \n"
+ L" primary: \"right\"; \n"
+ L" secondary: \"down\"; \n"
+ L" }; \n"
+ L" \n"
+ L" wrap: \"normal\"; \n"
+ L" \n"
+ L" layer: 0; \n"
+ L" \n"
+ L" style \n"
+ L" { \n"
+ L" linebreak: \"word\"; \n"
+ L" \n"
+ L" placement \n"
+ L" { \n"
+ L" clip: \"none\"; \n"
+ L" margin: {t: 0; r: 0; b: 0; l: 0;}; \n"
+ L" align: bottomcenter; \n"
+ L" pos: \"auto\" \n"
+ L" offset: {x: 0; y: 0;}; \n"
+ L" angle: {x: 0; y: 0; z: 0;}; \n"
+ L" org: \"auto\" \n"
+ L" path: \"\"; \n"
+ L" }; \n"
+ L" \n"
+ L" font \n"
+ L" { \n"
+ L" face: \"Arial\"; \n"
+ L" size: 20; \n"
+ L" weight: \"bold\"; \n"
+ L" color: white; \n"
+ L" underline: \"false\"; \n"
+ L" strikethrough: \"false\"; \n"
+ L" italic: \"false\"; \n"
+ L" spacing: 0; \n"
+ L" scale: {cx: 1; cy: 1;}; \n"
+ L" kerning: \"true\"; \n"
+ L" }; \n"
+ L" \n"
+ L" background \n"
+ L" { \n"
+ L" color: black; \n"
+ L" size: 2; \n"
+ L" type: \"outline\"; \n"
+ L" blur: 0; \n"
+ L" }; \n"
+ L" \n"
+ L" shadow \n"
+ L" { \n"
+ L" color: black {a: 128;}; \n"
+ L" depth: 2; \n"
+ L" angle: -45; \n"
+ L" blur: 0; \n"
+ L" }; \n"
+ L" \n"
+ L" fill \n"
+ L" { \n"
+ L" color: yellow; \n"
+ L" width: 0; \n"
+ L" }; \n"
+ L" }; \n"
+ L"}; \n";
+}
diff --git a/src/Subtitles/libssf/SubtitleFile.h b/src/Subtitles/libssf/SubtitleFile.h
new file mode 100644
index 000000000..b58798d53
--- /dev/null
+++ b/src/Subtitles/libssf/SubtitleFile.h
@@ -0,0 +1,73 @@
+/*
+ * 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
+ *
+ */
+
+#pragma once
+
+#include "File.h"
+#include "Subtitle.h"
+
+namespace ssf
+{
+ class SubtitleFile : public File
+ {
+ static LPCWSTR s_predef;
+
+ public:
+ struct SegmentItem
+ {
+ Definition* pDef;
+ float start, stop;
+ };
+
+ class Segment : public CAtlList<SegmentItem>
+ {
+ public:
+ float m_start, m_stop;
+ Segment() {}
+ Segment(float start, float stop, const SegmentItem* si = NULL);
+ Segment(const Segment& s);
+ void operator = (const Segment& s);
+ };
+
+ class SegmentList : public CAtlList<Segment>
+ {
+ CAtlArray<Segment*> m_index;
+ size_t Index(bool fForce = false);
+
+ public:
+ void RemoveAll();
+ void Insert(float start, float stop, Definition* pDef);
+ void Lookup(float at, CAtlList<SegmentItem>& sis);
+ bool Lookup(float at, size_t& k);
+ const Segment* GetSegment(size_t k);
+ };
+
+ SegmentList m_segments;
+
+ public:
+ SubtitleFile();
+ virtual ~SubtitleFile();
+
+ void Parse(InputStream& s);
+ void Append(InputStream& s, float start, float stop, bool fSetTime = false);
+ bool Lookup(float at, CAutoPtrList<Subtitle>& subs);
+ };
+} \ No newline at end of file
diff --git a/src/Subtitles/libssf/demo/demo.ssa b/src/Subtitles/libssf/demo/demo.ssa
new file mode 100644
index 000000000..f6a05fad7
--- /dev/null
+++ b/src/Subtitles/libssf/demo/demo.ssa
@@ -0,0 +1,87 @@
+[Script Info]
+; This is a Sub Station Alpha v4 script.
+; For Sub Station Alpha info and downloads,
+; go to http://www.eswat.demon.co.uk/
+; or email kotus@eswat.demon.co.uk
+;
+ScriptType: v4.00
+Collisions: Normal
+PlayResX: 320
+PlayResY: 240
+Timer: 100.0000
+
+[V4 Styles]
+Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding
+Style: MainB,Arial,14,&H00ffff,&H00ff00,&H000000,&H000000, 0,-1,1,2,4,1,16,16,16,0,0
+Style: Default,Arial,18,&Hffffff,&H00ffff,&H000000,&H000000, -1, 0,1,2,3,2,20,20,20,0,1
+Style: MainT,Arial,14,&H00ffff,&H00ff00,&H000000,&H000000, 0, 0,1,2,4,5,16,16,16,0,0
+Style: Karaoke,Arial,14,&H40ffff,&Hff4040,&H000000,&H000000, 0, 0,1,2,4,5,16,16,16,0,0
+Style: B4S4B,Arial,14,&He0e0e0,&H00ff00,&H804000,&H804000, 0,-1,1,4,4,5,16,16,40,0,0
+Style: B0S0K,Arial,14,&He0e0e0,&H00ff00,&H000000,&H000000, 0,-1,1,0,0,5,16,16,40,0,0
+Style: B2S2R,Arial,14,&He0e0e0,&H00ff00,&H004080,&H004080, 0,-1,1,2,2,5,16,16,40,0,0
+Style: B2S6G,Arial,14,&He0e0e0,&H00ff00,&H408000,&H408000, 0,-1,1,2,6,5,16,16,40,0,0
+Style: ShiftJIS,MS Gothic,20,&He0e0e0,&H00ff00,&H000000,&H000000, 0, 0,1,2,4,5,16,16,16,0,128
+Style: Timer,Arial,14,&H00ffff,&H00ff00,&H000000,&H000000, 0,-1,1,2,4,5,16,16,40,0,0
+
+
+[Events]
+Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
+Dialogue: Marked=0,0:00:00.00,0:00:05.00,MainB,,0000,0000,0000,!Effect,{\q1}Subtitler filter for VirtualDub\NDemonstration script\N\NAvery Lee <phaeron@virtualdub.org>
+Dialogue: Marked=0,0:00:05.00,0:00:10.00,MainB,,0000,0000,0000,!Effect,{\q1}Introduction\N\NThis script will demonstrate some of the simpler features of the subtitler. You will want to refer to the {\fnCourier New\b1}readme.txt{\r} file for a more complete reference.
+Dialogue: Marked=0,0:00:10.00,0:00:21.00,MainB,,0000,0000,0000,!Effect,{\q1}Dialogue lines\N\NScripts are based on dialogue lines. Each dialogue line has a start time, an end time, and text to display in between.
+Dialogue: Marked=0,0:00:10.00,0:00:11.00,Timer,,0000,0000,0000,!Effect,0:00
+Dialogue: Marked=0,0:00:11.00,0:00:12.00,Timer,,0000,0000,0000,!Effect,0:01
+Dialogue: Marked=0,0:00:12.00,0:00:13.00,Timer,,0000,0000,0000,!Effect,0:02
+Dialogue: Marked=0,0:00:13.00,0:00:14.00,Timer,,0000,0000,0000,!Effect,0:03
+Dialogue: Marked=0,0:00:14.00,0:00:15.00,Timer,,0000,0000,0000,!Effect,0:04
+Dialogue: Marked=0,0:00:15.00,0:00:16.00,Timer,,0000,0000,0000,!Effect,0:05
+Dialogue: Marked=0,0:00:16.00,0:00:17.00,Timer,,0000,0000,0000,!Effect,0:06
+Dialogue: Marked=0,0:00:17.00,0:00:18.00,Timer,,0000,0000,0000,!Effect,0:07
+Dialogue: Marked=0,0:00:18.00,0:00:19.00,Timer,,0000,0000,0000,!Effect,0:08
+Dialogue: Marked=0,0:00:19.00,0:00:20.00,Timer,,0000,0000,0000,!Effect,0:09
+Dialogue: Marked=0,0:00:20.00,0:00:21.00,Timer,,0000,0000,0000,!Effect,0:10
+Dialogue: Marked=0,0:00:12.00,0:00:15.00,MainT,,0000,0000,0000,!Effect,{\c&H80FF00&}Hello.
+Dialogue: Marked=0,0:00:16.00,0:00:19.00,MainT,,0000,0000,0000,!Effect,{\c&HE0E0E0&}Oh, hi.
+Dialogue: Marked=0,0:00:10.00,0:00:12.00,MainT,,0000,0000,0080,!Effect,{\c&H407F00&}[02:00-05:00] Hello.
+Dialogue: Marked=0,0:00:12.00,0:00:15.00,MainT,,0000,0000,0080,!Effect,{\c&H80FF00&}[02:00-05:00] Hello.
+Dialogue: Marked=0,0:00:15.00,0:00:21.00,MainT,,0000,0000,0080,!Effect,{\c&H407F00&}[02:00-05:00] Hello.
+Dialogue: Marked=0,0:00:10.00,0:00:16.00,MainT,,0000,0000,0080,!Effect,{\c&H707070&}[06:00-09:00] Oh, hi.
+Dialogue: Marked=0,0:00:16.00,0:00:19.00,MainT,,0000,0000,0080,!Effect,{\c&HE0E0E0&}[06:00-09:00] Oh, hi.
+Dialogue: Marked=0,0:00:19.00,0:00:21.00,MainT,,0000,0000,0080,!Effect,{\c&H707070&}[06:00-09:00] Oh, hi.
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0055,0055,0000,!Effect,{\q1\a10}Alignment\N\NDialogue lines may be placed in any of three horizontal placements and any of three vertical placements.
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0008,0008,0008,!Effect,{\a1\c&HE0E0E0&}Bottom\Nleft
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0008,0008,0008,!Effect,{\a2\c&HE0E0E0&}Bottom\Ncenter
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0008,0008,0008,!Effect,{\a3\c&HE0E0E0&}Bottom\Nright
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0008,0008,0008,!Effect,{\a5\c&HE0E0E0&}Top\Nleft
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0008,0008,0008,!Effect,{\a6\c&HE0E0E0&}Top\Ncenter
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0008,0008,0008,!Effect,{\a7\c&HE0E0E0&}Top\Nright
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0008,0008,0008,!Effect,{\a9\c&HE0E0E0&}Middle\Nleft
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0008,0008,0008,!Effect,{\a11\c&HE0E0E0&}Middle\Nright
+Dialogue: Marked=0,0:00:35.00,0:00:45.00,MainB,,0000,0000,0000,!Effect,{\q1}Text styles\N\NText may be varied in font, size, color, and style.
+Dialogue: Marked=0,0:00:35.00,0:00:45.00,MainT,,0000,0000,0000,!Effect,{\q1}You can have your text in {\c&H4060FF&}red{\r}, {\c&H60FF40&}green{\r}, and {\c&HFF6040&}blue{\r}; {\fnCourier New}Courier{\r}; {\fs16}16 point{\r} or {\fs32}32 point{\r}; {\b1}bold{\r}, {\i1}italic{\r}, or even {\b1\i1}bold italic{\r}.
+Dialogue: Marked=0,0:00:47.00,0:00:57.00,MainB,,0000,0000,0000,!Effect,{\q1}Wrapping styles\N\NThree wrapping modes are supported: manual only, automatic wrapping, and smart wrapping. Smart wrapping is automatic wrapping, but with the lines broken as evenly as possible.
+Dialogue: Marked=0,0:00:47.00,0:00:57.00,MainT,,0000,0000,0000,!Effect,{\q2\c&HE0FF80&}This line uses manual wrapping and won't\nbe wrapped except where explicitly broken.
+Dialogue: Marked=0,0:00:47.00,0:00:57.00,MainT,,0000,0000,0045,!Effect,{\q1\c&H80E0FF&}This line uses automatic wrapping and is broken automatically.
+Dialogue: Marked=0,0:00:47.00,0:00:57.00,MainT,,0000,0000,0090,!Effect,{\q0\a6\c&HFFE080&}This line uses smart wrapping and is broken with even lines. It's good for centered text.
+Dialogue: Marked=0,0:00:59.00,0:01:24.00,MainB,,0000,0000,0000,!Effect,{\q1}Collisions\N\NIf two dialogue items attempt to display at the same time and place, the second one is pushed clear of the first. However, text stays in place after it is resolved, even if the collider disappears.
+Dialogue: Marked=0,0:01:02.00,0:01:07.00,MainT,,0000,0000,0000,!Effect,{\q1\c&HE0E0E0&}Man, I gotta get out of here!
+Dialogue: Marked=0,0:01:07.00,0:01:17.00,MainT,,0000,0000,0000,!Effect,{\q1\c&HE0E0E0&}Which way should I go? I'm not going back to school....
+Dialogue: Marked=0,0:01:10.00,0:01:21.00,MainT,,0000,0000,0000,!Effect,{\q1\a7\c&H50D0FF&}Ranma, you're in the way! Move!
+Dialogue: Marked=0,0:01:15.00,0:01:21.30,MainT,,0000,0000,0000,!Effect,{\q1\c&HE0E0E0&}Why would I move for an uncute tomboy like you?
+Dialogue: Marked=0,0:01:21.00,0:01:25.00,MainT,,0000,0000,0000,!Effect,{\q1\a7\c&H50D0FF&}Ranma no baka!!!
+Dialogue: Marked=0,0:01:21.50,0:01:24.00,MainT,,0000,0000,0000,!Effect,{\q1\c&HE0E0E0&}*wham*
+Dialogue: Marked=0,0:01:26.00,0:01:36.00,MainB,,0000,0000,0000,!Effect,{\q1}Shadows and borders\N\NYou can vary the depth of the translucent shadow as well as the thickness and color of the border.
+Dialogue: Marked=0,0:01:26.00,0:01:36.00,B0S0K,,0000,0000,0000,!Effect,Border=0, shadow=0
+Dialogue: Marked=0,0:01:26.00,0:01:36.00,B2S2R,,0000,0000,0030,!Effect,Border=2, shadow=2 (red)
+Dialogue: Marked=0,0:01:26.00,0:01:36.00,B2S6G,,0000,0000,0060,!Effect,Border=2, shadow=6 (green)
+Dialogue: Marked=0,0:01:26.00,0:01:36.00,B4S4B,,0000,0000,0090,!Effect,Border=4, shadow=4 (blue)
+Dialogue: Marked=0,0:01:38.00,0:01:48.00,MainB,,0000,0000,0000,!Effect,{\q1}Karaoke\N\NLetting people sing to music is dangerous. But if they're going to do it anyway, they might as well have some help. Both snap and smooth styles are supported.
+Dialogue: Marked=0,0:01:40.00,0:01:45.00,Karaoke,,0000,0000,0000,Karaoke,{\fs18\k100}one {\k100}two {\k50}three-{\k50}and {\k100}four
+Dialogue: Marked=0,0:01:40.00,0:01:45.00,Karaoke,,0000,0000,0030,Karaoke,{\fs18\K100}one {\K100}two {\K50}three-{\K50}and {\K100}four
+Dialogue: Marked=0,0:01:50.00,0:02:00.00,MainB,,0000,0000,0000,!Effect,{\q1}DBCS (double-byte character set) support\N\NIf you have the appropriate language support installed, you can display far-east text in your subtitles. (By the way, I don't really know Japanese, so I'm kind of winged it above with the JWPce dictionary.)
+Dialogue: Marked=0,0:01:50.00,0:02:00.00,ShiftJIS,,0000,0000,0000,!Effect,{\q1}{\fe0}[Keitarou]{\fe128}‚¾BBB‘åä•vH
+Dialogue: Marked=0,0:01:52.00,0:02:00.00,ShiftJIS,,0000,0000,0000,!Effect,{\a7\q1\c&HFFFF00&\fe0}[Naru]{\fe128}‰½‚æI
+Dialogue: Marked=0,0:02:02.00,0:02:17.00,MainB,,0000,0000,0000,!Effect,{\q1}Motion effects\N\NYou can scroll text in like a banner, or up the screen.
+Dialogue: Marked=0,0:02:02.00,0:02:17.00,MainT,,0000,0000,0000,Banner;30,Text is fun. We like scrolling text.
+Dialogue: Marked=0,0:02:02.00,0:02:17.00,MainT,,0000,0000,0000,Scroll Up;40;50;120,{\q1}You can scroll lots of text up this way. Personally, I find it a little annoying to have a lot of text coming up very slowly on screen, but it's a useful technique to have for credits and other long lists in tabular form.
+Dialogue: Marked=0,0:02:19.00,0:02:50.00,MainB,,0000,0000,0000,!Effect,{\q1}Conclusion\N\NThat concludes the demo. I hope you've enjoyed the brief glimpse I've given you here, and encourage you to read the {\fnCourier New\b1}readme.txt{\r} file for more information on the effects you've seen here. Also, if you're bored, try loading this script into Kotus' Sub Station Alpha V4 program. Not all effects will render correctly, but most of the file will. Have fun, and keep on subtitling!\N\N{\fnCourier New}--{\r}Avery Lee <phaeron@virtualdub.org>\N{\fnCourier New}\h\h{\r}Author of VirtualDub and the 'subtitler' filter
diff --git a/src/Subtitles/libssf/demo/demo.ssf b/src/Subtitles/libssf/demo/demo.ssf
new file mode 100644
index 000000000..33258144e
--- /dev/null
+++ b/src/Subtitles/libssf/demo/demo.ssf
@@ -0,0 +1,595 @@
+/*
+ This file is the direct translation of demo.ssa of the "subtitler" virtualdub plugin
+*/
+
+file#file
+{
+ format: "ssf";
+ version: 1;
+};
+
+/*
+PlayResX: 320
+PlayResY: 240
+*/
+
+subtitle#subtitle
+{
+ frame
+ {
+ reference: "video";
+ resolution: {cx: 320; cy: 240;};
+ };
+
+ style
+ {
+ placement {align: bottomleft; margin {t: 16; r: 16; b: 16; l: 16;};};
+ font {size: 14; weight: "normal"; color: yellow;};
+ shadow.depth: 4;
+ };
+};
+
+/*
+Style: MainB,Arial,14,&H00ffff,&H00ff00,&H000000,&H000000, 0,-1,1,2,4,1,16,16,16,0,0
+Style: Default,Arial,18,&Hffffff,&H00ffff,&H000000,&H000000, -1, 0,1,2,3,2,20,20,20,0,1
+Style: MainT,Arial,14,&H00ffff,&H00ff00,&H000000,&H000000, 0, 0,1,2,4,5,16,16,16,0,0
+Style: Karaoke,Arial,14,&H40ffff,&Hff4040,&H000000,&H000000, 0, 0,1,2,4,5,16,16,16,0,0
+Style: B4S4B,Arial,14,&He0e0e0,&H00ff00,&H804000,&H804000, 0,-1,1,4,4,5,16,16,40,0,0
+Style: B0S0K,Arial,14,&He0e0e0,&H00ff00,&H000000,&H000000, 0,-1,1,0,0,5,16,16,40,0,0
+Style: B2S2R,Arial,14,&He0e0e0,&H00ff00,&H004080,&H004080, 0,-1,1,2,2,5,16,16,40,0,0
+Style: B2S6G,Arial,14,&He0e0e0,&H00ff00,&H408000,&H408000, 0,-1,1,2,6,5,16,16,40,0,0
+Style: ShiftJIS,MS Gothic,20,&He0e0e0,&H00ff00,&H000000,&H000000, 0, 0,1,2,4,5,16,16,16,0,128
+Style: Timer,Arial,14,&H00ffff,&H00ff00,&H000000,&H000000, 0,-1,1,2,4,5,16,16,40,0,0
+*/
+
+#MainT {placement.align: topleft;};
+#MainB {font.italic: "true";};
+#Karaoke : MainT {font.color.b: 0x40;};
+#Timer : MainT MainB {placement.margin.t: 40;};
+#BxSxC : Timer {font.color {r: 0xe0; g: 0xe0; b: 0xe0;};};
+#B4S4B : BxSxC {background {color: {r: 0x00; g: 0x40; b: 0x80;}; size: 4;};};
+#B0S0K : BxSxC {background.size: 0; shadow.depth: 0;};
+#B2S2R : BxSxC {background.color {r: 0x80; g: 0x40; b: 0x00;}; shadow.depth: 2;};
+#B2S6G : BxSxC {background.color {r: 0x00; g: 0x80; b: 0x40;}; shadow.depth: 6;};
+#ShiftJIS : MainT {font {face: "MS Gothic"; size: 20; color: {r: 0xe0; g: 0xe0; b: 0xe0;};};};
+
+/*
+Dialogue: Marked=0,0:00:00.00,0:00:05.00,MainB,,0000,0000,0000,!Effect,{\q1}Subtitler filter for VirtualDub\NDemonstration script\N\NAvery Lee <phaeron@virtualdub.org>
+Dialogue: Marked=0,0:00:05.00,0:00:10.00,MainB,,0000,0000,0000,!Effect,{\q1}Introduction\N\NThis script will demonstrate some of the simpler features of the subtitler. You will want to refer to the {\fnCourier New\b1}readme.txt{\r} file for a more complete reference.
+Dialogue: Marked=0,0:00:10.00,0:00:21.00,MainB,,0000,0000,0000,!Effect,{\q1}Dialogue lines\N\NScripts are based on dialogue lines. Each dialogue line has a start time, an end time, and text to display in between.
+*/
+
+#notes
+{
+ style: MainB;
+};
+
+subtitle : notes
+{
+ time {start: 00:00:00.000; stop: 00:00:05.000;};
+
+ @ {
+ Subtitler filter for VirtualDub\n
+ Demonstration script\n\n
+ Avery Lee <phaeron@virtualdub.org>
+ };
+};
+
+subtitle : notes
+{
+ time {start: 00:00:05.000; stop: 00:00:10.000;};
+
+ @ {
+ Introduction\n\n
+ This script will demonstrate some of the simpler features of the subtitler. You will want to
+ refer to the [{font {face: "Courier New"; weight: "bold";};}] {readme.txt} file for a more
+ complete reference.
+ };
+};
+
+subtitle : notes
+{
+ time {start: 00:00:10.000; stop: 00:00:21.000;};
+
+ @ {
+ Dialogue lines\n\n
+ Scripts are based on dialogue lines. Each dialogue line has a start time, an end time, and text
+ to display in between.
+ };
+};
+
+/*
+Dialogue: Marked=0,0:00:10.00,0:00:11.00,Timer,,0000,0000,0000,!Effect,0:00
+Dialogue: Marked=0,0:00:11.00,0:00:12.00,Timer,,0000,0000,0000,!Effect,0:01
+Dialogue: Marked=0,0:00:12.00,0:00:13.00,Timer,,0000,0000,0000,!Effect,0:02
+Dialogue: Marked=0,0:00:13.00,0:00:14.00,Timer,,0000,0000,0000,!Effect,0:03
+Dialogue: Marked=0,0:00:14.00,0:00:15.00,Timer,,0000,0000,0000,!Effect,0:04
+Dialogue: Marked=0,0:00:15.00,0:00:16.00,Timer,,0000,0000,0000,!Effect,0:05
+Dialogue: Marked=0,0:00:16.00,0:00:17.00,Timer,,0000,0000,0000,!Effect,0:06
+Dialogue: Marked=0,0:00:17.00,0:00:18.00,Timer,,0000,0000,0000,!Effect,0:07
+Dialogue: Marked=0,0:00:18.00,0:00:19.00,Timer,,0000,0000,0000,!Effect,0:08
+Dialogue: Marked=0,0:00:19.00,0:00:20.00,Timer,,0000,0000,0000,!Effect,0:09
+Dialogue: Marked=0,0:00:20.00,0:00:21.00,Timer,,0000,0000,0000,!Effect,0:10
+*/
+
+#clocktest
+{
+ time {start: +0s; stop: +1s;};
+ style: Timer;
+};
+
+subtitle : clocktest {@ {0:00}; time.start: 00:00:10.000;};
+subtitle : clocktest {@ {0:01};};
+subtitle : clocktest {@ {0:02};};
+subtitle : clocktest {@ {0:03};};
+subtitle : clocktest {@ {0:04};};
+subtitle : clocktest {@ {0:05};};
+subtitle : clocktest {@ {0:06};};
+subtitle : clocktest {@ {0:07};};
+subtitle : clocktest {@ {0:08};};
+subtitle : clocktest {@ {0:09};};
+subtitle : clocktest {@ {0:10};};
+
+/*
+Dialogue: Marked=0,0:00:12.00,0:00:15.00,MainT,,0000,0000,0000,!Effect,{\c&H80FF00&}Hello.
+Dialogue: Marked=0,0:00:16.00,0:00:19.00,MainT,,0000,0000,0000,!Effect,{\c&HE0E0E0&}Oh, hi.
+Dialogue: Marked=0,0:00:10.00,0:00:12.00,MainT,,0000,0000,0080,!Effect,{\c&H407F00&}[02:00-05:00] Hello.
+Dialogue: Marked=0,0:00:12.00,0:00:15.00,MainT,,0000,0000,0080,!Effect,{\c&H80FF00&}[02:00-05:00] Hello.
+Dialogue: Marked=0,0:00:15.00,0:00:21.00,MainT,,0000,0000,0080,!Effect,{\c&H407F00&}[02:00-05:00] Hello.
+Dialogue: Marked=0,0:00:10.00,0:00:16.00,MainT,,0000,0000,0080,!Effect,{\c&H707070&}[06:00-09:00] Oh, hi.
+Dialogue: Marked=0,0:00:16.00,0:00:19.00,MainT,,0000,0000,0080,!Effect,{\c&HE0E0E0&}[06:00-09:00] Oh, hi.
+Dialogue: Marked=0,0:00:19.00,0:00:21.00,MainT,,0000,0000,0080,!Effect,{\c&H707070&}[06:00-09:00] Oh, hi.
+*/
+
+subtitle
+{
+ time {start: 00:00:12.000; stop: 00:00:15.000;};
+ style: MainT {font.color {r: 0x00; g: 0xff; b: 0x80;};};
+ @ {Hello.};
+};
+
+subtitle
+{
+ time {start: 00:00:16.000; stop: 00:00:19.000;};
+ style: MainT {font.color {r: 0xe0; g: 0xe0; b: 0xe0;};};
+ @ {Oh, hi.};
+};
+
+#dialogtest
+{
+ style: MainT {placement.margin.t: 80;};
+ time.start: +0s;
+};
+
+#dialogtest1 : dialogtest
+{
+ @ {\[02:00-05:00\] Hello.};
+};
+
+#dialogtest2 : dialogtest
+{
+ @ {\[06:00-09:00\] Oh, hi.};
+};
+
+subtitle : dialogtest1
+{
+ time.start: 00:00:10.000;
+ time.stop: +2s;
+ style.font.color {r: 0x00; g: 0x7f; b: 0x40;};
+};
+
+subtitle : dialogtest1
+{
+ time.stop: +3s;
+ style.font.color {r: 0x00; g: 0xff; b: 0x80;};
+};
+
+subtitle : dialogtest1
+{
+ time.stop: +6s;
+ style.font.color {r: 0x00; g: 0x7f; b: 0x40;};
+};
+
+subtitle : dialogtest2
+{
+ time.start: 00:00:10.000;
+ time.stop: +6s;
+ style.font.color {r: 0x70; g: 0x70; b: 0x70;};
+};
+
+subtitle : dialogtest2
+{
+ time.stop: +3s;
+ style.font.color {r: 0xe0; g: 0xe0; b: 0xe0;};
+};
+
+subtitle : dialogtest2
+{
+ time.stop: +2s;
+ style.font.color {r: 0x70; g: 0x70; b: 0x70;};
+};
+
+/*
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0055,0055,0000,!Effect,{\q1\a10}Alignment\N\NDialogue lines may be placed in any of three horizontal placements and any of three vertical placements.
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0008,0008,0008,!Effect,{\a1\c&HE0E0E0&}Bottom\Nleft
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0008,0008,0008,!Effect,{\a2\c&HE0E0E0&}Bottom\Ncenter
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0008,0008,0008,!Effect,{\a3\c&HE0E0E0&}Bottom\Nright
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0008,0008,0008,!Effect,{\a5\c&HE0E0E0&}Top\Nleft
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0008,0008,0008,!Effect,{\a6\c&HE0E0E0&}Top\Ncenter
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0008,0008,0008,!Effect,{\a7\c&HE0E0E0&}Top\Nright
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0008,0008,0008,!Effect,{\a9\c&HE0E0E0&}Middle\Nleft
+Dialogue: Marked=0,0:00:23.00,0:00:33.00,MainB,,0008,0008,0008,!Effect,{\a11\c&HE0E0E0&}Middle\Nright
+*/
+
+#aligntest : notes
+{
+ time {start: 00:00:23.000; stop: 00:00:33.000;};
+ style {placement.margin {t: 8; r: 8; b: 8; l: 8;};};
+};
+
+#aligntestside : aligntest
+{
+ style.font.color {r: 0xe0; g: 0xe0; b: 0xe0;};
+};
+
+subtitle : aligntest
+{
+ style.placement {align: middlecenter; margin {r: 55; l: 55;};};
+
+ @ {
+ Alignment\n\n
+ Dialogue lines may be placed in any of three horizontal placements and any of three vertical placements.
+ };
+};
+
+subtitle : aligntestside {style.placement.align: bottomleft; @ {Bottom\nleft};};
+subtitle : aligntestside {style.placement.align: bottomcenter; @ {Bottom\ncenter};};
+subtitle : aligntestside {style.placement.align: bottomright; @ {Bottom\nright};};
+subtitle : aligntestside {style.placement.align: topleft; @ {Top\nleft};};
+subtitle : aligntestside {style.placement.align: topcenter; @ {Top\ncenter};};
+subtitle : aligntestside {style.placement.align: topright; @ {Top\nright};};
+subtitle : aligntestside {style.placement.align: middleleft; @ {Middle\nleft};};
+subtitle : aligntestside {style.placement.align: middleright; @ {Middle\nright};};
+
+/*
+Dialogue: Marked=0,0:00:35.00,0:00:45.00,MainB,,0000,0000,0000,!Effect,{\q1}Text styles\N\NText may be varied in font, size, color, and style.
+Dialogue: Marked=0,0:00:35.00,0:00:45.00,MainT,,0000,0000,0000,!Effect,{\q1}You can have your text in {\c&H4060FF&}red{\r}, {\c&H60FF40&}green{\r}, and {\c&HFF6040&}blue{\r}; {\fnCourier New}Courier{\r}; {\fs16}16 point{\r} or {\fs32}32 point{\r}; {\b1}bold{\r}, {\i1}italic{\r}, or even {\b1\i1}bold italic{\r}.
+*/
+
+#fonttest
+{
+ time {start: 00:00:35.000; stop: 00:00:45.000;};
+};
+
+subtitle : fonttest notes
+{
+ @ {
+ Text styles\n\n
+ Text may be varied in font, size, color, and style.
+ };
+};
+
+subtitle : fonttest
+{
+ style: MainT;
+
+ @ {
+ You can have your text in
+ [{font.color {r: 0xff; g: 0x60; b: 0x40;};}] {red},
+ [{font.color {r: 0x40; g: 0xff; b: 0x60;};}] {green}, and
+ [{font.color {r: 0x40; g: 0x60; b: 0xff;};}] {blue};
+ [{font.face: "Courier New";}] {Courier};
+ [{font.size: 16;}] {16 point} or
+ [{font.size: 32;}] {32 point};
+ [{font.weight: "bold";}] {bold},
+ [{font.italic: "true";}] {italic},
+ or even
+ [{font {weight: "bold"; italic: "true";};}] {bold italic}.
+ };
+};
+
+/*
+Dialogue: Marked=0,0:00:47.00,0:00:57.00,MainB,,0000,0000,0000,!Effect,{\q1}Wrapping styles\N\NThree wrapping modes are supported: manual only, automatic wrapping, and smart wrapping. Smart wrapping is automatic wrapping, but with the lines broken as evenly as possible.
+Dialogue: Marked=0,0:00:47.00,0:00:57.00,MainT,,0000,0000,0000,!Effect,{\q2\c&HE0FF80&}This line uses manual wrapping and won't\nbe wrapped except where explicitly broken.
+Dialogue: Marked=0,0:00:47.00,0:00:57.00,MainT,,0000,0000,0045,!Effect,{\q1\c&H80E0FF&}This line uses automatic wrapping and is broken automatically.
+Dialogue: Marked=0,0:00:47.00,0:00:57.00,MainT,,0000,0000,0090,!Effect,{\q0\a6\c&HFFE080&}This line uses smart wrapping and is broken with even lines. It's good for centered text.
+*/
+
+#wraptest
+{
+ time {start: 00:00:47.000; stop: 00:00:57.000;};
+};
+
+subtitle : wraptest notes
+{
+ @ {
+ Wrapping styles\n\n
+ Three wrapping modes are supported: manual only, automatic wrapping, and smart wrapping.
+ Smart wrapping is automatic wrapping, but with the lines broken as evenly as possible.
+ };
+};
+
+subtitle : wraptest
+{
+ style: MainT {font.color {r: 0x80; g: 0xff; b: 0xe0;};};
+ wrap: "manual";
+ @ {This line uses manual wrapping and won't\nbe wrapped except where explicitly broken.};
+};
+
+subtitle : wraptest
+{
+ style: MainT {font.color {r: 0xff; g: 0xe0; b: 0x80;}; placement.margin.t: 45;};
+ wrap: "normal";
+ @ {This line uses automatic wrapping and is broken automatically.};
+};
+
+subtitle : wraptest
+{
+ style: MainT {font.color {r: 0x80; g: 0xe0; b: 0xff;}; placement.margin.t: 90; placement.align: topcenter; };
+ wrap: "even";
+ @ {This line uses smart wrapping and is broken with even lines. It's good for centered text.};
+};
+
+/*
+Dialogue: Marked=0,0:00:59.00,0:01:24.00,MainB,,0000,0000,0000,!Effect,{\q1}Collisions\N\NIf two dialogue items attempt to display at the same time and place, the second one is pushed clear of the first. However, text stays in place after it is resolved, even if the collider disappears.
+Dialogue: Marked=0,0:01:02.00,0:01:07.00,MainT,,0000,0000,0000,!Effect,{\q1\c&HE0E0E0&}Man, I gotta get out of here!
+Dialogue: Marked=0,0:01:07.00,0:01:17.00,MainT,,0000,0000,0000,!Effect,{\q1\c&HE0E0E0&}Which way should I go? I'm not going back to school....
+Dialogue: Marked=0,0:01:10.00,0:01:21.00,MainT,,0000,0000,0000,!Effect,{\q1\a7\c&H50D0FF&}Ranma, you're in the way! Move!
+Dialogue: Marked=0,0:01:15.00,0:01:21.30,MainT,,0000,0000,0000,!Effect,{\q1\c&HE0E0E0&}Why would I move for an uncute tomboy like you?
+Dialogue: Marked=0,0:01:21.00,0:01:25.00,MainT,,0000,0000,0000,!Effect,{\q1\a7\c&H50D0FF&}Ranma no baka!!!
+Dialogue: Marked=0,0:01:21.50,0:01:24.00,MainT,,0000,0000,0000,!Effect,{\q1\c&HE0E0E0&}*wham*
+*/
+
+subtitle : notes
+{
+ time {start: 00:00:59.000; stop: 00:01:24.000;};
+
+ @ {
+ Collisions\n\n
+ If two dialogue items attempt to display at the same time and place, the second one is
+ pushed clear of the first. However, text stays in place after it is resolved, even if
+ the collider disappears.
+ };
+};
+
+#actor1
+{
+ style: MainT {font.color {r: 0xe0; g: 0xe0; b: 0xe0;};};
+};
+
+#actor2
+{
+ style: MainT {font.color {r: 0xff; g: 0xd0; b: 0x50;}; placement.align: topright;};
+};
+
+subtitle : actor1
+{
+ time {start: 00:01:02.000; stop: 00:01:07.000;};
+ @ {Man, I gotta get out of here!};
+};
+
+subtitle : actor1
+{
+ time {start: 00:01:07.000; stop: 00:01:17.000;};
+ @ {Which way should I go? I'm not going back to school....};
+};
+
+subtitle : actor2
+{
+ time {start: 00:01:10.000; stop: 00:01:21.000;};
+ @ {Ranma, you're in the way! Move!};
+};
+
+subtitle : actor1
+{
+ time {start: 00:01:15.000; stop: 00:01:21.300;};
+ @ {Why would I move for an uncute tomboy like you?};
+};
+
+subtitle : actor2
+{
+ time {start: 00:01:21.000; stop: 00:01:25.000;};
+ @ {Ranma no baka!!!};
+};
+
+subtitle : actor1
+{
+ time {start: 00:01:21.500; stop: 00:01:24.000;};
+ @ {*wham*};
+};
+
+/*
+Dialogue: Marked=0,0:01:26.00,0:01:36.00,MainB,,0000,0000,0000,!Effect,{\q1}Shadows and borders\N\NYou can vary the depth of the translucent shadow as well as the thickness and color of the border.
+Dialogue: Marked=0,0:01:26.00,0:01:36.00,B0S0K,,0000,0000,0000,!Effect,Border=0, shadow=0
+Dialogue: Marked=0,0:01:26.00,0:01:36.00,B2S2R,,0000,0000,0030,!Effect,Border=2, shadow=2 (red)
+Dialogue: Marked=0,0:01:26.00,0:01:36.00,B2S6G,,0000,0000,0060,!Effect,Border=2, shadow=6 (green)
+Dialogue: Marked=0,0:01:26.00,0:01:36.00,B4S4B,,0000,0000,0090,!Effect,Border=4, shadow=4 (blue)
+*/
+
+#bordershadowtest
+{
+ time {start: 00:01:26.000; stop: 00:01:36.000;};
+};
+
+subtitle : bordershadowtest notes
+{
+ @ {
+ Shadows and borders\n\n
+ You can vary the depth of the translucent shadow as well as the thickness and color of the border.
+ };
+};
+
+subtitle : bordershadowtest
+{
+ style: B0S0K;
+ @ {Border=0, shadow=0};
+};
+
+subtitle : bordershadowtest
+{
+ style: B2S2R {placement.margin.t: 30;};
+ @ {Border=2, shadow=2 (red)};
+};
+
+subtitle : bordershadowtest
+{
+ style: B2S6G {placement.margin.t: 60;};
+ @ {Border=2, shadow=6 (green)};
+};
+
+subtitle : bordershadowtest
+{
+ style: B4S4B {placement.margin.t: 90;};
+ @ {Border=4, shadow=4 (blue)};
+};
+
+/*
+Dialogue: Marked=0,0:01:38.00,0:01:48.00,MainB,,0000,0000,0000,!Effect,{\q1}Karaoke\N\NLetting people sing to music is dangerous. But if they're going to do it anyway, they might as well have some help. Both snap and smooth styles are supported.
+Dialogue: Marked=0,0:01:40.00,0:01:45.00,Karaoke,,0000,0000,0000,Karaoke,{\fs18\k100}one {\k100}two {\k50}three-{\k50}and {\k100}four
+Dialogue: Marked=0,0:01:40.00,0:01:45.00,Karaoke,,0000,0000,0030,Karaoke,{\fs18\K100}one {\K100}two {\K50}three-{\K50}and {\K100}four
+*/
+
+subtitle : notes
+{
+ time {start: 00:01:38.000; stop: 00:01:48.000;};
+
+ @ {
+ Karaoke\n\n
+ Letting people sing to music is dangerous. But if they're going to do it anyway,
+ they might as well have some help. Both snap and smooth styles are supported.
+ };
+};
+
+#karaoketest
+{
+ time {start: 00:01:40.000; stop: 00:01:45.000;};
+ style: Karaoke {font.size: 18;};
+};
+
+#hlcolor {r: 0x40; g: 0x40; b: 0xff;};
+#hleffect1 {time {scale: 0.01; start: +0s;}; transition: "start"; font.color: hlcolor;};
+#hleffect2 {time {scale: 0.01; start: +0s;}; fill.width: 1;};
+
+subtitle : karaoketest
+{
+ @ {
+ [hleffect1 {time.stop: +100;}] {one}
+ [hleffect1 {time.stop: +100;}] {two}
+ [hleffect1 {time.stop: +50;}] {three-}
+ [hleffect1 {time.stop: +50;}] {and}
+ [hleffect1 {time.stop: +100;}] {four}
+ };
+};
+
+subtitle : karaoketest
+{
+ style {placement.margin.t: 30; fill.color: hlcolor;};
+
+ @ {
+ [hleffect2 {time.stop: +100;}] {one}
+ [hleffect2 {time.stop: +100;}] {two}
+ [hleffect2 {time.stop: +50;}] {three-}
+ [hleffect2 {time.stop: +50;}] {and}
+ [hleffect2 {time.stop: +100;}] {four}
+ };
+};
+
+/*
+Dialogue: Marked=0,0:01:50.00,0:02:00.00,MainB,,0000,0000,0000,!Effect,{\q1}DBCS (double-byte character set) support\N\NIf you have the appropriate language support installed, you can display far-east text in your subtitles. (By the way, I don't really know Japanese, so I'm kind of winged it above with the JWPce dictionary.)
+Dialogue: Marked=0,0:01:50.00,0:02:00.00,ShiftJIS,,0000,0000,0000,!Effect,{\q1}{\fe0}[Keitarou]{\fe128} ...
+Dialogue: Marked=0,0:01:52.00,0:02:00.00,ShiftJIS,,0000,0000,0000,!Effect,{\a7\q1\c&HFFFF00&\fe0}[Naru]{\fe128} ...
+*/
+
+subtitle : notes
+{
+ time {start: 00:01:50.000; stop: 00:02:00.000;};
+
+ @ {
+ DBCS (double-byte character set) support\n\n
+ If you have the appropriate language support installed, you can display far-east text
+ in your subtitles. (By the way, I don't really know Japanese, so I'm kind of winged it
+ above with the JWPce dictionary.)
+ };
+};
+
+subtitle
+{
+ time {start: 00:01:50.000; stop: 00:02:00.000;};
+ style: ShiftJIS;
+ @ {\[Keitarou\]ã ã€‚。。大丈夫?};
+};
+
+subtitle
+{
+ time {start: 00:01:52.000; stop: 00:02:00.000;};
+ style: ShiftJIS {font.color: cyan; placement.align: topright;};
+ @ {\[Naru\]何よï¼};
+};
+
+/*
+Dialogue: Marked=0,0:02:02.00,0:02:17.00,MainB,,0000,0000,0000,!Effect,{\q1}Motion effects\N\NYou can scroll text in like a banner, or up the screen.
+Dialogue: Marked=0,0:02:02.00,0:02:17.00,MainT,,0000,0000,0000,Banner;30,Text is fun. We like scrolling text.
+Dialogue: Marked=0,0:02:02.00,0:02:17.00,MainT,,0000,0000,0000,Scroll Up;40;50;120,{\q1}You can scroll lots of text up this way. Personally, I find it a little annoying to have a lot of text coming up very slowly on screen, but it's a useful technique to have for credits and other long lists in tabular form.
+*/
+
+#effecttest
+{
+ time {start: 00:02:02.000; stop: 00:02:17.000;};
+ style.placement.clip: "frame";
+};
+
+subtitle : effecttest notes
+{
+ @ {
+ Motion effects\n\n
+ You can scroll text in like a banner, or up the screen.
+ };
+};
+
+subtitle : effecttest
+{
+ wrap: "manual";
+ style: MainT {placement {margin {l: "right"; r: "left";}; align.h: "left";};};
+ #hscroll {time {start: "start"; stop: "stop";}; placement.align.h: "right";};
+
+ @ {
+ [hscroll]
+ Text is fun. We like scrolling text.
+ };
+};
+
+subtitle : effecttest
+{
+ style: MainT {placement {margin {t: 120; b: 200;}; clip {t: 40; b: 120;}; align.v: "top";};};
+ #vscroll {time {start: "start"; stop: "stop";}; placement.align.v: "bottom";};
+
+ @ {
+ [vscroll]
+ You can scroll lots of text up this way. Personally, I find it a little annoying
+ to have a lot of text coming up very slowly on screen, but it's a useful technique
+ to have for credits and other long lists in tabular form.
+ };
+};
+
+/*
+Dialogue: Marked=0,0:02:19.00,0:02:50.00,MainB,,0000,0000,0000,!Effect,{\q1}Conclusion\N\NThat concludes the demo. I hope you've enjoyed the brief glimpse I've given you here, and encourage you to read the {\fnCourier New\b1}readme.txt{\r} file for more information on the effects you've seen here. Also, if you're bored, try loading this script into Kotus' Sub Station Alpha V4 program. Not all effects will render correctly, but most of the file will. Have fun, and keep on subtitling!\N\N{\fnCourier New}--{\r}Avery Lee <phaeron@virtualdub.org>\N{\fnCourier New}\h\h{\r}Author of VirtualDub and the 'subtitler' filter
+*/
+
+subtitle : notes
+{
+ time {start: 00:02:19.000; stop: 00:02:50.000;};
+
+ #monospace {font.face: "Courier New";};
+ #monospacebold : monospace {font.weight: "bold";};
+
+ @ {
+ Conclusion\n\n
+ That concludes the demo. I hope you've enjoyed the brief glimpse I've given you here,
+ and encourage you to read the [monospacebold] {readme.txt} file for more information
+ on the effects you've seen here. Also, if you're bored, try loading this script into
+ Kotus' Sub Station Alpha V4 program. Not all effects will render correctly, but most
+ of the file will. Have fun, and keep on subtitling!\n\n
+ [monospace]{--}Avery Lee <phaeron@virtualdub.org>\n
+ [monospace]{\h\h}Author of VirtualDub and the 'subtitler' filter
+ };
+};
diff --git a/src/Subtitles/libssf/docs/ssf-specs.txt b/src/Subtitles/libssf/docs/ssf-specs.txt
new file mode 100644
index 000000000..95d639e19
--- /dev/null
+++ b/src/Subtitles/libssf/docs/ssf-specs.txt
@@ -0,0 +1,613 @@
+Structured Subtitle Format 1.0 (boring name, need a better one)
+------------------------------
+
+The encoding must be utf-8/16le/16be with the BOM at the beginning.
+
+Parsing is prefered to be stream oriented, which means:
+- new-line characters in text do not differ from the rest of the white-space characters
+- forward references are not allowed
+
+Comments
+--------
+
+// a comment, ends at the end of the line
+/* this is a comment, too */
+
+Syntax
+------
+
+Fortunatelly, there is only one general way to define something. The elements are broken
+into lines for better understanding, but they can be compressed into one single line as well,
+whichever suits your preference. The term "type" could be read as "attribute" or "member of
+a structure" too.
+
+[!]
+[type[.type[..]]]
+[#name]
+[: | =]
+[refs | quoted string | num | bool | raw]
+;
+
+As you can see nearly everything is optional, even the terminating semi-colon is not required
+when a closing bracket ends the definition anyway. However, either type or #name must be given,
+otherwise there would be no meaning of the expression.
+
+"!":
+- gives the definition high priority, normal priority types of the same kind cannot override it:
+ #a {!t: 123;};
+ #b {t: 234;};
+ #c a b; // t of c equals to 123
+ - works on references too:
+ !#a {t: 123;};
+ #b {t: 234;};
+ #ab a b;
+ #c ab; // t of c still equals to 123
+
+"type" and "name":
+- alphanumeric or underscore characters only, without spaces
+- cannot start with a number
+- case-sensitive
+
+"type":
+- type is optional but can be inherited through referencing other names which happen to have a type already
+ color#c1 {a: 0x80;};
+ #c2: c1; // c2 also has the type of color now
+ #c3: c2; // c3 too
+- if there is a type already additional references cannot change it
+- when mixing different types (not recommended) the first one decides
+- there is one special type which have an important but a limited use: @ (see 'subtitle' for an example usage)
+ - it is not parsed for references or nested definitions
+ - the content of the following block {...} is saved as-is for the application
+ - cannot be global or named and therefore referenced
+ - { and } have to be escaped with \ inside the block (other block specific characters may as well)
+- type.type.type.... is equal to writing: type {type {type {...};};};
+
+"name":
+- every name is globally visible
+- redefining a name is forbidden, unless it was predefined by the application
+- using the type as the name (type#type) can change the type's default values
+ - however, the scope is important:
+
+ subtitle#subtitle {style.font.size: 20;};
+ style#style {font.size: 30;};
+ style#s1 {font.face: "Arial";};
+ style#s2 : s1 {font.color: red;};
+ subtitle#a {style: s2 {font.weight: "normal";};};
+
+ Here font.size equals to 20, because it inherits from subtitle#subtitle.style instead of the
+ global style#style, additionally it gets the properties of s2-s1 and the inline def. If it also
+ inherited properties from style#style through s2-s1 indirectly, there would be two default base
+ definitions and the order of overriding eachother would not be clear.
+
+ subtitle#a.style
+ <- subtitle#subtitle.style
+ <- s2 <- s1 <-NOT- style#style
+ <- {font.weight: "normal";}
+
+"refs":
+- combination of any names or nested definitions separated by spaces
+- forward references are not allowed
+- referencing a value type directly is not allowed: (TODO: this may change)
+ OK:
+ color#c1: {a: 12;};
+ color#c2: c1;
+ BAD:
+ #twelve: 12;
+ color#c2: {a: twelve;};
+- 'name' must have been defined by a parent node
+ OK:
+ #c1: {a: 12;};
+ style#s1 {color: c1;};
+ style#s2 {color: c1;};
+ BAD:
+ style#s1 {color#c1: {a: 12;};};
+ style#s2 {color: c1;}; // c1 cannot be accessed here
+
+"quoted string" or 'quoted string':
+- \ escapes characters, including " and '
+- cannot contain any new-line characters
+
+"num":
+- decimal, hexadecimal (prefixed with: 0x), float [+ unit (optional, see 'time' below for an example)]
+- there are numbers with restricted range or special formatting:
+ - degrees: <num> mod 360
+ - percent: must be between 0 and 1
+ - time: [+] [<num>:[<num>:[<num>.]]]<num> | <num>h | <num>m | <num>s | <num>ms
+
+"bool":
+- "true" | "false" | "on" | "off" | "yes" | "no" | 1 | 0
+- unrecognizable values will result in unpredictable behaviour, since there can't be a default fallback value
+
+Recognized types and attributes
+-------------------------------
+
+file
+{
+ format: <string>; // identifies the file format ("ssf")
+ version: <num>; // file format version (1)
+ language: <string>; // iso6392
+ title: <string>;
+ year: <num>;
+ author: <string>;
+};
+
+color
+{
+ // a, r, g, b: 0 - 255 or 0x00 - 0xff
+
+ a: <num>;
+ r: <num>;
+ g: <num>;
+ b: <num>;
+};
+
+point
+{
+ x: <num>;
+ y: <num>;
+};
+
+size
+{
+ cx: <num>;
+ cy: <num>;
+};
+
+rect
+{
+ t: <num>;
+ r: <num>;
+ b: <num>;
+ l: <num>;
+};
+
+align
+{
+ // when given in percent, 0 means top/left, 1 bottom/right, 0.5 middle/center
+
+ v: ["top" | "middle" | "bottom" | <percent>];
+ h: ["left" | "center" | "right" | <percent>];
+};
+
+angle
+{
+ x: <degrees>;
+ y: <degrees>;
+ z: <degrees>;
+};
+
+frame
+{
+ reference: ["video" | "window"];
+ resolution: <size>;
+};
+
+direction
+{
+ primary: ["right" | "left" | "down" | "up"];
+ secondary: ["right" | "left" | "down" | "up"]; // must be perpendicular to primary
+};
+
+placement
+{
+ clip: ["none" | "frame" | <rect>]; // anything drawn outside this rectangle is clipped, negative or larger than 'resolution' values are not allowed for rect
+ margin: <rect>; // "top" "right" "bottom" "left" are also valid values for the rect members, they refer to the values of the "frame" rect (0, 0, frame.resolution.cx, frame.resolution.cy)
+ align: <align>;
+ pos: ["auto" | <point>]; // absolute values, pos.x or pos.y can be animated only when both the source and destination style defined it
+ offset: <point>; // relative to pos, unlike pos this can be applied to fragments of the text as an override
+ angle: <angle>; // origin of rotation is the alignment point, unless it is overriden
+ org: ["auto" | <point>]; // override for the origin
+ path: <string>; // a series of x y coord pairs (at least two points)
+};
+
+font
+{
+ face: <string>;
+ size: <num>;
+ weight: ["normal" | "bold" | "thin" | <num>];
+ color: <color>;
+ underline: <bool>;
+ strikethrough: <bool>;
+ italic: <bool>;
+ spacing: <num>;
+ scale: <size>;
+ kerning: <bool>;
+};
+
+background
+{
+ color: <color>;
+ size: <num>;
+ type: ["outline" | "enlarge" | "box"];
+ blur: <num>; // number of passes
+
+ // "enlarge" can be computed faster, but because it follows the path of the original outline it is
+ // not rounded and size > 1 can make it look pretty bad if it intersects with itself.
+};
+
+shadow
+{
+ color: <color>;
+ depth: <num>;
+ angle: <degrees>;
+ blur: <num>; // number of passes
+};
+
+fill
+{
+ color: <color>;
+ width: <percent>;
+
+ // It cannot be applied to the same text multiple times as an override.
+ //
+ // #k1 {fill.width:1; time {start: +0; stop: +1s;}};
+ // #k2 {fill.width:1; time {start: 0; stop: 2s;}};
+ //
+ // OK:
+ // [k1] {Hello}
+ //
+ // BAD:
+ // [k1] {Wo[k2]{r}ld!}
+ //
+};
+
+time
+{
+ id: [<string> | <num>];
+ start: <time>; // inclusive
+ stop: <time>; // exclusive
+ scale: <num>; // if the time was set WITHOUT a unit, then start and stop are measured in [scale * seconds]
+};
+
+style
+{
+ linebreak: ["word" | "char" | "none"]; // ignored if subtitle.wrap: "manual"
+ placement: <placement>;
+ font: <font>;
+ background: <background>;
+ shadow: <shadow>;
+ fill: <fill>;
+};
+
+animation
+{
+ time: <time>; // before start: previous style, after stop: this style, animation is done between start and stop according to transition
+ transition: ["linear" | "start" | "stop" | <num>]; // <num> is the same as the acceleration parameter of advanced ssa (see the "ass-specs"), "start" or "stop" sets num to 0 and inf, "linear" sets it to 1.0
+ loop: <num>; // the number of times the effect should repeat, e.g. loop set to 2 makes the style turn from src to dst twice, between start => (start+stop)/2 and (start+stop)/2 => stop
+ direction: ["fw" | "bw" | "fwbw" | "bwfw"]; // "bwfw" and "fwbw" makes the value of loop multiplied by two internally
+};
+
+subtitle
+{
+ frame: <frame>;
+ direction: <direction>;
+ wrap: ["normal" | "even" | "manual"];
+ layer: <num>;
+ time: <time>;
+ style: <style>;
+ @: {... dialog lines ...};
+
+ To have a subtitle displayed the minimal set of fields required are:
+ - time.start
+ - time.stop
+ - @
+
+ About dialog lines
+ ------------------
+
+ All white-space will be compressed into one space character.
+
+ Special characters can be enforced:
+ - new-line: \n
+ - non-breaking space: \h
+
+ Empty space will be completly removed at these places:
+ - before and after outermost text block brackets:
+ @ { --> here <-- Hello World! --> here <-- }
+ - between the inner closing and opening brackets of overrides:
+ [s1] --> here <-- {Hello World!}
+ - around forced new-lines:
+ Hello --> here <-- \n --> here <-- World!
+
+ When neighboring spaces have different styles, the style of the trailing space is used:
+ #u {font.underline: "true"};
+ #s {font.strikethrough: "true"};
+ [u] { Hello }
+ [s] { World! }
+ =>
+ Hello_World!-
+ ______-------
+
+ These special characters have to be escaped with a backslash:
+ { } [ ] \
+
+ Style overrides
+ ---------------
+
+ [refs] {... more dialog text ...}
+
+ "name" as in type#name ... is unimportant and ignored.
+
+ If there is a text block right after an override, the new style will be used only inside
+ that block.
+
+ Style and time offsets are restored at the end of text blocks.
+
+ Examples:
+
+ [s1] {s1 applies only to this block.}
+
+ [s2] s2 applies till the end of the container block.
+
+ [s1 s2 {color: red;} s3] {Multiple styles are valid just like in a normal ref list}
+
+ {
+ Nesting text blocks. Some may have overrides, others may not.
+
+ [s1]
+ {
+ Every text block will be trimmed and white-space compressed,
+ so we can use the space freely to make it a bit more readable.
+
+ {Yet another nested block}
+ }
+ }
+
+ Animation:
+
+ The override is style + animation mixed.
+
+ [{time.stop: +00:00:01.000; transition: "linear"; font.size: 20;}]
+ {
+ Here the size of the font gradually becomes 20 after one second.
+ }
+
+ [{font.color: white;}]
+ {
+ [{time.stop: +00:00:10.000; font.color: black;}]
+ {Text turns from white to black in the first 10 seconds}
+
+ [a1]
+ {This one does whatever a1 tells to do}
+ }
+
+ [{time.start: "start"; time.stop: "stop"; font.size: 20}]
+ {
+ This is a way to refer to the subtitle's own start and stop values.
+
+ You can predefine it for easier use:
+
+ #a1 {time.start: "start"; time.stop: "stop";};
+ [a1 {font.size: 20}] {...}
+ }
+
+ Karaoke effect using animation:
+
+ [{background.size: 0;}]
+ {
+ [{time.start: 0s; time.stop: +1s; background.size: 10;}]
+ {Ka}
+ [{time.start: 1s; time.stop: +1s; background.size: 10;}]
+ {ra}
+ [{time.start: 2s; time.stop: +1s; background.size: 10;}]
+ {o}
+ [{time.start: 3s; time.stop: +1s; background.size: 10;}]
+ {ke!}
+ }
+
+ To simplify the above, we can predefine the common parameters:
+
+ #s1 {background.size: 0;};
+ #a1 {time.start: +0s; time.stop: +1s; background.size: 10;};
+
+ [s1]
+ {
+ [a1]{Ka}[a1]{ra}[a1]{o}[a1 {font.color: red;}]{ke!}
+
+ // That last piece has even turned into a red in the end :)
+
+ // And to make sure s1 has not been red already, we could derive it
+ // from "whitefont" (defined somewhere else as: #whitefont {font.color: white;};)
+
+ [a1]{Ka}[a1]{ra}[a1]{o}[a1 whitefont {font.color: red;};}]{ke!}
+ }
+
+ When specifying multiple style references watch out for the start/stop values
+ of time. Since they override eachother you should only set them once.
+
+ #a2 {font.color.a: 0;};
+ #a3 {placement.angle.x: 360;};
+
+ [s1] [a1 a2 a3]{Ka}[a1 a2 a3]{ra}[a1 a2 a3]{o}[a1 a2 a3]{ke!}
+
+ If you want to be even lazier:
+
+ #a4: a1 a2 a3;
+
+ [s1] [a4]{Ka}[a4]{ra}[a4]{o}[a4]{ke!}
+
+ To avoid the times overriding eachother use either nested text blocks ...
+
+ #a2 {time.start: +0.5s; time.stop: +1s; font.color.a: 0;};
+ #a3 {time.start: +1s; time.stop: +1s; placement.angle.x: 360;};
+
+ [s1] [a1]{[a2]{[a3]Ka}}[a1]{[a2]{[a3]ra}}[a1]{[a2]{[a3]o}}[a1]{[a2]{[a3]ke}}
+
+ ... or a list of references were each has a different time.id ...
+
+ #a1 {time {id: 1; start: +0s; stop: +1s;}; background.size: 10;};
+ #a2 {time {id: 2; start: +0.5s; stop: +1s;}; font.color.a: 0;};
+ #a3 {time {id: 3; start: +1s; stop: +1s;}; placement.angle.x: 360;};
+
+ [s1] [a1,a2,a3]{Ka}[a1,a2,a3]{ra}[a1,a2,a3]{o}[a1,a2,a3]{ke!}
+
+ ... or just let it auto-number the ids, each timer id becomes the actual position
+ in the comma separated list (a1 id => 1, a2 id => 2, a3 id => 3).
+
+ #a1 {time {start: +0s; stop: +1s;}; background.size: 10;};
+ #a2 {time {start: +0.5s; stop: +1s;}; font.color.a: 0;};
+ #a3 {time {start: +1s; stop: +1s;}; placement.angle.x: 360;};
+
+ [s1] [a1,a2,a3]{Ka}[a1,a2,a3]{ra}[a1,a2,a3]{o}[a1,a2,a3]{ke!}
+
+ It is also possible to leave some of the ref list slots empty.
+
+ [s1] [a1,a2,a3]{Ka}[,a2,a3]{ra}[a1,,a3]{o}[a1,a2,]{ke!}
+
+ Text includes
+ -------------
+
+ Text blocks can also be defined outside subtitles and included later.
+
+ #hw { @ {[{font.italic: "true"}]Hello World!}; };
+ subtitle { @ {[hw]}; };
+
+ When mixing style overrides and text includes, the new style applies to the text too.
+
+ #hw { @ {Hello World!}; };
+ subtitle { @ {[hw {font.italic: "true"}]}; };
+
+ Multiple levels of recursion:
+
+ #dblspace { @ {[{font.scale.cx: 2;}] }; }; // note: there is still only one space character here because of white space compression
+ #hw { @ {Hello[dblspace]World!}; };
+ subtitle { @ {[hw]}; };
+};
+
+Defaults and predefined names
+-----------------------------
+
+These must be predefined by the application and always assumed to be there for ssf version 1.
+
+color#white {a: 255; r: 255; g: 255; b: 255;};
+color#black {a: 255; r: 0; g: 0; b: 0;};
+color#gray {a: 255; r: 128; g: 128; b: 128;};
+color#red {a: 255; r: 255; g: 0; b: 0;};
+color#green {a: 255; r: 0; g: 255; b: 0;};
+color#blue {a: 255; r: 0; g: 0; b: 255;};
+color#cyan {a: 255; r: 0; g: 255; b: 255;};
+color#yellow {a: 255; r: 255; g: 255; b: 0;};
+color#magenta {a: 255; r: 255; g: 0; b: 255;};
+
+align#topleft {v: "top"; h: "left";};
+align#topcenter {v: "top"; h: "center";};
+align#topright {v: "top"; h: "right";};
+align#middleleft {v: "middle"; h: "left";};
+align#middlecenter {v: "middle"; h: "center";};
+align#middleright {v: "middle"; h: "right";};
+align#bottomleft {v: "bottom"; h: "left";};
+align#bottomcenter {v: "bottom"; h: "center";};
+align#bottomright {v: "bottom"; h: "right";};
+
+time#time {scale: 1;};
+time#startstop {start: "start"; stop: "stop";};
+
+#b {font.weight: "bold"};
+#i {font.italic: "true"};
+#u {font.underline: "true"};
+#s {font.strikethrough: "true"};
+
+#nobr {linebreak: "none"};
+
+subtitle#subtitle
+{
+ frame
+ {
+ reference: "video";
+ resolution: {cx: 640; cy: 480;};
+ };
+
+ direction
+ {
+ primary: "right";
+ secondary: "down";
+ };
+
+ wrap: "normal";
+
+ layer: 0;
+
+ style
+ {
+ linebreak: "word";
+
+ placement
+ {
+ clip: "none";
+ margin: {t: 0; r: 0; b: 0; l: 0;};
+ align: bottomcenter;
+ pos: "auto";
+ offset: {x: 0; y: 0;};
+ angle: {x: 0; y: 0; z: 0;};
+ };
+
+ font
+ {
+ face: "Arial";
+ size: 20;
+ weight: "bold";
+ color: white;
+ underline: "false";
+ strikethrough: "false";
+ italic: "false";
+ spacing: 0;
+ scale: {cx: 1; cy: 1;};
+ kerning: "true";
+ };
+
+ background
+ {
+ color: black;
+ size: 2;
+ type: "outline";
+ };
+
+ shadow
+ {
+ color: black {a: 128;};
+ depth: 2;
+ angle: -45;
+ blur: 0;
+ };
+
+ fill
+ {
+ color: yellow;
+ width: 0;
+ };
+ };
+};
+
+Streaming
+---------
+
+Correct packetization is important when a subtitle file has to be embeded into a media
+file. Putting everything into the header would be trivial, but sending the whole track
+data ahead cannot be called streaming really, and it also makes editing impossible
+unless the application learns how to parse and resave our format.
+
+The recommended way of segmenting ssf into media samples:
+- Search top level definitions which do not satisfy the requirements of a displayable
+subtitle (one of start/stop/@ is absent) and save them into the file header of the media
+file as the initializer data for playback.
+- Multiplex the rest of the top level definitions as media samples, their timestamps
+shall be used for interleaving with other streams.
+
+Example:
+
+#mystyle {font.face: "Times New Roman";};
+subtitle#s1 {time.start: 2s;};
+subtitle#s2 : s1 {style: mystyle; time.stop: +1s; @ {2s -> 3s};};
+subtitle#s3 {style: mystyle; time.start: 5s; @ {5s -> 7s};};
+subtitle#s4 : s3 {time.stop: +2s;};
+
+File header:
+
+#mystyle {font.face: "Times New Roman";};
+subtitle#s1 {time.start: 2s;};
+subtitle#s3 {style: mystyle; time.start: 5s; @ {5s -> 7s};};
+
+Media samples:
+
+subtitle#s2 : s1 {style: mystyle; time.stop: +1s; @ {2s -> 3s};};
+subtitle#s4 : s3 {time.stop: +2s;};
diff --git a/src/Subtitles/libssf/libssf.vcproj b/src/Subtitles/libssf/libssf.vcproj
new file mode 100644
index 000000000..907332dc8
--- /dev/null
+++ b/src/Subtitles/libssf/libssf.vcproj
@@ -0,0 +1,491 @@
+<?xml version="1.0" encoding="windows-1250"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9,00"
+ Name="libssf"
+ ProjectGUID="{DD9D2D92-2241-408A-859E-B85D444B7E3C}"
+ RootNamespace="libssf"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="131072"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ <Platform
+ Name="x64"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\..\common.vsprops;..\..\debug.vsprops"
+ UseOfMFC="1"
+ CharacterSet="1"
+ WholeProgramOptimization="0"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalOptions="/MP"
+ AdditionalIncludeDirectories="..\..\include;..\..\filters\BaseClasses"
+ PreprocessorDefinitions="WIN32;_DEBUG;_LIB"
+ UsePrecompiledHeader="2"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Debug|x64"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\..\common.vsprops;..\..\debug.vsprops"
+ UseOfMFC="1"
+ CharacterSet="1"
+ WholeProgramOptimization="0"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ TargetEnvironment="3"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalOptions="/MP"
+ AdditionalIncludeDirectories="..\..\include;..\..\filters\BaseClasses"
+ PreprocessorDefinitions="_WIN64;_DEBUG;_LIB"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="1"
+ UsePrecompiledHeader="2"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\..\common.vsprops;..\..\release.vsprops"
+ UseOfMFC="1"
+ CharacterSet="1"
+ WholeProgramOptimization="0"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalOptions="/MP"
+ AdditionalIncludeDirectories="..\..\include;..\..\filters\BaseClasses"
+ PreprocessorDefinitions="WIN32;NDEBUG;_LIB"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ AdditionalOptions="/IGNORE:4221"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|x64"
+ ConfigurationType="4"
+ InheritedPropertySheets="..\..\common.vsprops;..\..\release.vsprops"
+ UseOfMFC="1"
+ CharacterSet="1"
+ WholeProgramOptimization="0"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ TargetEnvironment="3"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalOptions="/MP"
+ AdditionalIncludeDirectories="..\..\include;..\..\filters\BaseClasses"
+ PreprocessorDefinitions="_WIN64;NDEBUG;_LIB"
+ EnableEnhancedInstructionSet="0"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ AdditionalOptions="/IGNORE:4221"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <File
+ RelativePath=".\Arabic.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\Array.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\Exception.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\File.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\FontWrapper.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\Glyph.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\GlyphPath.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\Node.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\NodeFactory.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\Rasterizer.cpp"
+ >
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AssemblerOutput="4"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|x64"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AssemblerOutput="4"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\Renderer.cpp"
+ >
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AssemblerOutput="4"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|x64"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AssemblerOutput="4"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\Split.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\stdafx.cpp"
+ >
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|x64"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|x64"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ UsePrecompiledHeader="1"
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\Stream.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\StringMap.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\Subtitle.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\SubtitleFile.cpp"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ <File
+ RelativePath=".\Arabic.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Array.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Exception.h"
+ >
+ </File>
+ <File
+ RelativePath=".\File.h"
+ >
+ </File>
+ <File
+ RelativePath=".\FontWrapper.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Glyph.h"
+ >
+ </File>
+ <File
+ RelativePath=".\GlyphPath.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Node.h"
+ >
+ </File>
+ <File
+ RelativePath=".\NodeFactory.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Rasterizer.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Renderer.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Split.h"
+ >
+ </File>
+ <File
+ RelativePath=".\stdafx.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Stream.h"
+ >
+ </File>
+ <File
+ RelativePath=".\StringMap.h"
+ >
+ </File>
+ <File
+ RelativePath=".\Subtitle.h"
+ >
+ </File>
+ <File
+ RelativePath=".\SubtitleFile.h"
+ >
+ </File>
+ </Filter>
+ <File
+ RelativePath=".\demo\demo.ssa"
+ >
+ </File>
+ <File
+ RelativePath=".\demo\demo.ssf"
+ >
+ </File>
+ <File
+ RelativePath=".\docs\ssf-specs.txt"
+ >
+ </File>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/src/Subtitles/libssf/stdafx.cpp b/src/Subtitles/libssf/stdafx.cpp
new file mode 100644
index 000000000..87a70aa14
--- /dev/null
+++ b/src/Subtitles/libssf/stdafx.cpp
@@ -0,0 +1,29 @@
+/*
+ * 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
+ *
+ */
+
+// stdafx.cpp : source file that includes just the standard includes
+// libssf.pch will be the pre-compiled header
+// stdafx.obj will contain the pre-compiled type information
+
+#include "stdafx.h"
+
+// TODO: reference any additional headers you need in STDAFX.H
+// and not in this file
diff --git a/src/Subtitles/libssf/stdafx.h b/src/Subtitles/libssf/stdafx.h
new file mode 100644
index 000000000..659ca7fb8
--- /dev/null
+++ b/src/Subtitles/libssf/stdafx.h
@@ -0,0 +1,69 @@
+/*
+ * 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
+ *
+ */
+
+// stdafx.h : include file for standard system include files,
+// or project specific include files that are used frequently, but
+// are changed infrequently
+//
+
+#pragma once
+#include "../../DSUtil/SharedInclude.h"
+
+// Modify the following defines if you have to target a platform prior to the ones specified below.
+// Refer to MSDN for the latest info on corresponding values for different platforms.
+#ifndef WINVER // Allow use of features specific to Windows XP or later.
+#define WINVER 0x0600
+#endif
+
+#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later.
+#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows.
+#endif
+
+#ifndef _WIN32_WINDOWS // Allow use of features specific to Windows 98 or later.
+#define _WIN32_WINDOWS 0x0410 // Change this to the appropriate value to target Windows Me or later.
+#endif
+
+#ifndef _WIN32_IE // Allow use of features specific to IE 6.0 or later.
+#define _WIN32_IE 0x0600 // Change this to the appropriate value to target other versions of IE.
+#endif
+
+#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
+#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit
+
+#ifndef VC_EXTRALEAN
+#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers
+#endif
+
+#include <afx.h>
+#include <afxwin.h> // MFC core and standard components
+#include <atlcoll.h> // MFC core and standard components
+
+// TODO: reference additional headers your program requires here
+
+#include <streams.h>
+#include "../../DSUtil/DSUtil.h"
+#include "../../dsutil/vd.h"
+
+#include <xmmintrin.h>
+#include <emmintrin.h>
+
+#define _USE_MATH_DEFINES
+#include <math.h>
diff --git a/src/Subtitles/stdafx.cpp b/src/Subtitles/stdafx.cpp
new file mode 100644
index 000000000..3dd05c6af
--- /dev/null
+++ b/src/Subtitles/stdafx.cpp
@@ -0,0 +1,29 @@
+/*
+ * 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
+ *
+ */
+
+// stdafx.cpp : source file that includes just the standard includes
+// subtitles.pch will be the pre-compiled header
+// stdafx.obj will contain the pre-compiled type information
+
+#include "stdafx.h"
+
+// TODO: reference any additional headers you need in STDAFX.H
+// and not in this file
diff --git a/src/Subtitles/stdafx.h b/src/Subtitles/stdafx.h
new file mode 100644
index 000000000..214f0c923
--- /dev/null
+++ b/src/Subtitles/stdafx.h
@@ -0,0 +1,44 @@
+/*
+ * 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
+ *
+ */
+
+// stdafx.h : include file for standard system include files,
+// or project specific include files that are used frequently, but
+// are changed infrequently
+//
+
+#pragma once
+#include "../DSUtil/SharedInclude.h"
+
+#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
+#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit
+#define WINVER 0x0600
+
+#ifndef VC_EXTRALEAN
+#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers
+#endif
+
+#include <afx.h>
+#include <afxwin.h> // MFC core and standard components
+
+// TODO: reference additional headers your program requires here
+
+#include <streams.h>
+#include "../DSUtil/DSUtil.h"