Simple pass-through of <Ruby> tags in subtitles.
[libdcp.git] / src / subtitle_asset_internal.cc
index c525256d525d5a51a91bdb46c376c3142534e1f7..99d8411b3614789d2812b26137d5a2c89c552626 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
 
     This file is part of libdcp.
 
     files in the program, then also delete it here.
 */
 
+
+/** @file  src/subtitle_asset_internal.cc
+ *  @brief Internal SubtitleAsset helpers
+ */
+
+
 #include "subtitle_asset_internal.h"
 #include "subtitle_string.h"
+#include "compose.hpp"
 #include <cmath>
 
+
 using std::string;
 using std::map;
-using boost::shared_ptr;
+using std::shared_ptr;
 using namespace dcp;
 
-string
-order::Context::xmlns () const
-{
-       return standard == SMPTE ? "dcst" : "";
-}
 
 order::Font::Font (shared_ptr<SubtitleString> s, Standard standard)
 {
        if (s->font()) {
-               if (standard == SMPTE) {
+               if (standard == Standard::SMPTE) {
                        _values["ID"] = s->font().get ();
                } else {
                        _values["Id"] = s->font().get ();
@@ -62,7 +65,7 @@ order::Font::Font (shared_ptr<SubtitleString> s, Standard standard)
        _values["Effect"] = effect_to_string (s->effect());
        _values["EffectColor"] = s->effect_colour().to_argb_string();
        _values["Script"] = "normal";
-       if (standard == SMPTE) {
+       if (standard == Standard::SMPTE) {
                _values["Underline"] = s->underline() ? "yes" : "no";
        } else {
                _values["Underlined"] = s->underline() ? "yes" : "no";
@@ -70,16 +73,18 @@ order::Font::Font (shared_ptr<SubtitleString> s, Standard standard)
        _values["Weight"] = s->bold() ? "bold" : "normal";
 }
 
+
 xmlpp::Element*
-order::Font::as_xml (xmlpp::Element* parent, Context& context) const
+order::Font::as_xml (xmlpp::Element* parent, Context&) const
 {
-       xmlpp::Element* e = parent->add_child ("Font", context.xmlns());
-       for (map<string, string>::const_iterator i = _values.begin(); i != _values.end(); ++i) {
-               e->set_attribute (i->first, i->second);
+       auto e = parent->add_child("Font");
+       for (const auto& i: _values) {
+               e->set_attribute (i.first, i.second);
        }
        return e;
 }
 
+
 /** Modify our values so that they contain only those that are common to us and
  *  other.
  */
@@ -88,49 +93,62 @@ order::Font::take_intersection (Font other)
 {
        map<string, string> inter;
 
-       for (map<string, string>::const_iterator i = other._values.begin(); i != other._values.end(); ++i) {
-               map<string, string>::iterator t = _values.find (i->first);
-               if (t != _values.end() && t->second == i->second) {
-                       inter.insert (*i);
+       for (auto const& i: other._values) {
+               auto t = _values.find (i.first);
+               if (t != _values.end() && t->second == i.second) {
+                       inter.insert (i);
                }
        }
 
        _values = inter;
 }
 
+
 /** Modify our values so that it contains only those keys that are not in other */
 void
 order::Font::take_difference (Font other)
 {
        map<string, string> diff;
-       for (map<string, string>::const_iterator i = _values.begin(); i != _values.end(); ++i) {
-               if (other._values.find (i->first) == other._values.end ()) {
-                       diff.insert (*i);
+       for (auto const& i: _values) {
+               if (other._values.find (i.first) == other._values.end()) {
+                       diff.insert (i);
                }
        }
 
        _values = diff;
 }
 
+
 bool
 order::Font::empty () const
 {
        return _values.empty ();
 }
 
+
 xmlpp::Element*
 order::Part::as_xml (xmlpp::Element* parent, Context &) const
 {
        return parent;
 }
 
+
 xmlpp::Element*
-order::String::as_xml (xmlpp::Element* parent, Context &) const
+order::String::as_xml (xmlpp::Element* parent, Context& context) const
 {
-       parent->add_child_text (text);
+       if (fabs(_space_before) > SPACE_BEFORE_EPSILON) {
+               auto space = parent->add_child("Space");
+               auto size = raw_convert<string>(_space_before, 2);
+               if (context.standard == Standard::INTEROP) {
+                       size += "em";
+               }
+               space->set_attribute("Size", size);
+       }
+       parent->add_child_text (_text);
        return 0;
 }
 
+
 void
 order::Part::write_xml (xmlpp::Element* parent, order::Context& context) const
 {
@@ -140,87 +158,130 @@ order::Part::write_xml (xmlpp::Element* parent, order::Context& context) const
 
        parent = as_xml (parent, context);
 
-       BOOST_FOREACH (boost::shared_ptr<order::Part> i, children) {
+       for (auto i: children) {
                i->write_xml (parent, context);
        }
 }
 
-xmlpp::Element*
-order::Text::as_xml (xmlpp::Element* parent, Context& context) const
-{
-       xmlpp::Element* e = parent->add_child ("Text", context.xmlns());
 
-       if (_h_align != HALIGN_CENTER) {
-               if (context.standard == SMPTE) {
-                       e->set_attribute ("Halign", halign_to_string (_h_align));
+static void
+position_align (xmlpp::Element* e, order::Context& context, HAlign h_align, float h_position, VAlign v_align, float v_position, float z_position)
+{
+       if (h_align != HAlign::CENTER) {
+               if (context.standard == Standard::SMPTE) {
+                       e->set_attribute ("Halign", halign_to_string (h_align));
                } else {
-                       e->set_attribute ("HAlign", halign_to_string (_h_align));
+                       e->set_attribute ("HAlign", halign_to_string (h_align));
                }
        }
 
-       if (fabs(_h_position) > ALIGN_EPSILON) {
-               if (context.standard == SMPTE) {
-                       e->set_attribute ("Hposition", raw_convert<string> (_h_position * 100, 6));
+       if (fabs(h_position) > ALIGN_EPSILON) {
+               if (context.standard == Standard::SMPTE) {
+                       e->set_attribute ("Hposition", raw_convert<string> (h_position * 100, 6));
                } else {
-                       e->set_attribute ("HPosition", raw_convert<string> (_h_position * 100, 6));
+                       e->set_attribute ("HPosition", raw_convert<string> (h_position * 100, 6));
                }
        }
 
-       if (context.standard == SMPTE) {
-               e->set_attribute ("Valign", valign_to_string (_v_align));
+       if (context.standard == Standard::SMPTE) {
+               e->set_attribute ("Valign", valign_to_string (v_align));
        } else {
-               e->set_attribute ("VAlign", valign_to_string (_v_align));
+               e->set_attribute ("VAlign", valign_to_string (v_align));
        }
 
-       if (fabs(_v_position) > ALIGN_EPSILON) {
-               if (context.standard == SMPTE) {
-                       e->set_attribute ("Vposition", raw_convert<string> (_v_position * 100, 6));
+       if (fabs(v_position) > ALIGN_EPSILON) {
+               if (context.standard == Standard::SMPTE) {
+                       e->set_attribute ("Vposition", raw_convert<string> (v_position * 100, 6));
                } else {
-                       e->set_attribute ("VPosition", raw_convert<string> (_v_position * 100, 6));
+                       e->set_attribute ("VPosition", raw_convert<string> (v_position * 100, 6));
                }
        } else {
-               if (context.standard == SMPTE) {
+               if (context.standard == Standard::SMPTE) {
                        e->set_attribute ("Vposition", "0");
                } else {
                        e->set_attribute ("VPosition", "0");
                }
        }
 
+       if (fabs(z_position) > ALIGN_EPSILON && context.standard == Standard::SMPTE) {
+               e->set_attribute("Zposition", raw_convert<string>(z_position * 100, 6));
+       }
+}
+
+
+xmlpp::Element*
+order::Text::as_xml (xmlpp::Element* parent, Context& context) const
+{
+       auto e = parent->add_child ("Text");
+
+       position_align(e, context, _h_align, _h_position, _v_align, _v_position, _z_position);
+
        /* Interop only supports "horizontal" or "vertical" for direction, so only write this
           for SMPTE.
        */
-       if (_direction != DIRECTION_LTR && context.standard == SMPTE) {
+       if (_direction != Direction::LTR && context.standard == Standard::SMPTE) {
                e->set_attribute ("Direction", direction_to_string (_direction));
        }
 
+       for (auto const& ruby: _rubies) {
+               auto xml = e->add_child("Ruby");
+               xml->add_child("Rb")->add_child_text(ruby.base);
+               auto rt = xml->add_child("Rt");
+               rt->add_child_text(ruby.annotation);
+               rt->set_attribute("Size", dcp::raw_convert<string>(ruby.size, 6));
+               rt->set_attribute("Position", ruby.position == RubyPosition::BEFORE ? "before" : "after");
+               rt->set_attribute("Offset", dcp::raw_convert<string>(ruby.offset, 6));
+               rt->set_attribute("Spacing", dcp::raw_convert<string>(ruby.spacing, 6));
+               rt->set_attribute("AspectAdjust", dcp::raw_convert<string>(ruby.aspect_adjust, 6));
+       }
+
        return e;
 }
 
+
 xmlpp::Element*
 order::Subtitle::as_xml (xmlpp::Element* parent, Context& context) const
 {
-       xmlpp::Element* e = parent->add_child ("Subtitle", context.xmlns());
+       auto e = parent->add_child ("Subtitle");
        e->set_attribute ("SpotNumber", raw_convert<string> (context.spot_number++));
        e->set_attribute ("TimeIn", _in.rebase(context.time_code_rate).as_string(context.standard));
        e->set_attribute ("TimeOut", _out.rebase(context.time_code_rate).as_string(context.standard));
-       if (context.standard == SMPTE) {
+       if (context.standard == Standard::SMPTE) {
                e->set_attribute ("FadeUpTime", _fade_up.rebase(context.time_code_rate).as_string(context.standard));
                e->set_attribute ("FadeDownTime", _fade_down.rebase(context.time_code_rate).as_string(context.standard));
        } else {
-               e->set_attribute ("FadeUpTime", raw_convert<string> (_fade_up.as_editable_units(context.time_code_rate)));
-               e->set_attribute ("FadeDownTime", raw_convert<string> (_fade_down.as_editable_units(context.time_code_rate)));
+               e->set_attribute ("FadeUpTime", raw_convert<string> (_fade_up.as_editable_units_ceil(context.time_code_rate)));
+               e->set_attribute ("FadeDownTime", raw_convert<string> (_fade_down.as_editable_units_ceil(context.time_code_rate)));
        }
        return e;
 }
 
+
 bool
 order::Font::operator== (Font const & other) const
 {
        return _values == other._values;
 }
 
+
 void
 order::Font::clear ()
 {
        _values.clear ();
 }
+
+
+xmlpp::Element *
+order::Image::as_xml (xmlpp::Element* parent, Context& context) const
+{
+       auto e = parent->add_child ("Image");
+
+       position_align(e, context, _h_align, _h_position, _v_align, _v_position, _z_position);
+       if (context.standard == Standard::SMPTE) {
+               e->add_child_text ("urn:uuid:" + _id);
+       } else {
+               e->add_child_text (_id + ".png");
+       }
+
+       return e;
+}