Extract some bits that don't need to be templated from templates

Cuts compile time by about 10% and shrinks the final binary a little.
This commit is contained in:
Thomas Goyne 2014-12-28 12:45:55 -08:00
parent 372b9fe115
commit a6b1639320
24 changed files with 383 additions and 289 deletions

View File

@ -125,6 +125,7 @@
</ClCompile>
<ClCompile Include="$(SrcDir)ass\dialogue_parser.cpp" />
<ClCompile Include="$(SrcDir)ass\time.cpp" />
<ClCompile Include="$(SrcDir)ass\uuencode.cpp" />
<ClCompile Include="$(SrcDir)audio\provider.cpp" />
<ClCompile Include="$(SrcDir)audio\provider_convert.cpp" />
<ClCompile Include="$(SrcDir)audio\provider_dummy.cpp" />
@ -151,6 +152,7 @@
<ClCompile Include="$(SrcDir)common\kana_table.cpp" />
<ClCompile Include="$(SrcDir)common\karaoke_matcher.cpp" />
<ClCompile Include="$(SrcDir)common\keyframe.cpp" />
<ClCompile Include="$(SrcDir)common\line_iterator.cpp" />
<ClCompile Include="$(SrcDir)common\log.cpp" />
<ClCompile Include="$(SrcDir)common\mru.cpp" />
<ClCompile Include="$(SrcDir)common\option.cpp" />

View File

@ -238,6 +238,9 @@
<ClCompile Include="$(SrcDir)windows\log_win.cpp">
<Filter>Source Files\Windows</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)common\line_iterator.cpp">
<Filter>Source Files\Common</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)common\log.cpp">
<Filter>Source Files\Common</Filter>
</ClCompile>
@ -286,6 +289,9 @@
<ClCompile Include="$(SrcDir)ass\time.cpp">
<Filter>ASS</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)ass\uuencode.cpp">
<Filter>ASS</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)common\color.cpp">
<Filter>Source Files\Common</Filter>
</ClCompile>

View File

@ -4,6 +4,7 @@ aegisub_OBJ := \
$(d)common/parser.o \
$(d)ass/dialogue_parser.o \
$(d)ass/time.o \
$(d)ass/uuencode.o \
$(subst .cpp,.o,$(wildcard $(d)audio/*.cpp)) \
$(subst .cpp,.o,$(wildcard $(d)common/cajun/*.cpp)) \
$(subst .cpp,.o,$(wildcard $(d)lua/modules/*.cpp)) \
@ -25,6 +26,7 @@ aegisub_OBJ := \
$(d)common/kana_table.o \
$(d)common/karaoke_matcher.o \
$(d)common/keyframe.o \
$(d)common/line_iterator.o \
$(d)common/log.o \
$(d)common/mru.o \
$(d)common/option.o \

View File

@ -0,0 +1,85 @@
// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
#include <libaegisub/ass/uuencode.h>
#include <algorithm>
// Despite being called uuencoding by ass_specs.doc, the format is actually
// somewhat different from real uuencoding. Each 3-byte chunk is split into 4
// 6-bit pieces, then 33 is added to each piece. Lines are wrapped after 80
// characters, and files with non-multiple-of-three lengths are padded with
// zero.
namespace agi { namespace ass {
std::string UUEncode(const char *begin, const char *end, bool insert_linebreaks) {
size_t size = std::distance(begin, end);
std::string ret;
ret.reserve((size * 4 + 2) / 3 + size / 80 * 2);
size_t written = 0;
for (size_t pos = 0; pos < size; pos += 3) {
unsigned char src[3] = { '\0', '\0', '\0' };
memcpy(src, begin + pos, std::min<size_t>(3u, size - pos));
unsigned char dst[4] = {
static_cast<unsigned char>(src[0] >> 2),
static_cast<unsigned char>(((src[0] & 0x3) << 4) | ((src[1] & 0xF0) >> 4)),
static_cast<unsigned char>(((src[1] & 0xF) << 2) | ((src[2] & 0xC0) >> 6)),
static_cast<unsigned char>(src[2] & 0x3F)
};
for (size_t i = 0; i < std::min<size_t>(size - pos + 1, 4u); ++i) {
ret += dst[i] + 33;
if (insert_linebreaks && ++written == 80 && pos + 3 < size) {
written = 0;
ret += "\r\n";
}
}
}
return ret;
}
std::vector<char> UUDecode(const char *begin, const char *end) {
std::vector<char> ret;
size_t len = end - begin;
ret.reserve(len * 3 / 4);
for (size_t pos = 0; pos + 1 < len; ) {
size_t bytes = 0;
unsigned char src[4] = { '\0', '\0', '\0', '\0' };
for (size_t i = 0; i < 4 && pos < len; ++pos) {
char c = begin[pos];
if (c && c != '\n' && c != '\r') {
src[i++] = c - 33;
++bytes;
}
}
if (bytes > 1)
ret.push_back((src[0] << 2) | (src[1] >> 4));
if (bytes > 2)
ret.push_back(((src[1] & 0xF) << 4) | (src[2] >> 2));
if (bytes > 3)
ret.push_back(((src[2] & 0x3) << 6) | (src[3]));
}
return ret;
}
} }

View File

@ -0,0 +1,79 @@
// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include <libaegisub/line_iterator.h>
#include <libaegisub/charset_conv.h>
#include <boost/algorithm/string/case_conv.hpp>
namespace agi {
line_iterator_base::line_iterator_base(std::istream &stream, std::string encoding)
: stream(&stream)
{
boost::to_lower(encoding);
if (encoding != "utf-8") {
agi::charset::IconvWrapper c("utf-8", encoding.c_str());
c.Convert("\r", 1, reinterpret_cast<char *>(&cr), sizeof(int));
c.Convert("\n", 1, reinterpret_cast<char *>(&lf), sizeof(int));
width = c.RequiredBufferSize("\n");
conv = std::make_shared<agi::charset::IconvWrapper>(encoding.c_str(), "utf-8");
}
}
bool line_iterator_base::getline(std::string &str) {
if (!stream) return false;
if (!stream->good()) {
stream = nullptr;
return false;
}
if (width == 1) {
std::getline(*stream, str);
if (str.size() && str.back() == '\r')
str.pop_back();
}
else {
union {
int32_t chr;
char buf[4];
} u;
for (;;) {
u.chr = 0;
std::streamsize read = stream->rdbuf()->sgetn(u.buf, width);
if (read < (std::streamsize)width) {
for (int i = 0; i < read; i++) {
str += u.buf[i];
}
stream->setstate(std::ios::eofbit);
break;
}
if (u.chr == cr) continue;
if (u.chr == lf) break;
for (int i = 0; i < read; i++) {
str += u.buf[i];
}
}
}
if (conv.get()) {
std::string tmp;
conv->Convert(str, tmp);
str = std::move(tmp);
}
return true;
}
}

View File

@ -61,15 +61,8 @@ class ConfigVisitor final : public json::ConstVisitor {
void ReadArray(json::Array const& src, std::string const& array_type) {
typename OptionValueType::value_type arr;
arr.reserve(src.size());
for (json::Object const& obj : src) {
if (obj.size() != 1)
return Error("Invalid array member");
if (obj.begin()->first != array_type)
return Error("Attempt to insert value into array of wrong type");
for (json::Object const& obj : src)
arr.push_back((typename OptionValueType::value_type::value_type)(obj.begin()->second));
}
values.push_back(agi::make_unique<OptionValueType>(name, std::move(arr)));
}
@ -92,6 +85,13 @@ class ConfigVisitor final : public json::ConstVisitor {
return Error("Invalid array member");
auto const& array_type = front.begin()->first;
for (json::Object const& obj : array) {
if (obj.size() != 1)
return Error("Invalid array member");
if (obj.begin()->first != array_type)
return Error("Attempt to insert value into array of wrong type");
}
if (array_type == "string")
ReadArray<OptionValueListString>(array, array_type);
else if (array_type == "int")
@ -158,11 +158,9 @@ void put_option(json::Object &obj, const std::string &path, json::UnknownElement
template<class T>
void put_array(json::Object &obj, const std::string &path, const char *element_key, std::vector<T> const& value) {
json::Array array;
for (T const& elem : value) {
array.push_back(json::Object());
static_cast<json::Object&>(array.back())[element_key] = (json::UnknownElement)elem;
}
array.resize(value.size());
for (size_t i = 0, size = value.size(); i < size; ++i)
static_cast<json::Object&>(array[i])[element_key] = (json::UnknownElement)value[i];
put_option(obj, path, std::move(array));
}

View File

@ -14,79 +14,13 @@
//
// Aegisub Project http://www.aegisub.org/
#include <algorithm>
#include <string>
#include <vector>
// Despite being called uuencoding by ass_specs.doc, the format is actually
// somewhat different from real uuencoding. Each 3-byte chunk is split into 4
// 6-bit pieces, then 33 is added to each piece. Lines are wrapped after 80
// characters, and files with non-multiple-of-three lengths are padded with
// zero.
namespace agi { namespace ass {
/// Encode a blob of data, using ASS's nonstandard variant
template<typename RandomAccessRange>
std::string UUEncode(RandomAccessRange const& data, bool insert_linebreaks=true) {
using std::begin;
using std::end;
size_t size = std::distance(begin(data), end(data));
std::string ret;
ret.reserve((size * 4 + 2) / 3 + size / 80 * 2);
size_t written = 0;
for (size_t pos = 0; pos < size; pos += 3) {
unsigned char src[3] = { '\0', '\0', '\0' };
memcpy(src, &data[pos], std::min<size_t>(3u, size - pos));
unsigned char dst[4] = {
static_cast<unsigned char>(src[0] >> 2),
static_cast<unsigned char>(((src[0] & 0x3) << 4) | ((src[1] & 0xF0) >> 4)),
static_cast<unsigned char>(((src[1] & 0xF) << 2) | ((src[2] & 0xC0) >> 6)),
static_cast<unsigned char>(src[2] & 0x3F)
};
for (size_t i = 0; i < std::min<size_t>(size - pos + 1, 4u); ++i) {
ret += dst[i] + 33;
if (insert_linebreaks && ++written == 80 && pos + 3 < size) {
written = 0;
ret += "\r\n";
}
}
}
return ret;
}
std::string UUEncode(const char *begin, const char *end, bool insert_linebreaks=true);
/// Decode an ASS uuencoded string
template<typename String>
std::vector<char> UUDecode(String const& str) {
std::vector<char> ret;
size_t len = std::end(str) - std::begin(str);
ret.reserve(len * 3 / 4);
for (size_t pos = 0; pos + 1 < len; ) {
size_t bytes = 0;
unsigned char src[4] = { '\0', '\0', '\0', '\0' };
for (size_t i = 0; i < 4 && pos < len; ++pos) {
char c = str[pos];
if (c && c != '\n' && c != '\r') {
src[i++] = c - 33;
++bytes;
}
}
if (bytes > 1)
ret.push_back((src[0] << 2) | (src[1] >> 4));
if (bytes > 2)
ret.push_back(((src[1] & 0xF) << 4) | (src[2] >> 2));
if (bytes > 3)
ret.push_back(((src[2] & 0x3) << 6) | (src[3]));
}
return ret;
}
std::vector<char> UUDecode(const char *begin, const char *end);
} }

View File

@ -23,22 +23,44 @@
#include <cstdint>
#include <boost/interprocess/streams/bufferstream.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include <libaegisub/charset_conv.h>
namespace agi {
namespace charset { class IconvWrapper; }
class line_iterator_base {
std::istream *stream = nullptr; ///< Stream to iterate over
std::shared_ptr<agi::charset::IconvWrapper> conv;
int cr = '\r'; ///< CR character in the source encoding
int lf = '\n'; ///< LF character in the source encoding
size_t width = 1; ///< width of LF character in the source encoding
protected:
bool getline(std::string &str);
public:
line_iterator_base(std::istream &stream, std::string encoding = "utf-8");
line_iterator_base() = default;
line_iterator_base(line_iterator_base const&) = default;
#ifndef _MSC_VER
line_iterator_base(line_iterator_base&&) = default;
#endif
line_iterator_base& operator=(line_iterator_base const&) = default;
#ifndef _MSC_VER
line_iterator_base& operator=(line_iterator_base&&) = default;
#endif
bool operator==(line_iterator_base const& rgt) const { return stream == rgt.stream; }
bool operator!=(line_iterator_base const& rgt) const { return !operator==(rgt); }
};
/// @class line_iterator
/// @brief An iterator over lines in a stream
template<class OutputType = std::string>
class line_iterator final : public std::iterator<std::input_iterator_tag, OutputType> {
std::istream *stream = nullptr; ///< Stream to iterator over
class line_iterator final : public line_iterator_base, public std::iterator<std::input_iterator_tag, OutputType> {
OutputType value; ///< Value to return when this is dereference
std::shared_ptr<agi::charset::IconvWrapper> conv;
int cr; ///< CR character in the source encoding
int lf; ///< LF character in the source encoding
size_t width; ///< width of LF character in the source encoding
/// @brief Convert a string to the output type
/// @param str Line read from the file
@ -47,9 +69,6 @@ class line_iterator final : public std::iterator<std::input_iterator_tag, Output
/// their desired output type or simply provide a specialization of this
/// method which does the conversion.
inline bool convert(std::string &str);
/// @brief Get the next line from the stream
/// @param[out] str String to fill with the next line
void getline(std::string &str);
/// @brief Get the next value from the stream
void next();
@ -60,19 +79,8 @@ public:
/// lifetime of the iterator and that it get cleaned up.
/// @param encoding Encoding of the text read from the stream
line_iterator(std::istream &stream, std::string encoding = "utf-8")
: stream(&stream)
, cr('\r')
, lf('\n')
, width(1)
: line_iterator_base(stream, std::move(encoding))
{
if (boost::to_lower_copy(encoding) != "utf-8") {
agi::charset::IconvWrapper c("utf-8", encoding.c_str());
c.Convert("\r", 1, reinterpret_cast<char *>(&cr), sizeof(int));
c.Convert("\n", 1, reinterpret_cast<char *>(&lf), sizeof(int));
width = c.RequiredBufferSize("\n");
conv = std::make_shared<agi::charset::IconvWrapper>(encoding.c_str(), "utf-8");
}
++(*this);
}
@ -96,30 +104,11 @@ public:
return tmp;
}
bool operator==(line_iterator<OutputType> const& rgt) const { return stream == rgt.stream; }
bool operator!=(line_iterator<OutputType> const& rgt) const { return !operator==(rgt); }
// typedefs needed by some stl algorithms
typedef OutputType* pointer;
typedef OutputType& reference;
typedef const OutputType* const_pointer;
typedef const OutputType& const_reference;
line_iterator<OutputType>& operator=(line_iterator<OutputType> that) {
using std::swap;
swap(*this, that);
return *this;
}
void swap(line_iterator<OutputType> &that) throw() {
using std::swap;
swap(stream, that.stream);
swap(value, that.value);
swap(conv, that.conv);
swap(lf, that.lf);
swap(cr, that.cr);
swap(width, that.width);
}
};
// Enable range-based for
@ -129,78 +118,19 @@ line_iterator<T>& begin(line_iterator<T>& it) { return it; }
template<typename T>
line_iterator<T> end(line_iterator<T>&) { return agi::line_iterator<T>(); }
template<class OutputType>
void line_iterator<OutputType>::getline(std::string &str) {
union {
int32_t chr;
char buf[4];
} u;
for (;;) {
u.chr = 0;
std::streamsize read = stream->rdbuf()->sgetn(u.buf, width);
if (read < (std::streamsize)width) {
for (int i = 0; i < read; i++) {
str += u.buf[i];
}
stream->setstate(std::ios::eofbit);
return;
}
if (u.chr == cr) continue;
if (u.chr == lf) return;
for (int i = 0; i < read; i++) {
str += u.buf[i];
}
}
}
template<class OutputType>
void line_iterator<OutputType>::next() {
if (!stream) return;
if (!stream->good()) {
stream = nullptr;
std::string str;
if (!getline(str))
return;
}
std::string str, cstr, *target;
if (width == 1) {
std::getline(*stream, str);
if (str.size() && str.back() == '\r')
str.pop_back();
}
else {
getline(str);
}
if (conv.get()) {
conv->Convert(str, cstr);
target = &cstr;
}
else {
target = &str;
}
if (!convert(*target))
if (!convert(str))
next();
}
template<>
inline void line_iterator<std::string>::next() {
if (!stream) return;
if (!stream->good()) {
stream = nullptr;
return;
}
std::string cstr;
std::string *target = conv ? &cstr : &value;
if (width == 1) {
std::getline(*stream, *target);
if (target->size() && target->back() == '\r')
target->pop_back();
}
else
getline(*target);
if (conv.get()) {
value.clear();
conv->Convert(*target, value);
}
value.clear();
getline(value);
}
template<class OutputType>
@ -210,9 +140,4 @@ inline bool line_iterator<OutputType>::convert(std::string &str) {
return !ss.fail();
}
template<class T>
void swap(agi::line_iterator<T> &lft, agi::line_iterator<T> &rgt) {
lft.swap(rgt);
}
}

View File

@ -20,19 +20,17 @@
#include <lua.hpp>
namespace agi { namespace lua {
void do_register_lib_function(lua_State *L, const char *name, const char *type_name, void *func);
void do_register_lib_table(lua_State *L, std::initializer_list<const char *> types);
static void register_lib_functions(lua_State *) {
// Base case of recursion; nothing to do
}
template<typename Func, typename... Rest>
void register_lib_functions(lua_State *L, const char *name, Func *func, Rest... rest) {
lua_pushvalue(L, -2); // push cast function
lua_pushstring(L, type_name<Func*>::name().c_str());
// This cast isn't legal, but LuaJIT internally requires that it work, so we can rely on it too
lua_pushlightuserdata(L, (void *)func);
lua_call(L, 2, 1);
lua_setfield(L, -2, name);
do_register_lib_function(L, name, type_name<Func*>::name().c_str(), (void *)func);
register_lib_functions(L, rest...);
}
@ -40,20 +38,7 @@ template<typename... Args>
void register_lib_table(lua_State *L, std::initializer_list<const char *> types, Args... functions) {
static_assert((sizeof...(functions) & 1) == 0, "Functions must be alternating names and function pointers");
lua_getglobal(L, "require");
lua_pushstring(L, "ffi");
lua_call(L, 1, 1);
// Register all passed type with the ffi
for (auto type : types) {
lua_getfield(L, -1, "cdef");
lua_pushfstring(L, "typedef struct %s %s;", type, type);
lua_call(L, 1, 0);
}
lua_getfield(L, -1, "cast");
lua_remove(L, -2); // ffi table
do_register_lib_table(L, types); // leaves ffi.cast on the stack
lua_createtable(L, 0, sizeof...(functions) / 2);
register_lib_functions(L, functions...);
lua_remove(L, -2); // ffi.cast function

View File

@ -77,10 +77,7 @@ void push_value(lua_State *L, std::vector<T> const& value) {
}
}
/// Wrap a function which may throw exceptions and make it trigger lua errors
/// whenever it throws
template<int (*func)(lua_State *L)>
int exception_wrapper(lua_State *L) {
inline int exception_wrapper(lua_State *L, int (*func)(lua_State *L)) {
try {
return func(L);
}
@ -101,6 +98,13 @@ int exception_wrapper(lua_State *L) {
}
}
/// Wrap a function which may throw exceptions and make it trigger lua errors
/// whenever it throws
template<int (*func)(lua_State *L)>
int exception_wrapper(lua_State *L) {
return exception_wrapper(L, func);
}
template<typename T>
void set_field(lua_State *L, const char *name, T value) {
push_value(L, value);

View File

@ -16,6 +16,7 @@
#include "libaegisub/lua/modules.h"
#include "libaegisub/lua/ffi.h"
#include "libaegisub/lua/utils.h"
extern "C" int luaopen_luabins(lua_State *L);
@ -40,5 +41,33 @@ void preload_modules(lua_State *L) {
set_field(L, "luabins", luaopen_luabins);
lua_pop(L, 2);
register_lib_functions(L); // silence an unused static function warning
}
void do_register_lib_function(lua_State *L, const char *name, const char *type_name, void *func) {
lua_pushvalue(L, -2); // push cast function
lua_pushstring(L, type_name);
lua_pushlightuserdata(L, func);
lua_call(L, 2, 1);
lua_setfield(L, -2, name);
}
void do_register_lib_table(lua_State *L, std::initializer_list<const char *> types) {
lua_getglobal(L, "require");
lua_pushstring(L, "ffi");
lua_call(L, 1, 1);
// Register all passed type with the ffi
for (auto type : types) {
lua_getfield(L, -1, "cdef");
lua_pushfstring(L, "typedef struct %s %s;", type, type);
lua_call(L, 1, 0);
}
lua_getfield(L, -1, "cast");
lua_remove(L, -2); // ffi table
// leaves ffi.cast on the stack
}
} }

View File

@ -21,7 +21,6 @@
#include <libaegisub/io.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/range/iterator_range.hpp>
AssAttachment::AssAttachment(AssAttachment const& rgt)
: entry_data(rgt.entry_data)
@ -49,7 +48,7 @@ AssAttachment::AssAttachment(agi::fs::path const& name, AssEntryGroup group)
agi::read_file_mapping file(name);
auto buff = file.read();
entry_data = (group == AssEntryGroup::FONT ? "fontname: " : "filename: ") + filename.get() + "\r\n";
entry_data = entry_data.get() + agi::ass::UUEncode(boost::make_iterator_range(buff, buff + file.size()));
entry_data = entry_data.get() + agi::ass::UUEncode(buff, buff + file.size());
}
size_t AssAttachment::GetSize() const {
@ -59,7 +58,7 @@ size_t AssAttachment::GetSize() const {
void AssAttachment::Extract(agi::fs::path const& filename) const {
auto header_end = entry_data.get().find('\n');
auto decoded = agi::ass::UUDecode(boost::make_iterator_range(entry_data.get().begin() + header_end + 1, entry_data.get().end()));
auto decoded = agi::ass::UUDecode(entry_data.get().c_str() + header_end + 1, &entry_data.get().back() + 1);
agi::io::Save(filename, true).Get().write(&decoded[0], decoded.size());
}

View File

@ -210,7 +210,7 @@ void AssParser::ParseExtradataLine(std::string const &data) {
value = inline_string_decode(value);
} else if (valuetype == "u") {
// ass uuencoded
auto valuedata = agi::ass::UUDecode(value);
auto valuedata = agi::ass::UUDecode(value.c_str(), value.c_str() + value.size());
value = std::string(valuedata.begin(), valuedata.end());
} else {
// unknown, error?

View File

@ -94,6 +94,23 @@ struct validate_sel_multiple : public Command {
}
};
template<typename String>
AssDialogue *get_dialogue(String data) {
boost::trim(data);
try {
// Try to interpret the line as an ASS line
return new AssDialogue(data);
}
catch (...) {
// Line didn't parse correctly, assume it's plain text that
// should be pasted in the Text field only
auto d = new AssDialogue;
d->End = 0;
d->Text = data;
return d;
}
}
template<typename Paster>
void paste_lines(agi::Context *c, bool paste_over, Paster&& paste_line) {
std::string data = GetClipboard();
@ -104,21 +121,7 @@ void paste_lines(agi::Context *c, bool paste_over, Paster&& paste_line) {
boost::char_separator<char> sep("\r\n");
for (auto curdata : boost::tokenizer<boost::char_separator<char>>(data, sep)) {
boost::trim(curdata);
AssDialogue *curdiag;
try {
// Try to interpret the line as an ASS line
curdiag = new AssDialogue(curdata);
}
catch (...) {
// Line didn't parse correctly, assume it's plain text that
// should be pasted in the Text field only
curdiag = new AssDialogue;
curdiag->End = 0;
curdiag->Text = curdata;
}
AssDialogue *inserted = paste_line(curdiag);
AssDialogue *inserted = paste_line(get_dialogue(curdata));
if (!inserted)
break;
@ -167,14 +170,21 @@ struct parsed_line {
parsed_line(parsed_line&& r) = default;
#endif
template<typename T>
T get_value(int blockn, T initial, std::string const& tag_name, std::string alt = "") const {
const AssOverrideTag *find_tag(int blockn, std::string const& tag_name, std::string const& alt) const {
for (auto ovr : blocks | sliced(0, blockn + 1) | reversed | agi::of_type<AssDialogueBlockOverride>()) {
for (auto const& tag : ovr->Tags | reversed) {
if (tag.Name == tag_name || tag.Name == alt)
return tag.Params[0].template Get<T>(initial);
return &tag;
}
}
return nullptr;
}
template<typename T>
T get_value(int blockn, T initial, std::string const& tag_name, std::string const& alt = "") const {
auto tag = find_tag(blockn, tag_name, alt);
if (tag)
return tag->Params[0].template Get<T>(initial);
return initial;
}
@ -1076,18 +1086,22 @@ struct edit_line_split_by_karaoke final : public validate_sel_nonempty {
}
};
template<typename Func>
void split_lines(agi::Context *c, Func&& set_time) {
void split_lines(agi::Context *c, AssDialogue *&n1, AssDialogue *&n2) {
int pos = c->textSelectionController->GetSelectionStart();
AssDialogue *n1 = c->selectionController->GetActiveLine();
auto n2 = new AssDialogue(*n1);
n1 = c->selectionController->GetActiveLine();
n2 = new AssDialogue(*n1);
c->ass->Events.insert(++c->ass->iterator_to(*n1), *n2);
std::string orig = n1->Text;
n1->Text = boost::trim_right_copy(orig.substr(0, pos));
n2->Text = boost::trim_left_copy(orig.substr(pos));
}
template<typename Func>
void split_lines(agi::Context *c, Func&& set_time) {
AssDialogue *n1, *n2;
split_lines(c, n1, n2);
set_time(n1, n2);
c->ass->Commit(_("split"), AssFile::COMMIT_DIAG_ADDREM | AssFile::COMMIT_DIAG_FULL);

View File

@ -143,12 +143,16 @@ DialogDummyVideo::DialogDummyVideo(wxWindow *parent)
});
}
template<typename T>
void DialogDummyVideo::AddCtrl(wxString const& label, T *ctrl) {
static void add_label(wxWindow *parent, wxSizer *sizer, wxString const& label) {
if (!label)
sizer->AddStretchSpacer();
else
sizer->Add(new wxStaticText(&d, -1, label), wxSizerFlags().Center().Left());
sizer->Add(new wxStaticText(parent, -1, label), wxSizerFlags().Center().Left());
}
template<typename T>
void DialogDummyVideo::AddCtrl(wxString const& label, T *ctrl) {
add_label(&d, sizer, label);
sizer->Add(ctrl, wxSizerFlags().Expand().Center().Left());
}

View File

@ -38,7 +38,10 @@ class DialogManager {
template<typename Event>
void OnClose(Event &evt) {
evt.Skip();
auto dialog = static_cast<wxWindow *>(evt.GetEventObject());
Destroy(static_cast<wxWindow *>(evt.GetEventObject()));
}
void Destroy(wxWindow *dialog) {
while (!dialog->IsTopLevel()) dialog = dialog->GetParent();
dialog->Destroy();
@ -50,10 +53,9 @@ class DialogManager {
}
}
template<typename T>
std::vector<dialog_pair>::iterator Find() {
std::vector<dialog_pair>::iterator Find(std::type_info const& type) {
for (auto it = begin(created_dialogs); it != end(created_dialogs); ++it) {
if (*it->first == typeid(T))
if (*it->first == type)
return it;
}
return end(created_dialogs);
@ -92,10 +94,10 @@ public:
diag.ShowModal();
}
catch (...) {
created_dialogs.erase(Find<DialogType>());
created_dialogs.erase(Find(typeid(DialogType)));
throw;
}
created_dialogs.erase(Find<DialogType>());
created_dialogs.erase(Find(typeid(DialogType)));
}
/// Get the dialog of the given type
@ -103,7 +105,7 @@ public:
/// @return A pointer to a DialogType or nullptr if no dialog of the given type has been created
template<class DialogType>
DialogType *Get() const {
auto it = const_cast<DialogManager *>(this)->Find<DialogType>();
auto it = const_cast<DialogManager *>(this)->Find(typeid(DialogType));
return it != created_dialogs.end() ? static_cast<DialogType*>(it->second) : nullptr;
}

View File

@ -215,35 +215,40 @@ struct GridColumnActor final : GridColumn {
}
};
template<int Index>
struct GridColumnMargin : GridColumn {
int index;
GridColumnMargin(int index) : index(index) { }
bool Centered() const override { return true; }
wxString Value(const AssDialogue *d, const agi::Context *) const override {
return d->Margin[Index] ? wxString(std::to_wstring(d->Margin[Index])) : wxString();
return d->Margin[index] ? wxString(std::to_wstring(d->Margin[index])) : wxString();
}
int Width(const agi::Context *c, WidthHelper &helper) const override {
int max = 0;
for (AssDialogue const& line : c->ass->Events) {
if (line.Margin[Index] > max)
max = line.Margin[Index];
if (line.Margin[index] > max)
max = line.Margin[index];
}
return max == 0 ? 0 : helper(std::to_wstring(max));
}
};
struct GridColumnMarginLeft final : GridColumnMargin<0> {
struct GridColumnMarginLeft final : GridColumnMargin {
GridColumnMarginLeft() : GridColumnMargin(0) { }
COLUMN_HEADER(_("Left"))
COLUMN_DESCRIPTION(_("Left Margin"))
};
struct GridColumnMarginRight final : GridColumnMargin<1> {
struct GridColumnMarginRight final : GridColumnMargin {
GridColumnMarginRight() : GridColumnMargin(1) { }
COLUMN_HEADER(_("Right"))
COLUMN_DESCRIPTION(_("Right Margin"))
};
struct GridColumnMarginVert final : GridColumnMargin<2> {
struct GridColumnMarginVert final : GridColumnMargin {
GridColumnMarginVert() : GridColumnMargin(2) { }
COLUMN_HEADER(_("Vert"))
COLUMN_DESCRIPTION(_("Vertical Margin"))
};

View File

@ -443,13 +443,9 @@ void SubsEditBox::OnChange(wxStyledTextEvent &event) {
}
}
template<class setter>
void SubsEditBox::SetSelectedRows(setter set, wxString const& desc, int type, bool amend) {
auto const& sel = c->selectionController->GetSelectedSet();
for_each(sel.begin(), sel.end(), set);
void SubsEditBox::Commit(wxString const& desc, int type, bool amend, AssDialogue *line) {
file_changed_slot.Block();
commit_id = c->ass->Commit(desc, type, (amend && desc == last_commit_type) ? commit_id : -1, sel.size() == 1 ? *sel.begin() : nullptr);
commit_id = c->ass->Commit(desc, type, (amend && desc == last_commit_type) ? commit_id : -1, line);
file_changed_slot.Unblock();
last_commit_type = desc;
last_time_commit_type = -1;
@ -457,6 +453,13 @@ void SubsEditBox::SetSelectedRows(setter set, wxString const& desc, int type, bo
undo_timer.Start(30000, wxTIMER_ONE_SHOT);
}
template<class setter>
void SubsEditBox::SetSelectedRows(setter set, wxString const& desc, int type, bool amend) {
auto const& sel = c->selectionController->GetSelectedSet();
for_each(sel.begin(), sel.end(), set);
Commit(desc, type, amend, sel.size() == 1 ? *sel.begin() : nullptr);
}
template<class T>
void SubsEditBox::SetSelectedRows(T AssDialogueBase::*field, T value, wxString const& desc, int type, bool amend) {
SetSelectedRows([&](AssDialogue *d) { d->*field = value; }, desc, type, amend);

View File

@ -110,6 +110,7 @@ class SubsEditBox final : public wxPanel {
/// @brief Commits the current edit box contents
/// @param desc Undo description to use
void CommitText(wxString const& desc);
void Commit(wxString const& desc, int type, bool amend, AssDialogue *line);
/// Last commit ID for undo coalescing
int commit_id = -1;

View File

@ -140,7 +140,7 @@ struct Writer {
// the inline_string encoding grew the data by more than uuencoding would
// so base64 encode it instead
line += "u"; // marker for uuencoding
line += agi::ass::UUEncode(edi.value, false);
line += agi::ass::UUEncode(edi.value.c_str(), edi.value.c_str() + edi.value.size(), false);
} else {
line += "e"; // marker for inline_string encoding (escaping)
line += encoded_data;

View File

@ -168,6 +168,22 @@ bool DoubleSpinValidator::TransferFromWindow() {
return true;
}
int EnumBinderBase::Get() {
if (auto rb = dynamic_cast<wxRadioBox*>(GetWindow()))
return rb->GetSelection();
if (auto rb = dynamic_cast<wxComboBox*>(GetWindow()))
return rb->GetSelection();
throw agi::InternalError("Control type not supported by EnumBinder");
}
void EnumBinderBase::Set(int value) {
if (auto rb = dynamic_cast<wxRadioBox*>(GetWindow()))
rb->SetSelection(value);
else if (auto rb = dynamic_cast<wxComboBox*>(GetWindow()))
rb->SetSelection(value);
throw agi::InternalError("Control type not supported by EnumBinder");
}
bool StringBinder::TransferFromWindow() {
wxWindow *window = GetWindow();
if (wxTextCtrl *ctrl = dynamic_cast<wxTextCtrl*>(window))

View File

@ -71,36 +71,33 @@ public:
DoubleSpinValidator(double *value) : value(value) { }
};
class EnumBinderBase : public wxValidator {
bool Validate(wxWindow *) override { return true; }
protected:
int Get();
void Set(int value);
};
template<typename T>
class EnumBinder final : public wxValidator {
class EnumBinder final : public EnumBinderBase {
T *value;
wxObject *Clone() const override { return new EnumBinder<T>(value); }
bool Validate(wxWindow *) override { return true; }
bool TransferFromWindow() override {
if (auto rb = dynamic_cast<wxRadioBox*>(GetWindow()))
*value = static_cast<T>(rb->GetSelection());
else if (auto rb = dynamic_cast<wxComboBox*>(GetWindow()))
*value = static_cast<T>(rb->GetSelection());
else
throw agi::InternalError("Control type not supported by EnumBinder");
*value = static_cast<T>(Get());
return true;
}
bool TransferToWindow() override {
if (auto rb = dynamic_cast<wxRadioBox*>(GetWindow()))
rb->SetSelection(static_cast<int>(*value));
else if (auto cb = dynamic_cast<wxComboBox*>(GetWindow()))
cb->SetSelection(static_cast<int>(*value));
else
throw agi::InternalError("Control type not supported by EnumBinder");
Set(static_cast<int>(*value));
return true;
}
public:
explicit EnumBinder(T *value) : value(value) { }
EnumBinder(EnumBinder const& rhs) : wxValidator(rhs), value(rhs.value) { }
EnumBinder(EnumBinder const& rhs) : EnumBinderBase(rhs), value(rhs.value) { }
};
template<typename T>

View File

@ -12,6 +12,7 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include <libaegisub/charset_conv.h>
#include <libaegisub/line_iterator.h>
#include <main.h>

View File

@ -24,31 +24,34 @@ using namespace agi::ass;
TEST(lagi_uuencode, short_blobs) {
std::vector<char> data;
auto encode = [&] { return UUEncode(&data[0], &data.back() + 1); };
data.push_back(120);
EXPECT_STREQ("?!", UUEncode(data).c_str());
EXPECT_STREQ("?!", encode().c_str());
data.push_back(121);
EXPECT_STREQ("?(E", UUEncode(data).c_str());
EXPECT_STREQ("?(E", encode().c_str());
data.push_back(122);
EXPECT_STREQ("?(F[", UUEncode(data).c_str());
EXPECT_STREQ("?(F[", encode().c_str());
}
TEST(lagi_uuencode, short_strings) {
std::vector<char> data;
auto decode = [](const char *str) { return UUDecode(str, str + strlen(str)); };
data.push_back(120);
EXPECT_EQ(data, UUDecode("?!"));
EXPECT_EQ(data, decode("?!"));
data.push_back(121);
EXPECT_EQ(data, UUDecode("?(E"));
EXPECT_EQ(data, decode("?(E"));
data.push_back(122);
EXPECT_EQ(data, UUDecode("?(F["));
EXPECT_EQ(data, decode("?(F["));
}
TEST(lagi_uuencode, random_blobs_roundtrip) {
std::vector<char> data;
for (size_t len = 0; len < 200; ++len) {
EXPECT_EQ(data, UUDecode(UUEncode(data)));
auto encoded = UUEncode(data.data(), data.data() + data.size());
EXPECT_EQ(data, UUDecode(encoded.data(), encoded.data() + encoded.size()));
data.push_back(rand());
}
}