forked from mia/Aegisub
e2f8e69612
Originally committed to SVN as r4581.
471 lines
15 KiB
C++
471 lines
15 KiB
C++
// Copyright (c) 2005, Rodrigo Braz Monteiro
|
|
// Copyright (c) 2010, Thomas Goyne <plorkyeran@aegisub.org>
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright notice,
|
|
// this list of conditions and the following disclaimer in the documentation
|
|
// and/or other materials provided with the distribution.
|
|
// * Neither the name of the Aegisub Group nor the names of its contributors
|
|
// may be used to endorse or promote products derived from this software
|
|
// without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
// Aegisub Project http://www.aegisub.org/
|
|
//
|
|
// $Id$
|
|
|
|
/// @file ass_override.cpp
|
|
/// @brief Parse and modify ASSA style overrides
|
|
/// @ingroup subs_storage
|
|
///
|
|
|
|
#include "config.h"
|
|
|
|
#ifndef AGI_PRE
|
|
#include <wx/log.h>
|
|
#include <wx/tokenzr.h>
|
|
#endif
|
|
|
|
#include <libaegisub/log.h>
|
|
|
|
#include "ass_dialogue.h"
|
|
#include "ass_override.h"
|
|
#include "utils.h"
|
|
|
|
AssOverrideParameter::AssOverrideParameter()
|
|
: classification(PARCLASS_NORMAL)
|
|
, omitted(false)
|
|
{
|
|
}
|
|
|
|
AssOverrideParameter::AssOverrideParameter(const AssOverrideParameter ¶m)
|
|
: VariableData(param)
|
|
, classification(param.classification)
|
|
, omitted(param.omitted)
|
|
{
|
|
}
|
|
|
|
void AssOverrideParameter::operator=(const AssOverrideParameter ¶m) {
|
|
DeleteValue();
|
|
new(this) AssOverrideParameter(param);
|
|
}
|
|
|
|
// From ass_dialogue.h
|
|
AssDialogueBlockOverride::~AssDialogueBlockOverride() {
|
|
delete_clear(Tags);
|
|
}
|
|
|
|
void AssDialogueBlockOverride::ParseTags() {
|
|
delete_clear(Tags);
|
|
|
|
wxStringTokenizer tkn(text, L"\\", wxTOKEN_STRTOK);
|
|
wxString curTag;
|
|
if (text.StartsWith(L"\\")) curTag = L"\\";
|
|
|
|
while (tkn.HasMoreTokens()) {
|
|
curTag += tkn.GetNextToken();
|
|
|
|
// Check for parenthesis matching for \t
|
|
while (curTag.Freq(L'(') > curTag.Freq(L')') && tkn.HasMoreTokens()) {
|
|
curTag << L"\\" << tkn.GetNextToken();
|
|
}
|
|
|
|
Tags.push_back(new AssOverrideTag(curTag));
|
|
|
|
curTag = L"\\";
|
|
}
|
|
}
|
|
void AssDialogueBlockOverride::AddTag(wxString const& tag) {
|
|
Tags.push_back(new AssOverrideTag(tag));
|
|
}
|
|
|
|
wxString AssDialogueBlockOverride::GetText() {
|
|
text.clear();
|
|
for (std::vector<AssOverrideTag*>::iterator cur=Tags.begin();cur!=Tags.end();cur++) {
|
|
text += **cur;
|
|
}
|
|
return text;
|
|
}
|
|
|
|
void AssDialogueBlockOverride::ProcessParameters(AssDialogueBlockOverride::ProcessParametersCallback callback,void *userData) {
|
|
for (std::vector<AssOverrideTag*>::iterator cur=Tags.begin();cur!=Tags.end();cur++) {
|
|
AssOverrideTag *curTag = *cur;
|
|
|
|
// Find parameters
|
|
for (unsigned n = 0; n < curTag->Params.size(); n++) {
|
|
AssOverrideParameter *curPar = curTag->Params[n];
|
|
|
|
if (curPar->GetType() != VARDATA_NONE && !curPar->omitted) {
|
|
(*callback)(curTag->Name,n,curPar,userData);
|
|
|
|
// Go recursive if it's a block parameter
|
|
if (curPar->GetType() == VARDATA_BLOCK) {
|
|
curPar->Get<AssDialogueBlockOverride*>()->ProcessParameters(callback,userData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AssOverrideParamProto::AssOverrideParamProto(VariableDataType type,int opt,ASS_ParameterClass classi)
|
|
: type(type)
|
|
, optional(opt)
|
|
, classification(classi)
|
|
{
|
|
}
|
|
|
|
void AssOverrideTagProto::AddParam(VariableDataType type, ASS_ParameterClass classi, int opt) {
|
|
params.push_back(AssOverrideParamProto(type, opt, classi));
|
|
}
|
|
void AssOverrideTagProto::Set(wxString newName, VariableDataType type, ASS_ParameterClass classi, int opt) {
|
|
name = newName;
|
|
params.push_back(AssOverrideParamProto(type, opt, classi));
|
|
}
|
|
|
|
static std::vector<AssOverrideTagProto> proto;
|
|
static void load_protos() {
|
|
static bool loaded = false;
|
|
if (loaded) return;
|
|
loaded = true;
|
|
|
|
proto.resize(56);
|
|
int i = 0;
|
|
|
|
// Longer tag names must appear before shorter tag names
|
|
|
|
proto[0].Set(L"\\alpha", VARDATA_TEXT); // \alpha
|
|
proto[++i].Set(L"\\bord", VARDATA_FLOAT,PARCLASS_ABSOLUTE_SIZE); // \bord<depth>
|
|
proto[++i].Set(L"\\xbord", VARDATA_FLOAT,PARCLASS_ABSOLUTE_SIZE); // \xbord<depth>
|
|
proto[++i].Set(L"\\ybord", VARDATA_FLOAT,PARCLASS_ABSOLUTE_SIZE); // \ybord<depth>
|
|
proto[++i].Set(L"\\shad", VARDATA_FLOAT,PARCLASS_ABSOLUTE_SIZE); // \shad<depth>
|
|
proto[++i].Set(L"\\xshad", VARDATA_FLOAT,PARCLASS_ABSOLUTE_SIZE); // \xshad<depth>
|
|
proto[++i].Set(L"\\yshad", VARDATA_FLOAT,PARCLASS_ABSOLUTE_SIZE); // \yshad<depth>
|
|
|
|
// \fade(<a1>,<a2>,<a3>,<t1>,<t2>,<t3>,<t4>)
|
|
i++;
|
|
proto[i].name = L"\\fade";
|
|
proto[i].AddParam(VARDATA_INT);
|
|
proto[i].AddParam(VARDATA_INT);
|
|
proto[i].AddParam(VARDATA_INT);
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_RELATIVE_TIME_START);
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_RELATIVE_TIME_START);
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_RELATIVE_TIME_START);
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_RELATIVE_TIME_START);
|
|
|
|
// \move(<x1>,<y1>,<x2>,<y2>[,<t1>,<t2>])
|
|
i++;
|
|
proto[i].name = L"\\move";
|
|
proto[i].AddParam(VARDATA_FLOAT,PARCLASS_ABSOLUTE_POS_X);
|
|
proto[i].AddParam(VARDATA_FLOAT,PARCLASS_ABSOLUTE_POS_Y);
|
|
proto[i].AddParam(VARDATA_FLOAT,PARCLASS_ABSOLUTE_POS_X);
|
|
proto[i].AddParam(VARDATA_FLOAT,PARCLASS_ABSOLUTE_POS_Y);
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_RELATIVE_TIME_START);
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_RELATIVE_TIME_START);
|
|
|
|
// If these are rearranged, keep rect clip and vector clip adjacent in this order
|
|
// \clip(<x1>,<y1>,<x2>,<y2>)
|
|
i++;
|
|
proto[i].name = L"\\clip";
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_ABSOLUTE_POS_X);
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_ABSOLUTE_POS_Y);
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_ABSOLUTE_POS_X);
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_ABSOLUTE_POS_Y);
|
|
|
|
// \clip([<scale>,]<some drawings>)
|
|
i++;
|
|
proto[i].name = L"\\clip";
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_NORMAL,OPTIONAL_2);
|
|
proto[i].AddParam(VARDATA_TEXT,PARCLASS_DRAWING);
|
|
|
|
// \iclip(<x1>,<y1>,<x2>,<y2>)
|
|
i++;
|
|
proto[i].name = L"\\iclip";
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_ABSOLUTE_POS_X);
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_ABSOLUTE_POS_Y);
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_ABSOLUTE_POS_X);
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_ABSOLUTE_POS_Y);
|
|
|
|
// \iclip([<scale>,]<some drawings>)
|
|
i++;
|
|
proto[i].name = L"\\iclip";
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_NORMAL,OPTIONAL_2);
|
|
proto[i].AddParam(VARDATA_TEXT,PARCLASS_DRAWING);
|
|
|
|
proto[++i].Set(L"\\fscx", VARDATA_FLOAT,PARCLASS_RELATIVE_SIZE_X); // \fscx<percent>
|
|
proto[++i].Set(L"\\fscy", VARDATA_FLOAT,PARCLASS_RELATIVE_SIZE_Y); // \fscy<percent>
|
|
// \pos(<x>,<y>)
|
|
i++;
|
|
proto[i].name = L"\\pos";
|
|
proto[i].AddParam(VARDATA_FLOAT,PARCLASS_ABSOLUTE_POS_X);
|
|
proto[i].AddParam(VARDATA_FLOAT,PARCLASS_ABSOLUTE_POS_Y);
|
|
|
|
// \org(<x>,<y>)
|
|
i++;
|
|
proto[i].name = L"\\org";
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_ABSOLUTE_POS_X);
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_ABSOLUTE_POS_Y);
|
|
|
|
proto[++i].Set(L"\\pbo", VARDATA_INT,PARCLASS_ABSOLUTE_POS_Y); // \pbo<y>
|
|
// \fad(<t1>,<t2>)
|
|
i++;
|
|
proto[i].name = L"\\fad";
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_RELATIVE_TIME_START);
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_RELATIVE_TIME_END);
|
|
|
|
proto[++i].Set(L"\\fsp", VARDATA_FLOAT,PARCLASS_ABSOLUTE_SIZE); // \fsp<pixels>
|
|
proto[++i].Set(L"\\frx", VARDATA_FLOAT); // \frx<degrees>
|
|
proto[++i].Set(L"\\fry", VARDATA_FLOAT); // \fry<degrees>
|
|
proto[++i].Set(L"\\frz", VARDATA_FLOAT); // \frz<degrees>
|
|
proto[++i].Set(L"\\fr", VARDATA_FLOAT); // \fr<degrees>
|
|
proto[++i].Set(L"\\fax", VARDATA_FLOAT); // \fax<factor>
|
|
proto[++i].Set(L"\\fay", VARDATA_FLOAT); // \fay<factor>
|
|
proto[++i].Set(L"\\1c", VARDATA_TEXT); // \1c&H<bbggrr>&
|
|
proto[++i].Set(L"\\2c", VARDATA_TEXT); // \2c&H<bbggrr>&
|
|
proto[++i].Set(L"\\3c", VARDATA_TEXT); // \3c&H<bbggrr>&
|
|
proto[++i].Set(L"\\4c", VARDATA_TEXT); // \4c&H<bbggrr>&
|
|
proto[++i].Set(L"\\1a", VARDATA_TEXT); // \1a&H<aa>&
|
|
proto[++i].Set(L"\\2a", VARDATA_TEXT); // \2a&H<aa>&
|
|
proto[++i].Set(L"\\3a", VARDATA_TEXT); // \3a&H<aa>&
|
|
proto[++i].Set(L"\\4a", VARDATA_TEXT); // \4a&H<aa>&
|
|
proto[++i].Set(L"\\fe", VARDATA_TEXT); // \fe<charset>
|
|
proto[++i].Set(L"\\ko", VARDATA_INT,PARCLASS_KARAOKE); // \ko<duration>
|
|
proto[++i].Set(L"\\kf", VARDATA_INT,PARCLASS_KARAOKE); // \kf<duration>
|
|
proto[++i].Set(L"\\be", VARDATA_INT); // \be<strength>
|
|
proto[++i].Set(L"\\blur", VARDATA_FLOAT); // \blur<strength>
|
|
proto[++i].Set(L"\\fn", VARDATA_TEXT); // \fn<name>
|
|
proto[++i].Set(L"\\fs+", VARDATA_FLOAT); // \fs+<size>
|
|
proto[++i].Set(L"\\fs-", VARDATA_FLOAT); // \fs-<size>
|
|
proto[++i].Set(L"\\fs", VARDATA_FLOAT,PARCLASS_ABSOLUTE_SIZE); // \fs<size>
|
|
proto[++i].Set(L"\\an", VARDATA_INT); // \an<alignment>
|
|
proto[++i].Set(L"\\c", VARDATA_TEXT); // \c&H<bbggrr>&
|
|
proto[++i].Set(L"\\b", VARDATA_INT); // \b<0/1/weight>
|
|
proto[++i].Set(L"\\i", VARDATA_BOOL); // \i<0/1>
|
|
proto[++i].Set(L"\\u", VARDATA_BOOL); // \u<0/1>
|
|
proto[++i].Set(L"\\s", VARDATA_BOOL); // \s<0/1>
|
|
proto[++i].Set(L"\\a", VARDATA_INT); // \a<alignment>
|
|
proto[++i].Set(L"\\k", VARDATA_INT,PARCLASS_KARAOKE); // \k<duration>
|
|
proto[++i].Set(L"\\K", VARDATA_INT,PARCLASS_KARAOKE); // \K<duration>
|
|
proto[++i].Set(L"\\q", VARDATA_INT); // \q<0-3>
|
|
proto[++i].Set(L"\\p", VARDATA_INT); // \p<n>
|
|
proto[++i].Set(L"\\r", VARDATA_TEXT); // \r[<name>]
|
|
|
|
// \t([<t1>,<t2>,][<accel>,]<style modifiers>)
|
|
i++;
|
|
proto[i].name = L"\\t";
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_RELATIVE_TIME_START,OPTIONAL_3 | OPTIONAL_4);
|
|
proto[i].AddParam(VARDATA_INT,PARCLASS_RELATIVE_TIME_START,OPTIONAL_3 | OPTIONAL_4);
|
|
proto[i].AddParam(VARDATA_FLOAT,PARCLASS_NORMAL,OPTIONAL_2 | OPTIONAL_4);
|
|
proto[i].AddParam(VARDATA_BLOCK);
|
|
}
|
|
|
|
AssOverrideTag::AssOverrideTag() : valid(false) { }
|
|
AssOverrideTag::AssOverrideTag(wxString text) {
|
|
SetText(text);
|
|
}
|
|
|
|
AssOverrideTag::~AssOverrideTag () {
|
|
delete_clear(Params);
|
|
}
|
|
|
|
void AssOverrideTag::Clear() {
|
|
delete_clear(Params);
|
|
Params.reserve(6);
|
|
valid = false;
|
|
}
|
|
|
|
void AssOverrideTag::SetText(const wxString &text) {
|
|
load_protos();
|
|
for (AssOverrideTagProto::iterator cur=proto.begin();cur!=proto.end();cur++) {
|
|
if (text.StartsWith(cur->name)) {
|
|
Name = cur->name;
|
|
ParseParameters(text.Mid(Name.length()), cur);
|
|
valid = true;
|
|
return;
|
|
}
|
|
}
|
|
// Junk tag
|
|
Name = text;
|
|
valid = false;
|
|
}
|
|
|
|
bool AssOverrideTag::IsValid() {
|
|
return valid;
|
|
}
|
|
|
|
std::vector<wxString> tokenize(const wxString &text) {
|
|
std::vector<wxString> paramList;
|
|
paramList.reserve(6);
|
|
|
|
if (text.empty()) {
|
|
return paramList;
|
|
}
|
|
if (text[0] != L'(') {
|
|
// There's just one parameter (because there's no parentheses)
|
|
// This means text is all our parameters
|
|
wxString param(text);
|
|
paramList.push_back(param.Trim(true).Trim(false));
|
|
return paramList;
|
|
}
|
|
|
|
// Ok, so there are parentheses used here, so there may be more than one parameter
|
|
// Enter fullscale parsing!
|
|
size_t i = 0, textlen = text.size();
|
|
size_t start = 0;
|
|
int parDepth = 1;
|
|
while (i < textlen && parDepth > 0) {
|
|
// Just skip until next ',' or ')', whichever comes first
|
|
// (Next ')' is achieved when parDepth == 0)
|
|
start = ++i;
|
|
while (i < textlen && parDepth > 0) {
|
|
wxChar c = text[i];
|
|
// parDepth 1 is where we start, and the tag-level we're interested in parsing on
|
|
if (c == L',' && parDepth == 1) break;
|
|
if (c == _T('(')) parDepth++;
|
|
else if (c == _T(')')) {
|
|
parDepth--;
|
|
if (parDepth < 0) {
|
|
wxLogWarning(L"Unmatched parenthesis near '%s'!\nTag-parsing incomplete.", text.SubString(i, 10).c_str());
|
|
return paramList;
|
|
}
|
|
else if (parDepth == 0) {
|
|
// We just ate the parenthesis ending this parameter block
|
|
// Make sure it doesn't get included in the parameter text
|
|
break;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
// i now points to the first character not member of this parameter
|
|
paramList.push_back(text.SubString(start, i-1).Trim(true).Trim(false));
|
|
}
|
|
|
|
if (i+1 < textlen) {
|
|
// There's some additional garbage after the parentheses
|
|
// Just add it in for completeness
|
|
paramList.push_back(text.Mid(i+1));
|
|
}
|
|
return paramList;
|
|
}
|
|
|
|
void AssOverrideTag::ParseParameters(const wxString &text, AssOverrideTagProto::iterator proto) {
|
|
Clear();
|
|
|
|
// Tokenize text, attempting to find all parameters
|
|
std::vector<wxString> paramList = tokenize(text);
|
|
size_t totalPars = paramList.size();
|
|
|
|
int parsFlag = 1 << (totalPars - 1); // Get optional parameters flag
|
|
// vector (i)clip is the second clip prototype in the list
|
|
if ((Name == L"\\clip" || Name == L"\\iclip") && totalPars != 4) {
|
|
++proto;
|
|
}
|
|
|
|
unsigned curPar = 0;
|
|
for (size_t n = 0; n < proto->params.size(); n++) {
|
|
AssOverrideParamProto *curproto = &proto->params[n];
|
|
|
|
// Create parameter
|
|
AssOverrideParameter *newparam = new AssOverrideParameter;
|
|
newparam->classification = curproto->classification;
|
|
Params.push_back(newparam);
|
|
|
|
// Check if it's optional and not present
|
|
if (!(curproto->optional & parsFlag) || curPar >= totalPars) {
|
|
newparam->omitted = true;
|
|
continue;
|
|
}
|
|
|
|
wxString curtok = paramList[curPar++];
|
|
|
|
if (curtok.empty()) {
|
|
curPar++;
|
|
continue;
|
|
}
|
|
|
|
wxChar firstChar = curtok[0];
|
|
bool auto4 = (firstChar == _T('!') || firstChar == _T('$') || firstChar == _T('%')) && curproto->type != VARDATA_BLOCK;
|
|
if (auto4) {
|
|
newparam->Set(curtok);
|
|
}
|
|
else {
|
|
switch (curproto->type) {
|
|
case VARDATA_INT: {
|
|
long temp;
|
|
curtok.ToLong(&temp);
|
|
newparam->Set<int>(temp);
|
|
break;
|
|
}
|
|
case VARDATA_FLOAT: {
|
|
double temp;
|
|
curtok.ToDouble(&temp);
|
|
newparam->Set(temp);
|
|
break;
|
|
}
|
|
case VARDATA_TEXT:
|
|
newparam->Set(curtok);
|
|
break;
|
|
case VARDATA_BOOL: {
|
|
long temp;
|
|
curtok.ToLong(&temp);
|
|
newparam->Set<bool>(temp != 0);
|
|
break;
|
|
}
|
|
case VARDATA_BLOCK: {
|
|
AssDialogueBlockOverride *temp = new AssDialogueBlockOverride;
|
|
temp->text = curtok;
|
|
temp->ParseTags();
|
|
newparam->Set(temp);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AssOverrideTag::operator wxString() {
|
|
wxString result = Name;
|
|
|
|
// Determine if it needs parentheses
|
|
bool parentheses =
|
|
Name == L"\\t" ||
|
|
Name == L"\\pos" ||
|
|
Name == L"\\fad" ||
|
|
Name == L"\\org" ||
|
|
Name == L"\\clip" ||
|
|
Name == L"\\iclip" ||
|
|
Name == L"\\move" ||
|
|
Name == L"\\fade";
|
|
if (parentheses) result += L"(";
|
|
|
|
// Add parameters
|
|
bool any = false;
|
|
for (std::vector<AssOverrideParameter*>::iterator cur=Params.begin();cur!=Params.end();cur++) {
|
|
if ((*cur)->GetType() != VARDATA_NONE && !(*cur)->omitted) {
|
|
result += (*cur)->Get<wxString>();
|
|
result += L",";
|
|
any = true;
|
|
}
|
|
}
|
|
if (any) result = result.Left(result.Length()-1);
|
|
|
|
if (parentheses) result += L")";
|
|
return result;
|
|
}
|