Aegisub/vsfilter/libssf/Subtitle.cpp

701 lines
18 KiB
C++

/*
* 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)
{
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;
}
}