Basic and slightly inaccurate support for <Space> in subtitles (#2103).
authorCarl Hetherington <cth@carlh.net>
Fri, 15 Oct 2021 20:25:42 +0000 (22:25 +0200)
committerCarl Hetherington <cth@carlh.net>
Sun, 17 Oct 2021 13:34:02 +0000 (15:34 +0200)
Adding horizontal space to a Pango layout is hard, and I think this
change probably gets it slightly wrong, but it's a step in the right
direction.

cscript
src/lib/reel_writer.cc
src/lib/render_text.cc
src/lib/render_text.h
src/lib/text_decoder.cc
src/lib/util.cc
test/render_subtitles_test.cc
wscript

diff --git a/cscript b/cscript
index 773b80d54e1ab46a87a56c1d59280edb435883c6..1d4107c025c9f6b6271d223d6e3b744a10f7b0c7 100644 (file)
--- a/cscript
+++ b/cscript
@@ -431,8 +431,8 @@ def dependencies(target, options):
         # Use distro-provided FFmpeg on Arch
         deps = []
 
-    deps.append(('libdcp', 'v1.8.2'))
-    deps.append(('libsub', 'v1.6.2'))
+    deps.append(('libdcp', 'v1.8.3'))
+    deps.append(('libsub', 'v1.6.3'))
     deps.append(('leqm-nrt', '93ae9e6'))
     deps.append(('rtaudio', 'f619b76'))
     # We get our OpenSSL libraries from the environment, but we
index 44da409a126f99ab07cba60253da7b882a9c5a88..78148d18f1fb4b6c301b59269f177124d1da253e 100644 (file)
@@ -839,7 +839,8 @@ ReelWriter::empty_text_asset (TextType type, optional<DCPTextTrack> track, bool
                                        dcp::Effect::NONE,
                                        dcp::Colour(),
                                        dcp::Time(),
-                                       dcp::Time()
+                                       dcp::Time(),
+                                       0
                                        )
                               );
                }
index 989bc7dfea7d33e83c575057d700c3d9f482fcc6..bcf04147a1af14496ed187e5824bbf867a70da93 100644 (file)
@@ -83,27 +83,53 @@ setup_layout (Glib::RefPtr<Pango::Layout> layout, string font_name, string marku
 
 
 string
-marked_up (list<StringText> subtitles, int target_height, float fade_factor)
+marked_up (list<StringText> subtitles, int target_height, float fade_factor, string font_name)
 {
-       string out;
+       auto constexpr pixels_to_1024ths_point = 72 * 1024 / 96;
 
-       for (auto const& i: subtitles) {
-               out += "<span ";
-               if (i.italic()) {
-                       out += "style=\"italic\" ";
+       auto make_span = [target_height, fade_factor](StringText const& subtitle, string text, string extra_attribute) {
+               string span;
+               span += "<span ";
+               if (subtitle.italic()) {
+                       span += "style=\"italic\" ";
                }
-               if (i.bold()) {
-                       out += "weight=\"bold\" ";
+               if (subtitle.bold()) {
+                       span += "weight=\"bold\" ";
                }
-               if (i.underline()) {
-                       out += "underline=\"single\" ";
+               if (subtitle.underline()) {
+                       span += "underline=\"single\" ";
                }
-               out += "size=\"" + dcp::raw_convert<string>(i.size_in_pixels(target_height) * 72 * 1024 / 96) + "\" ";
+               span += "size=\"" + dcp::raw_convert<string>(subtitle.size_in_pixels(target_height) * pixels_to_1024ths_point) + "\" ";
                /* Between 1-65535 inclusive, apparently... */
-               out += "alpha=\"" + dcp::raw_convert<string>(int(floor(fade_factor * 65534)) + 1) + "\" ";
-               out += "color=\"#" + i.colour().to_rgb_string() + "\">";
-               out += i.text();
-               out += "</span>";
+               span += "alpha=\"" + dcp::raw_convert<string>(int(floor(fade_factor * 65534)) + 1) + "\" ";
+               span += "color=\"#" + subtitle.colour().to_rgb_string() + "\"";
+               if (!extra_attribute.empty()) {
+                       span += " " + extra_attribute;
+               }
+               span += ">";
+               span += text;
+               span += "</span>";
+               return span;
+       };
+
+       string out;
+       for (auto const& i: subtitles) {
+               if (std::abs(i.space_before()) > dcp::SPACE_BEFORE_EPSILON) {
+                       /* We need to insert some horizontal space into the layout.  The only way I can find to do this
+                        * is to write a " " with some special letter_spacing.  As far as I can see, such a space will
+                        * be written with letter_spacing either side.  This means that to get a horizontal space x we
+                        * need to write a " " with letter spacing (x - s) / 2, where s is the width of the " ".
+                        */
+                       auto layout = create_layout();
+                       setup_layout(layout, font_name, make_span(i, " ", {}));
+                       int space_width;
+                       int dummy;
+                       layout->get_pixel_size(space_width, dummy);
+                       auto spacing = ((i.space_before() * i.size_in_pixels(target_height) - space_width) / 2) * pixels_to_1024ths_point;
+                       out += make_span(i, " ", "letter_spacing=\"" + dcp::raw_convert<string>(spacing) + "\"");
+               }
+
+               out += make_span(i, i.text(), {});
        }
 
        return out;
@@ -301,7 +327,7 @@ render_line (list<StringText> subtitles, list<shared_ptr<Font>> fonts, dcp::Size
 
        auto const font_name = setup_font (first, fonts);
        auto const fade_factor = calculate_fade_factor (first, time, frame_rate);
-       auto const markup = marked_up (subtitles, target.height, fade_factor);
+       auto const markup = marked_up (subtitles, target.height, fade_factor, font_name);
        auto layout = create_layout ();
        setup_layout (layout, font_name, markup);
        dcp::Size size;
index d1c8c7aee9b357be8252f86ba26b7821af634b2a..07a97bb4047ca90ca6fe5559b7b964e7f50e3a3c 100644 (file)
@@ -27,7 +27,7 @@ namespace dcpomatic {
        class Font;
 }
 
-std::string marked_up (std::list<StringText> subtitles, int target_height, float fade_factor);
+std::string marked_up (std::list<StringText> subtitles, int target_height, float fade_factor, std::string font_name);
 std::list<PositionImage> render_text (
        std::list<StringText>, std::list<std::shared_ptr<dcpomatic::Font> > fonts, dcp::Size, dcpomatic::DCPTime, int
        );
index 88f7cd4a9bab0f2d10f9efc1da79c036305a454b..99b68faba2b8ae570d7a60cc1cf28f4d67565923 100644 (file)
@@ -247,7 +247,8 @@ TextDecoder::emit_plain_start (ContentTime from, sub::Subtitle const & subtitle)
                                           use libsub for DCP subs) we can cheat by just putting 0 in here.
                                        */
                                        dcp::Time (),
-                                       dcp::Time ()
+                                       dcp::Time (),
+                                       0
                                        )
                                );
                }
index 78ed8da99f9e1a2e0ddfe3ea416c4d8296fd6702..c165a5129bae42bfe646b772d56d246a7102935f 100644 (file)
@@ -406,7 +406,7 @@ DCPOMATIC_ENABLE_WARNINGS
        list<StringText> subs;
        dcp::SubtitleString ss(
                optional<string>(), false, false, false, dcp::Colour(), 42, 1, dcp::Time(), dcp::Time(), 0, dcp::HAlign::CENTER, 0, dcp::VAlign::CENTER, dcp::Direction::LTR,
-               "Hello dolly", dcp::Effect::NONE, dcp::Colour(), dcp::Time(), dcp::Time()
+               "Hello dolly", dcp::Effect::NONE, dcp::Colour(), dcp::Time(), dcp::Time(), 0
                );
        subs.push_back (StringText(ss, 0));
        render_text (subs, list<shared_ptr<Font>>(), dcp::Size(640, 480), DCPTime(), 24);
index a81ef05bf96c3a3c607c82151abf1113bc8ac84d..5774c8e9af7e111dc4d5140eb68c39fc33627b2f 100644 (file)
@@ -51,7 +51,8 @@ add (std::list<StringText>& s, std::string text, bool italic, bool bold, bool un
                                dcp::Effect::NONE,
                                dcp::Colour (0, 0, 0),
                                dcp::Time (),
-                               dcp::Time ()
+                               dcp::Time (),
+                               0
                                ),
                        2
                        )
@@ -63,7 +64,7 @@ BOOST_AUTO_TEST_CASE (render_markup_test1)
 {
        std::list<StringText> s;
        add (s, "Hello", false, false, false);
-       BOOST_CHECK_EQUAL (marked_up (s, 1024, 1), "<span size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\">Hello</span>");
+       BOOST_CHECK_EQUAL (marked_up(s, 1024, 1, ""), "<span size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\">Hello</span>");
 }
 
 /** Test marked_up() in render_text.cc */
@@ -71,7 +72,7 @@ BOOST_AUTO_TEST_CASE (render_markup_test2)
 {
        std::list<StringText> s;
        add (s, "Hello", false, true, false);
-       BOOST_CHECK_EQUAL (marked_up (s, 1024, 1), "<span weight=\"bold\" size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\">Hello</span>");
+       BOOST_CHECK_EQUAL (marked_up(s, 1024, 1, ""), "<span weight=\"bold\" size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\">Hello</span>");
 }
 
 
@@ -80,7 +81,7 @@ BOOST_AUTO_TEST_CASE (render_markup_test3)
 {
        std::list<StringText> s;
        add (s, "Hello", true, true, false);
-       BOOST_CHECK_EQUAL (marked_up (s, 1024, 1), "<span style=\"italic\" weight=\"bold\" size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\">Hello</span>");
+       BOOST_CHECK_EQUAL (marked_up(s, 1024, 1, ""), "<span style=\"italic\" weight=\"bold\" size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\">Hello</span>");
 }
 
 /** Test marked_up() in render_text.cc */
@@ -88,7 +89,7 @@ BOOST_AUTO_TEST_CASE (render_markup_test4)
 {
        std::list<StringText> s;
        add (s, "Hello", true, true, true);
-       BOOST_CHECK_EQUAL (marked_up (s, 1024, 1), "<span style=\"italic\" weight=\"bold\" underline=\"single\" size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\">Hello</span>");
+       BOOST_CHECK_EQUAL (marked_up(s, 1024, 1, ""), "<span style=\"italic\" weight=\"bold\" underline=\"single\" size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\">Hello</span>");
 }
 
 /** Test marked_up() in render_text.cc */
@@ -97,7 +98,7 @@ BOOST_AUTO_TEST_CASE (render_markup_test5)
        std::list<StringText> s;
        add (s, "Hello", false, true, false);
        add (s, " world.", false, false, false);
-       BOOST_CHECK_EQUAL (marked_up (s, 1024, 1), "<span weight=\"bold\" size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\">Hello</span><span size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\"> world.</span>");
+       BOOST_CHECK_EQUAL (marked_up(s, 1024, 1, ""), "<span weight=\"bold\" size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\">Hello</span><span size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\"> world.</span>");
 }
 
 /** Test marked_up() in render_text.cc */
@@ -107,5 +108,5 @@ BOOST_AUTO_TEST_CASE (render_markup_test6)
        add (s, "Hello", true, false, false);
        add (s, " world ", false, false, false);
        add (s, "we are bold.", false, true, false);
-       BOOST_CHECK_EQUAL (marked_up (s, 1024, 1), "<span style=\"italic\" size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\">Hello</span><span size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\"> world </span><span weight=\"bold\" size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\">we are bold.</span>");
+       BOOST_CHECK_EQUAL (marked_up(s, 1024, 1, ""), "<span style=\"italic\" size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\">Hello</span><span size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\"> world </span><span weight=\"bold\" size=\"41472\" alpha=\"65535\" color=\"#FFFFFF\">we are bold.</span>");
 }
diff --git a/wscript b/wscript
index fcfa8561d88c3ecb237c610a9d352a8d6e7e9196..db47ae274298f3badc937049fd63d42dacc6ea63 100644 (file)
--- a/wscript
+++ b/wscript
@@ -35,8 +35,8 @@ except ImportError:
 from waflib import Logs, Context
 
 APPNAME = 'dcpomatic'
-libdcp_version = '1.8.2'
-libsub_version = '1.6.2'
+libdcp_version = '1.8.3'
+libsub_version = '1.6.3'
 
 this_version = subprocess.Popen(shlex.split('git tag -l --points-at HEAD'), stdout=subprocess.PIPE).communicate()[0]
 last_version = subprocess.Popen(shlex.split('git describe --tags --abbrev=0'), stdout=subprocess.PIPE).communicate()[0]