Support MarginL and MarginR in SSA subtitles (DoM #2811). main v1.6.49
authorCarl Hetherington <cth@carlh.net>
Wed, 22 May 2024 21:29:58 +0000 (23:29 +0200)
committerCarl Hetherington <cth@carlh.net>
Wed, 22 May 2024 21:30:01 +0000 (23:30 +0200)
src/exceptions.h
src/ssa_reader.cc
src/ssa_reader.h
src/stl_binary_reader.cc
src/subrip_reader.cc
src/web_vtt_reader.cc
test/data/horizontal_margin.ssa [new file with mode: 0644]
test/ssa_reader_test.cc
test/stl_binary_reader_test.cc

index 1895f110433f88f389193f4a780e8f6148276354..e0d0e90a9a2d6c7a4207bb1e732e9b0415dbe229 100644 (file)
@@ -90,6 +90,15 @@ private:
 };
 
 
+class WebVTTHeaderError : public WebVTTError
+{
+public:
+       WebVTTHeaderError()
+               : WebVTTError("No WEBVTT header found")
+       {}
+};
+
+
 class SSAError : public std::runtime_error
 {
 public:
index 416d317650a5f70a97341c289580d1236d6fae0e..1302b6becaddf141f87c6b862501a04cfcd04865 100644 (file)
@@ -149,6 +149,10 @@ public:
                                }
                        } else if (keys[i] == "MarginV") {
                                vertical_margin = raw_convert<int> (style[i]);
+                       } else if (keys[i] == "MarginL") {
+                               left_margin = raw_convert<int>(style[i]);
+                       } else if (keys[i] == "MarginR") {
+                               right_margin = raw_convert<int>(style[i]);
                        }
                }
        }
@@ -166,6 +170,8 @@ public:
        HorizontalReference horizontal_reference;
        VerticalReference vertical_reference;
        int vertical_margin;
+       int left_margin = 0;
+       int right_margin = 0;
 
 private:
        Colour colour (string c) const
@@ -185,6 +191,24 @@ private:
        }
 };
 
+
+void
+SSAReader::Context::update_horizontal_position(RawSubtitle& sub) const
+{
+       switch (sub.horizontal_position.reference) {
+       case LEFT_OF_SCREEN:
+               sub.horizontal_position.proportional = static_cast<float>(left_margin) / play_res_x;
+               break;
+       case HORIZONTAL_CENTRE_OF_SCREEN:
+               sub.horizontal_position.proportional = static_cast<float>(left_margin - right_margin) / (2 * play_res_x);
+               break;
+       case RIGHT_OF_SCREEN:
+               sub.horizontal_position.proportional = static_cast<float>(right_margin) / play_res_x;
+               break;
+       }
+}
+
+
 Time
 SSAReader::parse_time (string t) const
 {
@@ -199,67 +223,77 @@ SSAReader::parse_time (string t) const
                );
 }
 
+
 void
-SSAReader::parse_style(RawSubtitle& sub, string style, int play_res_x, int play_res_y, Colour primary_colour)
+SSAReader::parse_tag(RawSubtitle& sub, string tag, Context const& context)
 {
-       if (style == "\\i1") {
+       if (tag == "\\i1") {
                sub.italic = true;
-       } else if (style == "\\i0" || style == "\\i") {
+       } else if (tag == "\\i0" || tag == "\\i") {
                sub.italic = false;
-       } else if (style == "\\b1") {
+       } else if (tag == "\\b1") {
                sub.bold = true;
-       } else if (style == "\\b0") {
+       } else if (tag == "\\b0") {
                sub.bold = false;
-       } else if (style == "\\u1") {
+       } else if (tag == "\\u1") {
                sub.underline = true;
-       } else if (style == "\\u0") {
+       } else if (tag == "\\u0") {
                sub.underline = false;
-       } else if (style == "\\an1") {
+       } else if (tag == "\\an1") {
                sub.horizontal_position.reference = sub::LEFT_OF_SCREEN;
                sub.vertical_position.reference = sub::BOTTOM_OF_SCREEN;
-       } else if (style == "\\an2") {
+               context.update_horizontal_position(sub);
+       } else if (tag == "\\an2") {
                sub.horizontal_position.reference = sub::HORIZONTAL_CENTRE_OF_SCREEN;
                sub.vertical_position.reference = sub::BOTTOM_OF_SCREEN;
-       } else if (style == "\\an3") {
+               context.update_horizontal_position(sub);
+       } else if (tag == "\\an3") {
                sub.horizontal_position.reference = sub::RIGHT_OF_SCREEN;
                sub.vertical_position.reference = sub::BOTTOM_OF_SCREEN;
-       } else if (style == "\\an4") {
+               context.update_horizontal_position(sub);
+       } else if (tag == "\\an4") {
                sub.horizontal_position.reference = sub::LEFT_OF_SCREEN;
                sub.vertical_position.reference = sub::VERTICAL_CENTRE_OF_SCREEN;
-       } else if (style == "\\an5") {
+               context.update_horizontal_position(sub);
+       } else if (tag == "\\an5") {
                sub.horizontal_position.reference = sub::HORIZONTAL_CENTRE_OF_SCREEN;
                sub.vertical_position.reference = sub::VERTICAL_CENTRE_OF_SCREEN;
-       } else if (style == "\\an6") {
+               context.update_horizontal_position(sub);
+       } else if (tag == "\\an6") {
                sub.horizontal_position.reference = sub::RIGHT_OF_SCREEN;
                sub.vertical_position.reference = sub::VERTICAL_CENTRE_OF_SCREEN;
-       } else if (style == "\\an7") {
+               context.update_horizontal_position(sub);
+       } else if (tag == "\\an7") {
                sub.horizontal_position.reference = sub::LEFT_OF_SCREEN;
                sub.vertical_position.reference = sub::TOP_OF_SCREEN;
-       } else if (style == "\\an8") {
+               context.update_horizontal_position(sub);
+       } else if (tag == "\\an8") {
                sub.horizontal_position.reference = sub::HORIZONTAL_CENTRE_OF_SCREEN;
                sub.vertical_position.reference = sub::TOP_OF_SCREEN;
-       } else if (style == "\\an9") {
+               context.update_horizontal_position(sub);
+       } else if (tag == "\\an9") {
                sub.horizontal_position.reference = sub::RIGHT_OF_SCREEN;
                sub.vertical_position.reference = sub::TOP_OF_SCREEN;
-       } else if (boost::starts_with(style, "\\pos")) {
+               context.update_horizontal_position(sub);
+       } else if (boost::starts_with(tag, "\\pos")) {
                vector<string> bits;
-               boost::algorithm::split (bits, style, boost::is_any_of("(,"));
+               boost::algorithm::split (bits, tag, boost::is_any_of("(,"));
                SUB_ASSERT (bits.size() == 3);
                sub.horizontal_position.reference = sub::LEFT_OF_SCREEN;
-               sub.horizontal_position.proportional = raw_convert<float>(bits[1]) / play_res_x;
+               sub.horizontal_position.proportional = raw_convert<float>(bits[1]) / context.play_res_x;
                sub.vertical_position.reference = sub::TOP_OF_SCREEN;
-               sub.vertical_position.proportional = raw_convert<float>(bits[2]) / play_res_y;
-       } else if (boost::starts_with(style, "\\fs")) {
-               SUB_ASSERT (style.length() > 3);
-               sub.font_size.set_proportional(raw_convert<float>(style.substr(3)) / play_res_y);
-       } else if (boost::starts_with(style, "\\c")) {
+               sub.vertical_position.proportional = raw_convert<float>(bits[2]) / context.play_res_y;
+       } else if (boost::starts_with(tag, "\\fs")) {
+               SUB_ASSERT (tag.length() > 3);
+               sub.font_size.set_proportional(raw_convert<float>(tag.substr(3)) / context.play_res_y);
+       } else if (boost::starts_with(tag, "\\c")) {
                /* \c&Hbbggrr& */
-               if (style.length() > 2) {
-                       sub.colour = h_colour(style.substr(2, style.length() - 3));
-               } else if (style.length() == 2) {
-                       sub.colour = primary_colour;
+               if (tag.length() > 2) {
+                       sub.colour = h_colour(tag.substr(2, tag.length() - 3));
+               } else if (tag.length() == 2) {
+                       sub.colour = context.primary_colour;
                } else {
-                       throw SSAError(String::compose("Badly formatted colour tag %1", style));
+                       throw SSAError(String::compose("Badly formatted colour tag %1", tag));
                }
        }
 }
@@ -269,17 +303,17 @@ SSAReader::parse_style(RawSubtitle& sub, string style, int play_res_x, int play_
  *  @return List of RawSubtitles to represent line with vertical reference TOP_OF_SUBTITLE.
  */
 vector<RawSubtitle>
-SSAReader::parse_line(RawSubtitle base, string line, int play_res_x, int play_res_y, Colour primary_colour)
+SSAReader::parse_line(RawSubtitle base, string line, Context const& context)
 {
        enum {
                TEXT,
-               STYLE,
+               TAG,
                BACKSLASH
        } state = TEXT;
 
        vector<RawSubtitle> subs;
        RawSubtitle current = base;
-       string style;
+       string tag;
 
        if (!current.vertical_position.reference) {
                current.vertical_position.reference = BOTTOM_OF_SCREEN;
@@ -292,12 +326,14 @@ SSAReader::parse_line(RawSubtitle base, string line, int play_res_x, int play_re
         */
        current.vertical_position.proportional = 0;
 
+       context.update_horizontal_position(current);
+
        /* We must have a font size, as there could be a margin specified
           in pixels and in that case we must know how big the subtitle
           lines are to work out the position on screen.
        */
        if (!current.font_size.proportional()) {
-               current.font_size.set_proportional(72.0 / play_res_y);
+               current.font_size.set_proportional(72.0 / context.play_res_y);
        }
 
        /* Count the number of line breaks */
@@ -311,34 +347,34 @@ SSAReader::parse_line(RawSubtitle base, string line, int play_res_x, int play_re
        }
 
        /* There are vague indications that with ASS 1 point should equal 1 pixel */
-       double const line_size = current.font_size.proportional(play_res_y) * 1.2;
+       double const line_size = current.font_size.proportional(context.play_res_y) * 1.2;
 
        for (size_t i = 0; i < line.length(); ++i) {
                char const c = line[i];
                switch (state) {
                case TEXT:
                        if (c == '{') {
-                               state = STYLE;
+                               state = TAG;
                        } else if (c == '\\') {
                                state = BACKSLASH;
                        } else if (c != '\r' && c != '\n') {
                                current.text += c;
                        }
                        break;
-               case STYLE:
+               case TAG:
                        if (c == '}' || c == '\\') {
                                if (!current.text.empty ()) {
                                        subs.push_back (current);
                                        current.text = "";
                                }
-                               parse_style(current, style, play_res_x, play_res_y, primary_colour);
-                               style = "";
+                               parse_tag(current, tag, context);
+                               tag = "";
                        }
 
                        if (c == '}') {
                                state = TEXT;
                        } else {
-                               style += c;
+                               tag += c;
                        }
                        break;
                case BACKSLASH:
@@ -476,6 +512,8 @@ SSAReader::read (function<optional<string> ()> get_line)
 
                                RawSubtitle sub;
                                optional<Style> style;
+                               int left_margin = 0;
+                               int right_margin = 0;
 
                                for (size_t i = 0; i < event.size(); ++i) {
                                        trim (event[i]);
@@ -511,13 +549,24 @@ SSAReader::read (function<optional<string> ()> get_line)
                                                if (sub.vertical_position.reference != sub::VERTICAL_CENTRE_OF_SCREEN) {
                                                        sub.vertical_position.proportional = float(style->vertical_margin) / play_res_y;
                                                }
+                                               left_margin = style->left_margin;
+                                               right_margin = style->right_margin;
                                        } else if (event_format[i] == "MarginV") {
                                                if (event[i] != "0" && sub.vertical_position.reference != sub::VERTICAL_CENTRE_OF_SCREEN) {
                                                        /* Override the style if its non-zero */
                                                        sub.vertical_position.proportional = raw_convert<float>(event[i]) / play_res_y;
                                                }
+                                       } else if (event_format[i] == "MarginL") {
+                                               if (event[i] != "0") {
+                                                       left_margin = raw_convert<int>(event[i]);
+                                               }
+                                       } else if (event_format[i] == "MarginR") {
+                                               if (event[i] != "0") {
+                                                       right_margin = raw_convert<int>(event[i]);
+                                               }
                                        } else if (event_format[i] == "Text") {
-                                               for (auto j: parse_line(sub, event[i], play_res_x, play_res_y, style ? style->primary_colour : Colour(1, 1, 1))) {
+                                               auto context = Context(play_res_x, play_res_y, style ? style->primary_colour : Colour(1, 1, 1), left_margin, right_margin);
+                                               for (auto j: parse_line(sub, event[i], context)) {
                                                        _subs.push_back (j);
                                                }
                                        }
index 45cc271382b74c4bff37de442ffab01a6e38a8f7..e8c2a521f7e6e7c51cd23cc4e6cf6c893e12a4e6 100644 (file)
@@ -41,8 +41,28 @@ public:
        SSAReader (FILE* f);
        SSAReader (std::string subs);
 
-       static std::vector<RawSubtitle> parse_line(RawSubtitle base, std::string line, int play_res_x, int play_res_y, Colour primary_colour);
-       static void parse_style(RawSubtitle& sub, std::string style, int play_res_x, int play_res_y, Colour primary_colour);
+       class Context
+       {
+       public:
+               Context(int play_res_x_, int play_res_y_, Colour primary_colour_, int left_margin_ = 0, int right_margin_ = 0)
+                       : play_res_x(play_res_x_)
+                       , play_res_y(play_res_y_)
+                       , primary_colour(primary_colour_)
+                       , left_margin(left_margin_)
+                       , right_margin(right_margin_)
+               {}
+
+               int play_res_x;
+               int play_res_y;
+               Colour primary_colour;
+               int left_margin;
+               int right_margin;
+
+               void update_horizontal_position(RawSubtitle& sub) const;
+       };
+
+       static std::vector<RawSubtitle> parse_line(RawSubtitle base, std::string line, Context const& context);
+       static void parse_tag(RawSubtitle& sub, std::string style, Context const& context);
 
 private:
        void read (boost::function<boost::optional<std::string> ()> get_line);
index 35091f083cfbcddebcbdcb12af77595a7289d35f..7b38065b1aac637325c39f12fc80f16e5b64a5bb 100644 (file)
@@ -248,14 +248,46 @@ void STLBinaryReader::read (shared_ptr<InputReader> reader)
                                        break;
                                }
 
-                               if (c >= 0x80 && c <= 0x83) {
-                                       /* Italic or underline control code */
+                               if (c <= 0x07 || (c >= 0x80 && c <= 0x83)) {
+                                       /* Colour, italic or underline control code */
                                        sub.text = utf_to_utf<char> (iso6937_to_utf16 (text.c_str()));
                                        _subs.push_back (sub);
                                        text.clear ();
                                }
 
                                switch (c) {
+                               case 0x0:
+                                       /* Black */
+                                       sub.colour = Colour(0, 0, 0);
+                                       break;
+                               case 0x1:
+                                       /* Red */
+                                       sub.colour = Colour(1, 0, 0);
+                                       break;
+                               case 0x2:
+                                       /* Lime */
+                                       sub.colour = Colour(0, 1, 0);
+                                       break;
+                               case 0x3:
+                                       /* Yellow */
+                                       sub.colour = Colour(1, 1, 0);
+                                       break;
+                               case 0x4:
+                                       /* Blue */
+                                       sub.colour = Colour(0, 0, 1);
+                                       break;
+                               case 0x5:
+                                       /* Magenta */
+                                       sub.colour = Colour(1, 0, 1);
+                                       break;
+                               case 0x6:
+                                       /* Cyan */
+                                       sub.colour = Colour(0, 1, 1);
+                                       break;
+                               case 0x7:
+                                       /* White */
+                                       sub.colour = Colour(1, 1, 1);
+                                       break;
                                case 0x80:
                                        italic = true;
                                        break;
index c0ef21c5848586c3f8b4e0a717f695002dd97cdc..3f68025f8bfe2dd99b2a2cf68ddbdfee21900e87 100644 (file)
@@ -309,7 +309,7 @@ SubripReader::convert_line (string t, RawSubtitle& p)
                                ++i;
                        }
                        ++i;
-                       SSAReader::parse_style (p, ssa, 288, 288, Colour(1, 1, 1));
+                       SSAReader::parse_tag(p, ssa, SSAReader::Context{288, 288, Colour(1, 1, 1)});
                } else {
                        p.text += t[i];
                        ++i;
index cd6844de48d45ee6e63ea8fee55e53f451b91975..4618466575fc471f3cc367948f3ef5b5fbdba554 100644 (file)
@@ -86,7 +86,7 @@ WebVTTReader::read(std::function<optional<string> ()> get_line)
                switch (state) {
                case State::HEADER:
                        if (!boost::starts_with(*line, "WEBVTT")) {
-                               throw WebVTTError("No WEBVTT header found");
+                               throw WebVTTHeaderError();
                        }
                        state = State::DATA;
                        break;
diff --git a/test/data/horizontal_margin.ssa b/test/data/horizontal_margin.ssa
new file mode 100644 (file)
index 0000000..c5a9f9e
--- /dev/null
@@ -0,0 +1,23 @@
+[Script Info]
+Title: libsub test
+ScriptType: v4.00
+WrapStyle: 0
+ScaledBorderAndShadow: yes
+PlayResX: 1920
+PlayResY: 1080
+
+[V4 Styles]
+Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding
+Style: Default,Arial,20,16777215,255,0,0,0,0,1,2,2,2,200,100,10,0,1
+
+[Fonts]
+
+[Graphics]
+
+[Events]
+Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
+Dialogue: Marked=0,0:00:01.23,0:00:04.55,Default,,100,0,0,,Hello world
+Dialogue: Marked=0,0:01:01.23,0:01:04.55,Default,,10,0,0,,Left of centre
+Dialogue: Marked=0,0:02:01.23,0:02:04.55,Default,,10,0,0,,{\an4}Left
+Dialogue: Marked=0,0:03:01.23,0:03:04.55,Default,,10,0,0,,{\an6}Right
+Dialogue: Marked=0,0:04:01.23,0:04:04.55,Default,,0,100,0,,{\an4}Style left
index 71eb03a6b05c0d09a30a49d04b0b997ea34789e2..8820bf41df1ccab2bd0f727f88d1eb1f236cf5ad 100644 (file)
@@ -36,13 +36,20 @@ using std::string;
 using std::vector;
 
 
+static
+vector<sub::Subtitle>
+read_file(boost::filesystem::path filename)
+{
+       auto file = fopen(filename.string().c_str(), "r");
+       BOOST_REQUIRE(file);
+       sub::SSAReader reader(file);
+       fclose(file);
+       return sub::collect<vector<sub::Subtitle>>(reader.subtitles());
+}
+
 BOOST_AUTO_TEST_CASE (ssa_reader_test)
 {
-       boost::filesystem::path p = private_test / "example.ssa";
-       FILE* f = fopen (p.string().c_str(), "r");
-       sub::SSAReader reader (f);
-       fclose (f);
-       auto subs = sub::collect<vector<sub::Subtitle>> (reader.subtitles());
+       auto subs = read_file(private_test / "example.ssa");
 
        auto i = subs.begin ();
 
@@ -91,8 +98,7 @@ BOOST_AUTO_TEST_CASE (ssa_reader_line_test1)
        auto r = sub::SSAReader::parse_line (
                base,
                "This is a line with some {\\i1}italics{\\i0} and then\\nthere is a new line.",
-               1920, 1080,
-               sub::Colour(1, 1, 1)
+               sub::SSAReader::Context(1920, 1080, sub::Colour(1, 1, 1))
                );
 
        auto i = r.begin();
@@ -123,8 +129,7 @@ BOOST_AUTO_TEST_CASE (ssa_reader_line_test2)
        auto r = sub::SSAReader::parse_line (
                base,
                "{\\i1}It's all just italics{\\i0}",
-               1920, 1080,
-               sub::Colour(1, 1, 1)
+               sub::SSAReader::Context(1920, 1080, sub::Colour(1, 1, 1))
                );
 
        /* Convert a font size in points to a vertical position for this file */
@@ -141,8 +146,7 @@ BOOST_AUTO_TEST_CASE (ssa_reader_line_test2)
        r = sub::SSAReader::parse_line (
                base,
                "{\\i1}Italic{\\i0}\\Nand new line",
-               1920, 1080,
-               sub::Colour(1, 1, 1)
+               sub::SSAReader::Context(1920, 1080, sub::Colour(1, 1, 1))
                );
 
        i = r.begin ();
@@ -159,11 +163,7 @@ BOOST_AUTO_TEST_CASE (ssa_reader_line_test2)
 static void
 test (boost::filesystem::path p)
 {
-       p = private_test / p;
-       FILE* f = fopen (p.string().c_str(), "r");
-       BOOST_REQUIRE (f);
-       sub::SSAReader r (f);
-       fclose (f);
+       read_file(private_test / p);
 }
 
 /** Test of reading some typical .ssa files */
@@ -209,11 +209,7 @@ BOOST_AUTO_TEST_CASE (ssa_reader_test2)
 /** Test reading of a file within the libsub tree which exercises the parser */
 BOOST_AUTO_TEST_CASE (ssa_reader_test3)
 {
-       boost::filesystem::path p = "test/data/test.ssa";
-       FILE* f = fopen (p.string().c_str(), "r");
-       sub::SSAReader reader (f);
-       fclose (f);
-       auto subs = sub::collect<vector<sub::Subtitle>> (reader.subtitles());
+       auto subs = read_file("test/data/test.ssa");
 
        /* Convert a font size in points to a proportional size for this file */
        auto fs = [](int x) {
@@ -258,7 +254,7 @@ BOOST_AUTO_TEST_CASE (ssa_reader_test3)
        /* Alignments */
 
        SUB_START (sub::Time::from_hms (0, 0, 9, 230), sub::Time::from_hms (0, 0, 11, 560));
-       LINE ((10.0 / 1080), sub::BOTTOM_OF_SCREEN, 0, sub::LEFT_OF_SCREEN);
+       LINE ((10.0 / 1080), sub::BOTTOM_OF_SCREEN, (10.0 / 1920), sub::LEFT_OF_SCREEN);
        BLOCK("bottom left", "Arial", fs(20), false, false, false);
        SUB_END ();
 
@@ -268,13 +264,13 @@ BOOST_AUTO_TEST_CASE (ssa_reader_test3)
        SUB_END ();
 
        SUB_START (sub::Time::from_hms (0, 0, 9, 250), sub::Time::from_hms (0, 0, 11, 560));
-       LINE ((10.0 / 1080), sub::BOTTOM_OF_SCREEN, 0, sub::RIGHT_OF_SCREEN);
+       LINE ((10.0 / 1080), sub::BOTTOM_OF_SCREEN, (10.0 / 1920), sub::RIGHT_OF_SCREEN);
        BLOCK("bottom right", "Arial", fs(20), false, false, false);
        SUB_END ();
 
        SUB_START (sub::Time::from_hms (0, 0, 9, 260), sub::Time::from_hms (0, 0, 11, 560));
        /* Position is half of a 20pt line (with line spacing) above vertical centre */
-       LINE (-vp(10), sub::VERTICAL_CENTRE_OF_SCREEN, 0, sub::LEFT_OF_SCREEN);
+       LINE (-vp(10), sub::VERTICAL_CENTRE_OF_SCREEN, (10.0 / 1920), sub::LEFT_OF_SCREEN);
        BLOCK("middle left", "Arial", fs(20), false, false, false);
        SUB_END ();
 
@@ -284,12 +280,12 @@ BOOST_AUTO_TEST_CASE (ssa_reader_test3)
        SUB_END ();
 
        SUB_START (sub::Time::from_hms (0, 0, 9, 280), sub::Time::from_hms (0, 0, 11, 560));
-       LINE (-vp(10), sub::VERTICAL_CENTRE_OF_SCREEN, 0, sub::RIGHT_OF_SCREEN);
+       LINE (-vp(10), sub::VERTICAL_CENTRE_OF_SCREEN, (10.0 / 1920), sub::RIGHT_OF_SCREEN);
        BLOCK("middle right", "Arial", fs(20), false, false, false);
        SUB_END ();
 
        SUB_START (sub::Time::from_hms (0, 0, 9, 290), sub::Time::from_hms (0, 0, 11, 560));
-       LINE ((10.0 / 1080), sub::TOP_OF_SCREEN, 0, sub::LEFT_OF_SCREEN);
+       LINE ((10.0 / 1080), sub::TOP_OF_SCREEN, (10.0 / 1920), sub::LEFT_OF_SCREEN);
        BLOCK("top left", "Arial", fs(20), false, false, false);
        SUB_END ();
 
@@ -299,7 +295,7 @@ BOOST_AUTO_TEST_CASE (ssa_reader_test3)
        SUB_END ();
 
        SUB_START (sub::Time::from_hms (0, 0, 9, 310), sub::Time::from_hms (0, 0, 11, 560));
-       LINE ((10.0 / 1080), sub::TOP_OF_SCREEN, 0, sub::RIGHT_OF_SCREEN);
+       LINE ((10.0 / 1080), sub::TOP_OF_SCREEN, (10.0 / 1920), sub::RIGHT_OF_SCREEN);
        BLOCK("top right", "Arial", fs(20), false, false, false);
        SUB_END ();
 
@@ -310,11 +306,7 @@ BOOST_AUTO_TEST_CASE (ssa_reader_test3)
 /** Test reading of a file within the libsub-test-private tree which exercises the parser */
 BOOST_AUTO_TEST_CASE (ssa_reader_test4)
 {
-       boost::filesystem::path p = private_test / "dcpsubtest2-en.ssa";
-       FILE* f = fopen (p.string().c_str(), "r");
-       sub::SSAReader reader (f);
-       fclose (f);
-       auto subs = sub::collect<vector<sub::Subtitle>> (reader.subtitles());
+       auto subs = read_file(private_test / "dcpsubtest2-en.ssa");
 
        auto i = subs.begin();
        vector<sub::Line>::iterator j;
@@ -369,11 +361,7 @@ BOOST_AUTO_TEST_CASE (ssa_reader_test4)
 /** Test reading of a .ass file */
 BOOST_AUTO_TEST_CASE (ssa_reader_test5)
 {
-       boost::filesystem::path p = private_test / "dcpsubtest-en.ass";
-       FILE* f = fopen (p.string().c_str(), "r");
-       sub::SSAReader reader (f);
-       fclose (f);
-       auto subs = sub::collect<vector<sub::Subtitle>> (reader.subtitles());
+       auto subs = read_file(private_test / "dcpsubtest-en.ass");
 
        /* Convert a font size in points to a proportional size for this file */
        auto fs = [](int x) {
@@ -428,12 +416,7 @@ BOOST_AUTO_TEST_CASE (ssa_reader_test5)
 /** Test reading of another .ass file */
 BOOST_AUTO_TEST_CASE (ssa_reader_test6)
 {
-       boost::filesystem::path p = private_test / "DCP-o-matic_test_subs_1.ass";
-       auto f = fopen (p.string().c_str(), "r");
-       BOOST_REQUIRE (f);
-       sub::SSAReader reader (f);
-       fclose (f);
-       auto subs = sub::collect<vector<sub::Subtitle>> (reader.subtitles());
+       auto subs = read_file(private_test / "DCP-o-matic_test_subs_1.ass");
 
        /* Convert a font size in points to a proportional size for this file */
        auto fs = [](int x) {
@@ -521,12 +504,7 @@ BOOST_AUTO_TEST_CASE (ssa_reader_test6)
 
 BOOST_AUTO_TEST_CASE (ssa_reader_test7)
 {
-       auto p = boost::filesystem::path("test") / "data" / "test3.ssa";
-       auto f = fopen(p.string().c_str(), "r");
-       BOOST_REQUIRE(f);
-       sub::SSAReader reader(f);
-       fclose(f);
-       auto subs = sub::collect<vector<sub::Subtitle>>(reader.subtitles());
+       auto subs = read_file("test/data/test3.ssa");
 
        /* Convert a font size in points to a proportional size for this file */
        auto fs = [](int x) {
@@ -584,11 +562,7 @@ BOOST_AUTO_TEST_CASE (ssa_reader_test7)
 /** Test \pos */
 BOOST_AUTO_TEST_CASE (ssa_reader_pos)
 {
-       boost::filesystem::path p = "test/data/test2.ssa";
-       FILE* f = fopen (p.string().c_str(), "r");
-       sub::SSAReader reader (f);
-       fclose (f);
-       auto subs = sub::collect<vector<sub::Subtitle>> (reader.subtitles());
+       auto subs = read_file("test/data/test2.ssa");
 
        /* Convert a font size in points to a proportional size for this file */
        auto fs = [](int x) {
@@ -616,8 +590,7 @@ BOOST_AUTO_TEST_CASE (ssa_reader_fs)
        auto r = sub::SSAReader::parse_line (
                base,
                "This is a line with some {\\fs64}font sizing.",
-               1920, 1080,
-               sub::Colour(1, 1, 1)
+               sub::SSAReader::Context(1920, 1080, sub::Colour(1, 1, 1))
                );
 
        auto i = r.begin ();
@@ -640,8 +613,7 @@ test_c(string command, string colour)
        auto r = sub::SSAReader::parse_line (
                base,
                String::compose("{\\c%1}Hello world", command),
-               1920, 1080,
-               sub::Colour(1, 0, 1)
+               sub::SSAReader::Context(1920, 1080, sub::Colour(1, 0, 1))
                );
 
        auto i = r.begin ();
@@ -665,3 +637,37 @@ BOOST_AUTO_TEST_CASE (ssa_reader_c)
        test_c("", "ff00ff");
 }
 
+
+BOOST_AUTO_TEST_CASE(ssa_reader_horizontal_margin)
+{
+       auto subs = read_file("test/data/horizontal_margin.ssa");
+       BOOST_REQUIRE_EQUAL(subs.size(), 5U);
+
+       int n = 0;
+
+       BOOST_REQUIRE_EQUAL(subs[n].lines.size(), 1U);
+       BOOST_CHECK(subs[n].lines[0].horizontal_position.reference == sub::HORIZONTAL_CENTRE_OF_SCREEN);
+       BOOST_CHECK_CLOSE(subs[n].lines[0].horizontal_position.proportional, 0, 1);
+       ++n;
+
+       BOOST_REQUIRE_EQUAL(subs[n].lines.size(), 1U);
+       BOOST_CHECK(subs[n].lines[0].horizontal_position.reference == sub::HORIZONTAL_CENTRE_OF_SCREEN);
+       BOOST_CHECK_CLOSE(subs[n].lines[0].horizontal_position.proportional, -90.0f / (2 * 1920.0f), 1);
+       ++n;
+
+       BOOST_REQUIRE_EQUAL(subs[n].lines.size(), 1U);
+       BOOST_CHECK(subs[n].lines[0].horizontal_position.reference == sub::LEFT_OF_SCREEN);
+       BOOST_CHECK_CLOSE(subs[n].lines[0].horizontal_position.proportional, 10.0f / 1920.f, 1);
+       ++n;
+
+       BOOST_REQUIRE_EQUAL(subs[n].lines.size(), 1U);
+       BOOST_CHECK(subs[n].lines[0].horizontal_position.reference == sub::RIGHT_OF_SCREEN);
+       BOOST_CHECK_CLOSE(subs[n].lines[0].horizontal_position.proportional, 100.0f / 1920.f, 1);
+       ++n;
+
+       BOOST_REQUIRE_EQUAL(subs[n].lines.size(), 1U);
+       BOOST_CHECK(subs[n].lines[0].horizontal_position.reference == sub::LEFT_OF_SCREEN);
+       BOOST_CHECK_CLOSE(subs[n].lines[0].horizontal_position.proportional, 200.0f / 1920.f, 1);
+       ++n;
+}
+
index 6566c5f161e0d0ff2b77f672c95cf3c6bb55d5d6..a0469d009796d5e1f1847d0a9ccd24532c0a86bd 100644 (file)
@@ -17,6 +17,7 @@
 
 */
 
+
 #include "stl_binary_reader.h"
 #include "subtitle.h"
 #include "test.h"
 #include <boost/test/unit_test.hpp>
 #include <fstream>
 
+
 using std::ifstream;
+using std::make_shared;
 using std::ofstream;
 using std::shared_ptr;
-using std::make_shared;
+using std::vector;
+
 
 /* Test reading of a binary STL file */
 BOOST_AUTO_TEST_CASE (stl_binary_reader_test1)
@@ -94,3 +98,36 @@ BOOST_AUTO_TEST_CASE (stl_binary_reader_test3)
        }
 }
 
+
+BOOST_AUTO_TEST_CASE(stl_binary_reader_with_colour)
+{
+       auto path = private_test / "at.stl";
+       ifstream in(path.string().c_str());
+       auto reader = make_shared<sub::STLBinaryReader>(in);
+
+       vector<sub::Colour> reference = {
+               { 1, 1, 1 },
+               { 1, 0, 1 },
+               { 1, 1, 1 },
+               { 1, 0, 1 },
+               { 1, 1, 1 },
+               { 1, 1, 0 },
+               { 1, 1, 1 },
+               { 1, 1, 0 },
+               { 1, 1, 1 },
+               { 1, 1, 0 },
+               { 1, 1, 1 },
+               { 1, 1, 0 },
+               { 1, 1, 1 },
+               { 1, 1, 0 }
+       };
+
+       /* Check the first few lines */
+       auto subs = reader->subtitles();
+       BOOST_REQUIRE(subs.size() >= reference.size());
+
+       for (size_t i = 0; i < reference.size(); ++i) {
+               BOOST_CHECK(subs[i].colour == reference[i]);
+       }
+}
+