// Copyright (c) 2012, Thomas Goyne // // 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 "../config.h" #include "parser.h" #include "libaegisub/color.h" #include "libaegisub/ass/dialogue_parser.h" #include #include #include #include #include #include #include BOOST_FUSION_ADAPT_STRUCT( agi::Color, (unsigned char, r) (unsigned char, g) (unsigned char, b) (unsigned char, a) ) namespace { using namespace boost::spirit; /// Convert a abgr value in an int or unsigned int to an agi::Color struct unpack_colors : public boost::static_visitor { template struct result { typedef agi::Color type; }; template agi::Color operator()(T arg) const { return boost::apply_visitor(*this, arg); } agi::Color operator()(int abgr) const { return (*this)((unsigned)abgr); } agi::Color operator()(unsigned int abgr) const { return agi::Color(abgr & 0xFF, (abgr >> 8) & 0xFF, (abgr >> 16) & 0xFF, (abgr >> 24) & 0xFF); } }; template struct color_grammar : qi::grammar { qi::rule color; qi::rule css_color; qi::rule ass_color; qi::rule rgb_component; qi::rule rgb_percent; qi::rule hex_byte; qi::rule hex_char; qi::rule comma; qi::rule blank; #define HEX_PARSER(type, len) qi::uint_parser() color_grammar() : color_grammar::base_type(color) { color = css_color | ass_color; boost::phoenix::function unpack; // Order is important here; int_ (for SSA) needs to come before the ASS // option as decimal numbers of the appropriate length are also valid // hex numbers // ASS with alpha needs to come before ASS without alpha for the same // reason ass_color = ( int_ | -lit('&') >> -(lit('H') | lit('h')) >> (HEX_PARSER(int, 8) | HEX_PARSER(int, 6)) >> -lit('&') )[_val = unpack(_1)] >> blank >> qi::eoi; css_color = "rgb(" >> blank >> rgb_component >> comma >> rgb_component >> comma >> rgb_component >> blank >> ')' | '#' >> hex_byte >> hex_byte >> hex_byte | '#' >> hex_char >> hex_char >> hex_char ; hex_char = HEX_PARSER(int, 1)[_val = _1 * 16 + _1]; hex_byte = HEX_PARSER(int, 2); rgb_component = qi::uint_parser(); comma = *qi::blank >> "," >> *qi::blank; blank = *qi::blank; } }; template struct dialogue_tokens : lex::lexer { int paren_depth; dialogue_tokens() : paren_depth(0) { using lex::_state; using lex::char_; using lex::string; using namespace boost::phoenix; using namespace agi::ass::DialogueTokenType; this->self = string("\\\\[nNh]", LINE_BREAK) | char_('{', OVR_BEGIN)[ref(paren_depth) = 0, _state = "OVR"] | string(".", TEXT) ; this->self("OVR") = char_('{', ERROR) | char_('}', OVR_END)[_state = "INITIAL"] | char_('\\', TAG_START)[_state = "TAGSTART"] | string("\\s+", WHITESPACE) | string(".", COMMENT) ; this->self("ARG") = char_('{', ERROR) | char_('}', OVR_END)[_state = "INITIAL"] | char_('(', OPEN_PAREN)[++ref(paren_depth)] | char_(')', CLOSE_PAREN)[--ref(paren_depth), if_(ref(paren_depth) == 0)[_state = "OVR"]] | char_('\\', TAG_START)[_state = "TAGSTART"] | char_(',', ARG_SEP) | string("\\s+", WHITESPACE) | string(".", ARG) ; this->self("TAGSTART") = string("\\s+", WHITESPACE) | string("r|fn", TAG_NAME)[_state = "ARG"] | char_('\\', TAG_START) | char_('}', OVR_END)[_state = "INITIAL"] | string("[a-z0-9]", TAG_NAME)[_state = "TAGNAME"] | string(".", COMMENT)[_state = "OVR"] ; this->self("TAGNAME") = string("[a-z]+", TAG_NAME)[_state = "ARG"] | char_('(', OPEN_PAREN)[++ref(paren_depth), _state = "ARG"] | char_(')', CLOSE_PAREN)[--ref(paren_depth), if_(ref(paren_depth) == 0)[_state = "OVR"]] | char_('}', OVR_END)[_state = "INITIAL"] | char_('\\', TAG_START)[_state = "TAGSTART"] | string(".", ARG)[_state = "ARG"] ; } }; } namespace agi { namespace parser { bool parse(Color &dst, std::string const& str) { std::string::const_iterator begin = str.begin(); bool parsed = parse(begin, str.end(), color_grammar(), dst); return parsed && begin == str.end(); } } namespace ass { std::vector TokenizeDialogueBody(std::string const& str) { dialogue_tokens > tokenizer; char const* first = str.c_str(); char const* last = first + str.size(); std::vector data; dialogue_tokens >::iterator_type it = tokenizer.begin(first, last), end = tokenizer.end(); for (; it != end && token_is_valid(*it); ++it) { int id = it->id(); ptrdiff_t len = it->value().end() - it->value().begin(); assert(len > 0); if (data.empty() || data.back().type != id) data.push_back(DialogueToken(id, len)); else data.back().length += len; } return data; } } }