// Copyright (c) 2005, Rodrigo Braz Monteiro // 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/ /// @file ass_dialogue.cpp /// @brief Class for dialogue lines in subtitles /// @ingroup subs_storage #include "config.h" #include "ass_dialogue.h" #include "subtitle_format.h" #include "utils.h" #include #include #include #include #include #include #include #include #include using namespace boost::adaptors; static int next_id = 0; AssDialogue::AssDialogue() : Id(++next_id) , Comment(false) , Layer(0) , Start(0) , End(5000) , Style("Default") { memset(Margin, 0, sizeof Margin); } AssDialogue::AssDialogue(AssDialogue const& that) : Id(++next_id) , Comment(that.Comment) , Layer(that.Layer) , Start(that.Start) , End(that.End) , Style(that.Style) , Actor(that.Actor) , Effect(that.Effect) , Text(that.Text) { memmove(Margin, that.Margin, sizeof Margin); } AssDialogue::AssDialogue(std::string const& data) : Id(++next_id) { Parse(data); } AssDialogue::~AssDialogue () { } class tokenizer { agi::StringRange str; boost::split_iterator pos; public: tokenizer(agi::StringRange const& str) : str(str) , pos(agi::Split(str, ',')) { } agi::StringRange next_tok() { if (pos.eof()) throw SubtitleFormatParseError("Failed parsing line: " + std::string(str.begin(), str.end()), 0); return *pos++; } std::string next_str() { return agi::str(next_tok()); } std::string next_str_trim() { return agi::str(boost::trim_copy(next_tok())); } }; void AssDialogue::Parse(std::string const& raw) { agi::StringRange str; if (boost::starts_with(raw, "Dialogue:")) { Comment = false; str = agi::StringRange(raw.begin() + 10, raw.end()); } else if (boost::starts_with(raw, "Comment:")) { Comment = true; str = agi::StringRange(raw.begin() + 9, raw.end()); } else throw SubtitleFormatParseError("Failed parsing line: " + raw, 0); tokenizer tkn(str); // Get first token and see if it has "Marked=" in it auto tmp = tkn.next_str_trim(); bool ssa = boost::istarts_with(tmp, "marked="); // Get layer number if (ssa) Layer = 0; else Layer = boost::lexical_cast(tmp); Start = tkn.next_str_trim(); End = tkn.next_str_trim(); Style = tkn.next_str_trim(); Actor = tkn.next_str_trim(); for (int& margin : Margin) margin = mid(0, boost::lexical_cast(tkn.next_str()), 9999); Effect = tkn.next_str_trim(); Text = std::string(tkn.next_tok().begin(), str.end()); } void append_int(std::string &str, int v) { boost::spirit::karma::generate(back_inserter(str), boost::spirit::karma::int_, v); str += ','; } void append_str(std::string &out, std::string const& str) { out += str; out += ','; } void append_unsafe_str(std::string &out, std::string const& str) { if (str.find(',') == str.npos) out += str; else out += boost::replace_all_copy(str, ",", ";"); out += ','; } std::string AssDialogue::GetData(bool ssa) const { std::string str = Comment ? "Comment: " : "Dialogue: "; str.reserve(51 + Style.get().size() + Actor.get().size() + Effect.get().size() + Text.get().size()); if (ssa) append_str(str, "Marked=0"); else append_int(str, Layer); append_str(str, Start.GetAssFormated()); append_str(str, End.GetAssFormated()); append_unsafe_str(str, Style); append_unsafe_str(str, Actor); for (int i = 0; i < 3; ++i) append_int(str, Margin[i]); append_unsafe_str(str, Effect); str += Text.get(); if (str.find('\n') != str.npos || str.find('\r') != str.npos) { boost::replace_all(str, "\n", ""); boost::replace_all(str, "\r", ""); } return str; } const std::string AssDialogue::GetEntryData() const { return GetData(false); } std::string AssDialogue::GetSSAText() const { return GetData(true); } std::auto_ptr> AssDialogue::ParseTags() const { boost::ptr_vector Blocks; // Empty line, make an empty block if (Text.get().empty()) { Blocks.push_back(new AssDialogueBlockPlain); return Blocks.release(); } int drawingLevel = 0; std::string const& text(Text.get()); for (size_t len = text.size(), cur = 0; cur < len; ) { // Overrides block if (text[cur] == '{') { size_t end = text.find('}', cur); // VSFilter requires that override blocks be closed, while libass // does not. We match VSFilter here. if (end == std::string::npos) goto plain; ++cur; // Get contents of block std::string work = text.substr(cur, end - cur); cur = end + 1; if (work.size() && work.find('\\') == std::string::npos) { //We've found an override block with no backslashes //We're going to assume it's a comment and not consider it an override block Blocks.push_back(new AssDialogueBlockComment(work)); } else { // Create block AssDialogueBlockOverride *block = new AssDialogueBlockOverride(work); block->ParseTags(); Blocks.push_back(block); // Look for \p in block for (auto const& tag : block->Tags) { if (tag.Name == "\\p") drawingLevel = tag.Params[0].Get(0); } } continue; } // Plain-text/drawing block plain: std::string work; size_t end = text.find('{', cur + 1); if (end == std::string::npos) { work = text.substr(cur); cur = len; } else { work = text.substr(cur, end - cur); cur = end; } if (drawingLevel == 0) Blocks.push_back(new AssDialogueBlockPlain(work)); else Blocks.push_back(new AssDialogueBlockDrawing(work, drawingLevel)); } return Blocks.release(); } void AssDialogue::StripTags() { Text = GetStrippedText(); } static std::string get_text(AssDialogueBlock &d) { return d.GetText(); } void AssDialogue::UpdateText(boost::ptr_vector& blocks) { if (blocks.empty()) return; Text = join(blocks | transformed(get_text), ""); } void AssDialogue::SetMarginString(std::string const& origvalue, int which) { if (which < 0 || which > 2) throw InvalidMarginIdError(); Margin[which] = mid(0, atoi(origvalue.c_str()), 9999); } std::string AssDialogue::GetMarginString(int which) const { if (which < 0 || which > 2) throw InvalidMarginIdError(); return std::to_string(Margin[which]); } bool AssDialogue::CollidesWith(const AssDialogue *target) const { if (!target) return false; return ((Start < target->Start) ? (target->Start < End) : (Start < target->End)); } static std::string get_text_p(AssDialogueBlock *d) { return d->GetText(); } std::string AssDialogue::GetStrippedText() const { boost::ptr_vector blocks(ParseTags()); return join(blocks | agi::of_type() | transformed(get_text_p), ""); } AssEntry *AssDialogue::Clone() const { AssDialogue *clone = new AssDialogue(*this); *const_cast(&clone->Id) = Id; return clone; } void AssDialogueBlockDrawing::TransformCoords(int mx, int my, double x, double y) { // HACK: Implement a proper parser ffs!! // Could use Spline but it'd be slower and this seems to work fine bool is_x = true; std::string final; for (auto const& cur : agi::Split(text, ' ')) { if (std::all_of(begin(cur), end(cur), isdigit)) { int val = boost::lexical_cast(agi::str(cur)); if (is_x) val = (int)((val + mx) * x + .5); else val = (int)((val + my) * y + .5); final += std::to_string(val); final += ' '; } else if (cur.size() == 1) { char c = tolower(cur[0]); if (c == 'm' || c == 'n' || c == 'l' || c == 'b' || c == 's' || c == 'p' || c == 'c') { is_x = true; final += c; final += ' '; } } } // Write back final final.pop_back(); text = final; }