Switch subtitle string font specs to be the font ID; split SubtitleContent into Inter...
authorCarl Hetherington <cth@carlh.net>
Sun, 14 Dec 2014 22:44:23 +0000 (22:44 +0000)
committerCarl Hetherington <cth@carlh.net>
Sun, 14 Dec 2014 22:44:23 +0000 (22:44 +0000)
19 files changed:
src/dcp.cc
src/interop_load_font.cc [new file with mode: 0644]
src/interop_load_font.h [new file with mode: 0644]
src/interop_subtitle_content.cc [new file with mode: 0644]
src/interop_subtitle_content.h [new file with mode: 0644]
src/load_font.cc [deleted file]
src/load_font.h [deleted file]
src/smpte_load_font.cc [new file with mode: 0644]
src/smpte_load_font.h [new file with mode: 0644]
src/smpte_subtitle_content.cc [new file with mode: 0644]
src/smpte_subtitle_content.h [new file with mode: 0644]
src/subtitle_content.cc
src/subtitle_content.h
src/subtitle_string.cc
src/subtitle_string.h
src/wscript
test/subs_in_out.cc
test/subtitle_tests.cc
tools/dcpinfo.cc

index 58b6c66fa709822b0ad6b196dff74fb03debbc0d..b1443e1ada0cc03c5c5b5f5326d20e8b44e99cfe 100644 (file)
@@ -25,7 +25,8 @@
 #include "dcp.h"
 #include "sound_mxf.h"
 #include "picture_mxf.h"
-#include "subtitle_content.h"
+#include "interop_subtitle_content.h"
+#include "smpte_subtitle_content.h"
 #include "mono_picture_mxf.h"
 #include "stereo_picture_mxf.h"
 #include "util.h"
@@ -132,7 +133,7 @@ DCP::read (bool keep_going, ReadErrors* errors)
                        if (root == "CompositionPlaylist") {
                                _assets.push_back (shared_ptr<CPL> (new CPL (path)));
                        } else if (root == "DCSubtitle") {
-                               _assets.push_back (shared_ptr<SubtitleContent> (new SubtitleContent (path, false)));
+                               _assets.push_back (shared_ptr<InteropSubtitleContent> (new InteropSubtitleContent (path)));
                        }
                } else if (boost::algorithm::ends_with (path.string(), ".mxf")) {
                        ASDCP::EssenceType_t type;
@@ -154,7 +155,7 @@ DCP::read (bool keep_going, ReadErrors* errors)
                                        _assets.push_back (shared_ptr<StereoPictureMXF> (new StereoPictureMXF (path)));
                                        break;
                                case ASDCP::ESS_TIMED_TEXT:
-                                       _assets.push_back (shared_ptr<SubtitleContent> (new SubtitleContent (path, true)));
+                                       _assets.push_back (shared_ptr<SMPTESubtitleContent> (new SMPTESubtitleContent (path)));
                                        break;
                                default:
                                        throw DCPReadError ("Unknown MXF essence type");
diff --git a/src/interop_load_font.cc b/src/interop_load_font.cc
new file mode 100644 (file)
index 0000000..ec2653e
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "interop_load_font.h"
+#include <libcxml/cxml.h>
+
+using std::string;
+using boost::shared_ptr;
+using boost::optional;
+using namespace dcp;
+
+InteropLoadFont::InteropLoadFont (shared_ptr<const cxml::Node> node)
+{
+       optional<string> x = node->optional_string_attribute ("Id");
+       if (!x) {
+               x = node->optional_string_attribute ("ID");
+       }
+       id = x.get_value_or ("");
+       
+       uri = node->string_attribute ("URI");
+}
diff --git a/src/interop_load_font.h b/src/interop_load_font.h
new file mode 100644 (file)
index 0000000..b6043bd
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/shared_ptr.hpp>
+#include <boost/optional.hpp>
+
+namespace cxml {
+       class Node;
+}
+
+namespace dcp {
+       
+class InteropLoadFont 
+{
+public:
+       InteropLoadFont () {}
+       InteropLoadFont (boost::shared_ptr<const cxml::Node> node);
+
+       std::string id;
+       std::string uri;
+};
+
+}
diff --git a/src/interop_subtitle_content.cc b/src/interop_subtitle_content.cc
new file mode 100644 (file)
index 0000000..85e52da
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "interop_subtitle_content.h"
+#include "interop_load_font.h"
+#include "xml.h"
+#include "raw_convert.h"
+#include "font.h"
+
+using std::list;
+using std::string;
+using boost::shared_ptr;
+using namespace dcp;
+
+InteropSubtitleContent::InteropSubtitleContent (boost::filesystem::path file)
+       : SubtitleContent (file)
+{
+       shared_ptr<cxml::Document> xml (new cxml::Document ("DCSubtitle"));
+       xml->read_file (file);
+       _id = xml->string_child ("SubtitleID");
+
+       _movie_title = xml->string_child ("MovieTitle");
+
+       _load_font_nodes = type_children<dcp::InteropLoadFont> (xml, "LoadFont");
+       list<shared_ptr<dcp::Font> > font_nodes = type_children<dcp::Font> (xml, "Font");
+
+       parse_common (xml, font_nodes);
+}
+
+InteropSubtitleContent::InteropSubtitleContent (string movie_title, string language)
+       : _movie_title (movie_title)
+{
+       _language = language;
+}
+
+struct SubtitleSorter {
+       bool operator() (SubtitleString const & a, SubtitleString const & b) {
+               if (a.in() != b.in()) {
+                       return a.in() < b.in();
+               }
+               return a.v_position() < b.v_position();
+       }
+};
+
+Glib::ustring
+InteropSubtitleContent::xml_as_string () const
+{
+       xmlpp::Document doc;
+       xmlpp::Element* root = doc.create_root_node ("DCSubtitle");
+       root->set_attribute ("Version", "1.0");
+
+       root->add_child("SubtitleID")->add_child_text (_id);
+       root->add_child("MovieTitle")->add_child_text (_movie_title);
+       root->add_child("ReelNumber")->add_child_text (raw_convert<string> (_reel_number));
+       root->add_child("Language")->add_child_text (_language);
+
+       if (_load_font_nodes.size() > 1) {
+               boost::throw_exception (MiscError ("multiple LoadFont nodes not supported"));
+       }
+
+       if (!_load_font_nodes.empty ()) {
+               xmlpp::Element* load_font = root->add_child("LoadFont");
+               load_font->set_attribute ("Id", _load_font_nodes.front()->id);
+               load_font->set_attribute ("URI", _load_font_nodes.front()->uri);
+       }
+
+       list<SubtitleString> sorted = _subtitles;
+       sorted.sort (SubtitleSorter ());
+
+       /* XXX: multiple fonts not supported */
+       /* XXX: script, underlined, weight not supported */
+
+       bool italic = false;
+       Color color;
+       int size = 0;
+       Effect effect = NONE;
+       Color effect_color;
+       int spot_number = 1;
+       Time last_in;
+       Time last_out;
+       Time last_fade_up_time;
+       Time last_fade_down_time;
+
+       xmlpp::Element* font = 0;
+       xmlpp::Element* subtitle = 0;
+
+       for (list<SubtitleString>::iterator i = sorted.begin(); i != sorted.end(); ++i) {
+
+               /* We will start a new <Font>...</Font> whenever some font property changes.
+                  I suppose we should really make an optimal hierarchy of <Font> tags, but
+                  that seems hard.
+               */
+
+               bool const font_changed =
+                       italic       != i->italic()       ||
+                       color        != i->color()        ||
+                       size         != i->size()         ||
+                       effect       != i->effect()       ||
+                       effect_color != i->effect_color();
+
+               if (font_changed) {
+                       italic = i->italic ();
+                       color = i->color ();
+                       size = i->size ();
+                       effect = i->effect ();
+                       effect_color = i->effect_color ();
+               }
+
+               if (!font || font_changed) {
+                       font = root->add_child ("Font");
+                       string id = "theFontId";
+                       if (!_load_font_nodes.empty()) {
+                               id = _load_font_nodes.front()->id;
+                       }
+                       font->set_attribute ("Id", id);
+                       font->set_attribute ("Italic", italic ? "yes" : "no");
+                       font->set_attribute ("Color", color.to_argb_string());
+                       font->set_attribute ("Size", raw_convert<string> (size));
+                       font->set_attribute ("Effect", effect_to_string (effect));
+                       font->set_attribute ("EffectColor", effect_color.to_argb_string());
+                       font->set_attribute ("Script", "normal");
+                       font->set_attribute ("Underlined", "no");
+                       font->set_attribute ("Weight", "normal");
+               }
+
+               if (!subtitle || font_changed ||
+                   (last_in != i->in() ||
+                    last_out != i->out() ||
+                    last_fade_up_time != i->fade_up_time() ||
+                    last_fade_down_time != i->fade_down_time()
+                           )) {
+
+                       subtitle = font->add_child ("Subtitle");
+                       subtitle->set_attribute ("SpotNumber", raw_convert<string> (spot_number++));
+                       subtitle->set_attribute ("TimeIn", i->in().to_string());
+                       subtitle->set_attribute ("TimeOut", i->out().to_string());
+                       subtitle->set_attribute ("FadeUpTime", raw_convert<string> (i->fade_up_time().to_ticks()));
+                       subtitle->set_attribute ("FadeDownTime", raw_convert<string> (i->fade_down_time().to_ticks()));
+
+                       last_in = i->in ();
+                       last_out = i->out ();
+                       last_fade_up_time = i->fade_up_time ();
+                       last_fade_down_time = i->fade_down_time ();
+               }
+
+               xmlpp::Element* text = subtitle->add_child ("Text");
+               text->set_attribute ("VAlign", valign_to_string (i->v_align()));                
+               text->set_attribute ("VPosition", raw_convert<string> (i->v_position()));
+               text->add_child_text (i->text());
+       }
+
+       return doc.write_to_string_formatted ("UTF-8");
+}
+
diff --git a/src/interop_subtitle_content.h b/src/interop_subtitle_content.h
new file mode 100644 (file)
index 0000000..cd8d7bb
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "subtitle_content.h"
+#include <boost/filesystem.hpp>
+
+namespace dcp {
+
+class InteropLoadFont; 
+
+class InteropSubtitleContent : public SubtitleContent
+{
+public:
+       InteropSubtitleContent (std::string movie_title, std::string language);
+       InteropSubtitleContent (boost::filesystem::path file);
+
+       Glib::ustring xml_as_string () const;
+       
+private:
+       std::string _movie_title;
+       std::list<boost::shared_ptr<InteropLoadFont> > _load_font_nodes;
+};
+
+}
diff --git a/src/load_font.cc b/src/load_font.cc
deleted file mode 100644 (file)
index b46569c..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
-    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include "load_font.h"
-#include <libcxml/cxml.h>
-
-using std::string;
-using boost::shared_ptr;
-using boost::optional;
-using namespace dcp;
-
-LoadFont::LoadFont (boost::shared_ptr<const cxml::Node> node)
-{
-       optional<string> x = node->optional_string_attribute ("Id");
-       if (!x) {
-               x = node->optional_string_attribute ("ID");
-       }
-       id = x.get_value_or ("");
-       
-       uri = node->optional_string_attribute ("URI");
-}
diff --git a/src/load_font.h b/src/load_font.h
deleted file mode 100644 (file)
index 6d52a50..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
-    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-*/
-
-#include <boost/shared_ptr.hpp>
-#include <boost/optional.hpp>
-
-namespace cxml {
-       class Node;
-}
-
-namespace dcp {
-       
-class LoadFont 
-{
-public:
-       LoadFont () {}
-       LoadFont (boost::shared_ptr<const cxml::Node> node);
-
-       std::string id;
-       boost::optional<std::string> uri;
-};
-
-}
diff --git a/src/smpte_load_font.cc b/src/smpte_load_font.cc
new file mode 100644 (file)
index 0000000..90e9c99
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "smpte_load_font.h"
+#include <libcxml/cxml.h>
+
+using std::string;
+using boost::shared_ptr;
+using namespace dcp;
+
+SMPTELoadFont::SMPTELoadFont (shared_ptr<const cxml::Node> node)
+{
+       id = node->string_attribute ("ID");
+       urn = node->content().substr (9);
+}
diff --git a/src/smpte_load_font.h b/src/smpte_load_font.h
new file mode 100644 (file)
index 0000000..d8d1fa6
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/shared_ptr.hpp>
+#include <boost/optional.hpp>
+
+namespace cxml {
+       class Node;
+}
+
+namespace dcp {
+       
+class SMPTELoadFont 
+{
+public:
+       SMPTELoadFont () {}
+       SMPTELoadFont (boost::shared_ptr<const cxml::Node> node);
+
+       std::string id;
+       std::string urn;
+};
+
+}
diff --git a/src/smpte_subtitle_content.cc b/src/smpte_subtitle_content.cc
new file mode 100644 (file)
index 0000000..1fa94d1
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "smpte_subtitle_content.h"
+#include "smpte_load_font.h"
+#include "font.h"
+#include "exceptions.h"
+#include "xml.h"
+#include "AS_DCP.h"
+#include "KM_util.h"
+
+using std::string;
+using std::list;
+using std::stringstream;
+using boost::shared_ptr;
+using namespace dcp;
+
+SMPTESubtitleContent::SMPTESubtitleContent (boost::filesystem::path file)
+       : SubtitleContent (file)
+{
+       ASDCP::TimedText::MXFReader reader;
+       Kumu::Result_t r = reader.OpenRead (file.string().c_str ());
+       if (ASDCP_FAILURE (r)) {
+               boost::throw_exception (MXFFileError ("could not open MXF file for reading", file, r));
+       }
+       
+       string s;
+       reader.ReadTimedTextResource (s, 0, 0);
+       shared_ptr<cxml::Document> xml (new cxml::Document ("SubtitleReel"));
+       stringstream t;
+       t << s;
+       xml->read_stream (t);
+       
+       ASDCP::WriterInfo info;
+       reader.FillWriterInfo (info);
+       
+       char buffer[64];
+       Kumu::bin2UUIDhex (info.AssetUUID, ASDCP::UUIDlen, buffer, sizeof (buffer));
+       _id = buffer;
+
+       _load_font_nodes = type_children<dcp::SMPTELoadFont> (xml, "LoadFont");
+
+       shared_ptr<cxml::Node> subtitle_list = xml->optional_node_child ("SubtitleList");
+       list<shared_ptr<dcp::Font> > font_nodes = type_children<dcp::Font> (subtitle_list, "Font");
+
+       parse_common (xml, font_nodes);
+}
diff --git a/src/smpte_subtitle_content.h b/src/smpte_subtitle_content.h
new file mode 100644 (file)
index 0000000..b760f72
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "subtitle_content.h"
+
+namespace dcp {
+
+class SMPTELoadFont;   
+
+class SMPTESubtitleContent : public SubtitleContent
+{
+public:
+       /** @param file MXF file */
+       SMPTESubtitleContent (boost::filesystem::path file);
+
+private:
+       std::list<boost::shared_ptr<SMPTELoadFont> > _load_font_nodes;
+};
+
+}
index 4f83d5bd61e259f436f6097ed704dc147852c1ad..7bc080b313d1fc7f669ca6d6c503ca1d683c8ac6 100644 (file)
@@ -23,7 +23,6 @@
 #include "xml.h"
 #include "font.h"
 #include "text.h"
-#include "load_font.h"
 #include "subtitle_string.h"
 #include "AS_DCP.h"
 #include "KM_util.h"
@@ -41,71 +40,26 @@ using boost::shared_ptr;
 using boost::optional;
 using namespace dcp;
 
-SubtitleContent::SubtitleContent (boost::filesystem::path file, bool mxf)
+SubtitleContent::SubtitleContent (boost::filesystem::path file)
        : Content (file)
 {
-       shared_ptr<cxml::Document> xml;
-       
-       if (mxf) {
-               ASDCP::TimedText::MXFReader reader;
-               Kumu::Result_t r = reader.OpenRead (file.string().c_str ());
-               if (ASDCP_FAILURE (r)) {
-                       boost::throw_exception (MXFFileError ("could not open MXF file for reading", file, r));
-               }
-               
-               string s;
-               reader.ReadTimedTextResource (s, 0, 0);
-               xml.reset (new cxml::Document ("SubtitleReel"));
-               stringstream t;
-               t << s;
-               xml->read_stream (t);
-
-               ASDCP::WriterInfo info;
-               reader.FillWriterInfo (info);
-               
-               char buffer[64];
-               Kumu::bin2UUIDhex (info.AssetUUID, ASDCP::UUIDlen, buffer, sizeof (buffer));
-               _id = buffer;
-
-       } else {
-               xml.reset (new cxml::Document ("DCSubtitle"));
-               xml->read_file (file);
-               _id = xml->string_child ("SubtitleID");
-       }
 
-       /* XXX: hacks aplenty in here; probably need separate parsers for DCSubtitle and SubtitleReel */
+}
 
-       _movie_title = xml->optional_string_child ("MovieTitle");
+void
+SubtitleContent::parse_common (shared_ptr<cxml::Document> xml, list<shared_ptr<dcp::Font> > font_nodes)
+{
        _reel_number = xml->string_child ("ReelNumber");
        _language = xml->string_child ("Language");
 
-       xml->ignore_child ("LoadFont");
-
-       list<shared_ptr<dcp::Font> > font_nodes = type_children<dcp::Font> (xml, "Font");
-       _load_font_nodes = type_children<dcp::LoadFont> (xml, "LoadFont");
-
        /* Now make Subtitle objects to represent the raw XML nodes
           in a sane way.
        */
 
-       shared_ptr<cxml::Node> subtitle_list = xml->optional_node_child ("SubtitleList");
-       if (subtitle_list) {
-               list<shared_ptr<dcp::Font> > font = type_children<dcp::Font> (subtitle_list, "Font");
-               copy (font.begin(), font.end(), back_inserter (font_nodes));
-       }
-       
        ParseState parse_state;
        examine_font_nodes (xml, font_nodes, parse_state);
 }
 
-SubtitleContent::SubtitleContent (string movie_title, string language)
-       : _movie_title (movie_title)
-       , _reel_number ("1")
-       , _language (language)
-{
-
-}
-
 void
 SubtitleContent::examine_font_nodes (
        shared_ptr<const cxml::Node> xml,
@@ -167,7 +121,7 @@ SubtitleContent::maybe_add_subtitle (string text, ParseState const & parse_state
 
        _subtitles.push_back (
                SubtitleString (
-                       font_id_to_name (effective_font.id),
+                       effective_font.id,
                        effective_font.italic.get(),
                        effective_font.color.get(),
                        effective_font.size,
@@ -197,40 +151,12 @@ SubtitleContent::subtitles_at (Time t) const
        return s;
 }
 
-std::string
-SubtitleContent::font_id_to_name (string id) const
-{
-       list<shared_ptr<dcp::LoadFont> >::const_iterator i = _load_font_nodes.begin();
-       while (i != _load_font_nodes.end() && (*i)->id != id) {
-               ++i;
-       }
-
-       if (i == _load_font_nodes.end ()) {
-               return "";
-       }
-
-       if ((*i)->uri && (*i)->uri.get() == "arial.ttf") {
-               return "Arial";
-       }
-
-       return "";
-}
-
 void
 SubtitleContent::add (SubtitleString s)
 {
        _subtitles.push_back (s);
 }
 
-struct SubtitleSorter {
-       bool operator() (SubtitleString const & a, SubtitleString const & b) {
-               if (a.in() != b.in()) {
-                       return a.in() < b.in();
-               }
-               return a.v_position() < b.v_position();
-       }
-};
-
 void
 SubtitleContent::write_xml (boost::filesystem::path p) const
 {
@@ -246,120 +172,6 @@ SubtitleContent::write_xml (boost::filesystem::path p) const
        _file = p;
 }
 
-Glib::ustring
-SubtitleContent::xml_as_string () const
-{
-       xmlpp::Document doc;
-       xmlpp::Element* root = doc.create_root_node ("DCSubtitle");
-       root->set_attribute ("Version", "1.0");
-
-       root->add_child("SubtitleID")->add_child_text (_id);
-       if (_movie_title) {
-               root->add_child("MovieTitle")->add_child_text (_movie_title.get ());
-       }
-       root->add_child("ReelNumber")->add_child_text (raw_convert<string> (_reel_number));
-       root->add_child("Language")->add_child_text (_language);
-
-       if (_load_font_nodes.size() > 1) {
-               boost::throw_exception (MiscError ("multiple LoadFont nodes not supported"));
-       }
-
-       if (!_load_font_nodes.empty ()) {
-               xmlpp::Element* load_font = root->add_child("LoadFont");
-               load_font->set_attribute ("Id", _load_font_nodes.front()->id);
-               if (_load_font_nodes.front()->uri) {
-                       load_font->set_attribute ("URI", _load_font_nodes.front()->uri.get ());
-               }
-       }
-
-       list<SubtitleString> sorted = _subtitles;
-       sorted.sort (SubtitleSorter ());
-
-       /* XXX: multiple fonts not supported */
-       /* XXX: script, underlined, weight not supported */
-
-       bool italic = false;
-       Color color;
-       int size = 0;
-       Effect effect = NONE;
-       Color effect_color;
-       int spot_number = 1;
-       Time last_in;
-       Time last_out;
-       Time last_fade_up_time;
-       Time last_fade_down_time;
-
-       xmlpp::Element* font = 0;
-       xmlpp::Element* subtitle = 0;
-
-       for (list<SubtitleString>::iterator i = sorted.begin(); i != sorted.end(); ++i) {
-
-               /* We will start a new <Font>...</Font> whenever some font property changes.
-                  I suppose we should really make an optimal hierarchy of <Font> tags, but
-                  that seems hard.
-               */
-
-               bool const font_changed =
-                       italic       != i->italic()       ||
-                       color        != i->color()        ||
-                       size         != i->size()         ||
-                       effect       != i->effect()       ||
-                       effect_color != i->effect_color();
-
-               if (font_changed) {
-                       italic = i->italic ();
-                       color = i->color ();
-                       size = i->size ();
-                       effect = i->effect ();
-                       effect_color = i->effect_color ();
-               }
-
-               if (!font || font_changed) {
-                       font = root->add_child ("Font");
-                       string id = "theFontId";
-                       if (!_load_font_nodes.empty()) {
-                               id = _load_font_nodes.front()->id;
-                       }
-                       font->set_attribute ("Id", id);
-                       font->set_attribute ("Italic", italic ? "yes" : "no");
-                       font->set_attribute ("Color", color.to_argb_string());
-                       font->set_attribute ("Size", raw_convert<string> (size));
-                       font->set_attribute ("Effect", effect_to_string (effect));
-                       font->set_attribute ("EffectColor", effect_color.to_argb_string());
-                       font->set_attribute ("Script", "normal");
-                       font->set_attribute ("Underlined", "no");
-                       font->set_attribute ("Weight", "normal");
-               }
-
-               if (!subtitle || font_changed ||
-                   (last_in != i->in() ||
-                    last_out != i->out() ||
-                    last_fade_up_time != i->fade_up_time() ||
-                    last_fade_down_time != i->fade_down_time()
-                           )) {
-
-                       subtitle = font->add_child ("Subtitle");
-                       subtitle->set_attribute ("SpotNumber", raw_convert<string> (spot_number++));
-                       subtitle->set_attribute ("TimeIn", i->in().to_string());
-                       subtitle->set_attribute ("TimeOut", i->out().to_string());
-                       subtitle->set_attribute ("FadeUpTime", raw_convert<string> (i->fade_up_time().to_ticks()));
-                       subtitle->set_attribute ("FadeDownTime", raw_convert<string> (i->fade_down_time().to_ticks()));
-
-                       last_in = i->in ();
-                       last_out = i->out ();
-                       last_fade_up_time = i->fade_up_time ();
-                       last_fade_down_time = i->fade_down_time ();
-               }
-
-               xmlpp::Element* text = subtitle->add_child ("Text");
-               text->set_attribute ("VAlign", valign_to_string (i->v_align()));                
-               text->set_attribute ("VPosition", raw_convert<string> (i->v_position()));
-               text->add_child_text (i->text());
-       }
-
-       return doc.write_to_string_formatted ("UTF-8");
-}
-
 Time
 SubtitleContent::latest_subtitle_out () const
 {
index 945eb4d098bc99b179c3662053db32e2571a0ab3..222d52ccbe1d8cd78a8753b53610b47dc1365ec8 100644 (file)
@@ -32,23 +32,15 @@ class SubtitleString;
 class Font;
 class Text;
 class Subtitle;
-class LoadFont;
 
 /** @class SubtitleContent
- *  @brief A representation of an XML or MXF file containing subtitles.
- *
- *  XXX: perhaps this should inhert from MXF, or there should be different
- *  classes for XML and MXF subs.
+ *  @brief A parent for classes representing a file containing subtitles.
  */
 class SubtitleContent : public Content
 {
 public:
-       /** Construct a SubtitleContent.
-        *  @param file Filename.
-        *  @param mxf true if the file is an MXF file, false for XML.
-        */
-       SubtitleContent (boost::filesystem::path file, bool mxf);
-       SubtitleContent (std::string movie_title, std::string language);
+       SubtitleContent () {}
+       SubtitleContent (boost::filesystem::path file);
 
        bool equals (
                boost::shared_ptr<const Asset>,
@@ -72,11 +64,16 @@ public:
        void add (SubtitleString);
 
        void write_xml (boost::filesystem::path) const;
-       Glib::ustring xml_as_string () const;
+       virtual Glib::ustring xml_as_string () const {
+               /* XXX: this should be pure virtual when SMPTE writing is implemented */
+               return "";
+       }
 
        Time latest_subtitle_out () const;
 
 protected:
+       void parse_common (boost::shared_ptr<cxml::Document> xml, std::list<boost::shared_ptr<dcp::Font> > font_nodes);
+       
        std::string pkl_type (Standard) const {
                return "text/xml";
        }
@@ -85,9 +82,13 @@ protected:
                return "Subtitle";
        }
        
-private:
-       std::string font_id_to_name (std::string id) const;
+       /* strangely, this is sometimes a string */
+       std::string _reel_number;
+       std::string _language;
+
+       std::list<SubtitleString> _subtitles;
 
+private:
        struct ParseState {
                std::list<boost::shared_ptr<Font> > font_nodes;
                std::list<boost::shared_ptr<Text> > text_nodes;
@@ -107,14 +108,6 @@ private:
                std::list<boost::shared_ptr<Text> > const & text_nodes,
                ParseState& parse_state
                );
-
-       boost::optional<std::string> _movie_title;
-       /* strangely, this is sometimes a string */
-       std::string _reel_number;
-       std::string _language;
-       std::list<boost::shared_ptr<LoadFont> > _load_font_nodes;
-
-       std::list<SubtitleString> _subtitles;
 };
 
 }
index 66869fd59329db90e04343f98b44a633ce8dd921..a92eac9240b30867ed663cb6a92e29be14f4de16 100644 (file)
 
 using std::string;
 using std::ostream;
+using boost::optional;
 using namespace dcp;
 
 SubtitleString::SubtitleString (
-       string font,
+       optional<string> font,
        bool italic,
        Color color,
        int size,
@@ -92,7 +93,7 @@ dcp::operator<< (ostream& s, SubtitleString const & sub)
 {
        s << "\n`" << sub.text() << "' from " << sub.in() << " to " << sub.out() << ";\n"
          << "fade up " << sub.fade_up_time() << ", fade down " << sub.fade_down_time() << ";\n"
-         << "font " << sub.font() << ", ";
+         << "font " << sub.font().get_value_or ("[default]") << ", ";
 
        if (sub.italic()) {
                s << "italic";
index b313e82b56100bc775faaa42bb1f171bf983abee..512a1b5b3829fb2219fb569771f893175ad2d5f5 100644 (file)
@@ -26,6 +26,7 @@
 
 #include "types.h"
 #include "dcp_time.h"
+#include <boost/optional.hpp>
 #include <string>
 
 namespace dcp {
@@ -37,7 +38,7 @@ class SubtitleString
 {
 public:
        SubtitleString (
-               std::string font,
+               boost::optional<std::string> font,
                bool italic,
                Color color,
                int size,
@@ -52,8 +53,8 @@ public:
                Time fade_down_time
                );
 
-       /** @return font name */
-       std::string font () const {
+       /** @return font ID */
+       boost::optional<std::string> font () const {
                return _font;
        }
 
@@ -122,8 +123,8 @@ public:
        }
 
 private:
-       /** font name */
-       std::string _font;
+       /** font ID */
+       boost::optional<std::string> _font;
        /** true if the text is italic */
        bool _italic;
        /** text colour */
index 6145a5cdb292dff2ea4ac9db4ddd186b9501c259..6b030e35a26ea807c8c4f5df445337d62c66981e 100644 (file)
@@ -29,8 +29,9 @@ def build(bld):
                  font.cc
                  gamma_lut.cc
                  image.cc
+                 interop_load_font.cc
+                 interop_subtitle_content.cc
                  key.cc
-                 load_font.cc
                  local_time.cc
                  metadata.cc
                  mono_picture_mxf.cc
@@ -51,6 +52,8 @@ def build(bld):
                  reel_subtitle_asset.cc
                  rgb_xyz.cc
                  signer.cc
+                 smpte_load_font.cc
+                 smpte_subtitle_content.cc
                  sound_mxf.cc
                  sound_mxf_writer.cc
                  sound_frame.cc
index f3f4baf979be5ac33c8ceb8feaee75a26cf064b8..e5d6c19c9cc46fe9dbf58ab95230a0b95ad87170 100644 (file)
@@ -1,5 +1,24 @@
+/*
+    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include "interop_subtitle_content.h"
 #include <iostream>
-#include "subtitle_content.h"
 
 using namespace std;
 
@@ -10,7 +29,7 @@ int main (int argc, char* argv[])
                exit (EXIT_FAILURE);
        }
        
-       dcp::SubtitleContent s (argv[1], false);
+       dcp::InteropSubtitleContent s (argv[1]);
        cout << s.xml_as_string ();
        return 0;
 }
index f90218e557621d978543774f9bd748cf6c43a01e..411b61204b191789ed43b8c102fd10e44a6671cb 100644 (file)
 
 */
 
-#include "subtitle_content.h"
+#include "interop_subtitle_content.h"
 #include "subtitle_string.h"
 #include <boost/test/unit_test.hpp>
 
 using std::list;
+using std::string;
 using boost::shared_ptr;
 
 /* Load some subtitle content from XML and check that it is read correctly */
 BOOST_AUTO_TEST_CASE (subtitles1)
 {
-       dcp::SubtitleContent subs ("test/data/subs1.xml", false);
+       dcp::InteropSubtitleContent subs ("test/data/subs1.xml");
 
        BOOST_CHECK_EQUAL (subs.language(), "French");
 
        list<dcp::SubtitleString> s = subs.subtitles_at (dcp::Time (0, 0, 6, 1));
        BOOST_CHECK_EQUAL (s.size(), 1);
        BOOST_CHECK_EQUAL (s.front(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFontId"),
                                   false,
                                   dcp::Color (255, 255, 255),
                                   39,
@@ -52,7 +53,7 @@ BOOST_AUTO_TEST_CASE (subtitles1)
        s = subs.subtitles_at (dcp::Time (0, 0, 7, 190));
        BOOST_CHECK_EQUAL (s.size(), 2);
        BOOST_CHECK_EQUAL (s.front(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFontId"),
                                   true,
                                   dcp::Color (255, 255, 255),
                                   39,
@@ -67,7 +68,7 @@ BOOST_AUTO_TEST_CASE (subtitles1)
                                   dcp::Time (0, 0, 0, 1)
                                   ));
        BOOST_CHECK_EQUAL (s.back(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFontId"),
                                   false,
                                   dcp::Color (255, 255, 255),
                                   39,
@@ -85,7 +86,7 @@ BOOST_AUTO_TEST_CASE (subtitles1)
        s = subs.subtitles_at (dcp::Time (0, 0, 11, 95));
        BOOST_CHECK_EQUAL (s.size(), 1);
        BOOST_CHECK_EQUAL (s.back(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFontId"),
                                   false,
                                   dcp::Color (255, 255, 255),
                                   39,
@@ -103,7 +104,7 @@ BOOST_AUTO_TEST_CASE (subtitles1)
        s = subs.subtitles_at (dcp::Time (0, 0, 14, 42));
        BOOST_CHECK_EQUAL (s.size(), 1);
        BOOST_CHECK_EQUAL (s.back(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFontId"),
                                   false,
                                   dcp::Color (255, 255, 255),
                                   39,
@@ -122,12 +123,12 @@ BOOST_AUTO_TEST_CASE (subtitles1)
 /** And similarly for another one */
 BOOST_AUTO_TEST_CASE (subtitles2)
 {
-       dcp::SubtitleContent subs ("test/data/subs2.xml", false);
+       dcp::InteropSubtitleContent subs ("test/data/subs2.xml");
 
        list<dcp::SubtitleString> s = subs.subtitles_at (dcp::Time (0, 0, 42, 100));
        BOOST_CHECK_EQUAL (s.size(), 2);
        BOOST_CHECK_EQUAL (s.front(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   true,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -142,7 +143,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
                                   dcp::Time (0, 0, 0, 0)
                                   ));
        BOOST_CHECK_EQUAL (s.back(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   true,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -160,7 +161,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
        s = subs.subtitles_at (dcp::Time (0, 0, 50, 50));
        BOOST_CHECK_EQUAL (s.size(), 2);
        BOOST_CHECK_EQUAL (s.front(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   true,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -175,7 +176,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
                                   dcp::Time (0, 0, 0, 0)
                                   ));
        BOOST_CHECK_EQUAL (s.back(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   true,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -193,7 +194,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
        s = subs.subtitles_at (dcp::Time (0, 1, 2, 300));
        BOOST_CHECK_EQUAL (s.size(), 2);
        BOOST_CHECK_EQUAL (s.front(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   true,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -208,7 +209,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
                                   dcp::Time (0, 0, 0, 0)
                                   ));
        BOOST_CHECK_EQUAL (s.back(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   true,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -226,7 +227,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
        s = subs.subtitles_at (dcp::Time (0, 1, 15, 50));
        BOOST_CHECK_EQUAL (s.size(), 2);
        BOOST_CHECK_EQUAL (s.front(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   true,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -241,7 +242,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
                                   dcp::Time (0, 0, 0, 0)
                                   ));
        BOOST_CHECK_EQUAL (s.back(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   true,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -259,7 +260,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
        s = subs.subtitles_at (dcp::Time (0, 1, 27, 200));
        BOOST_CHECK_EQUAL (s.size(), 2);
        BOOST_CHECK_EQUAL (s.front(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   true,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -274,7 +275,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
                                   dcp::Time (0, 0, 0, 0)
                                   ));
        BOOST_CHECK_EQUAL (s.back(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   true,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -292,7 +293,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
        s = subs.subtitles_at (dcp::Time (0, 1, 42, 300));
        BOOST_CHECK_EQUAL (s.size(), 2);
        BOOST_CHECK_EQUAL (s.front(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   false,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -307,7 +308,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
                                   dcp::Time (0, 0, 0, 0)
                                   ));
        BOOST_CHECK_EQUAL (s.back(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   false,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -325,7 +326,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
        s = subs.subtitles_at (dcp::Time (0, 1, 45, 200));
        BOOST_CHECK_EQUAL (s.size(), 2);
        BOOST_CHECK_EQUAL (s.front(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   false,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -340,7 +341,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
                                   dcp::Time (0, 0, 0, 0)
                                   ));
        BOOST_CHECK_EQUAL (s.back(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   false,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -358,7 +359,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
        s = subs.subtitles_at (dcp::Time (0, 1, 47, 249));
        BOOST_CHECK_EQUAL (s.size(), 2);
        BOOST_CHECK_EQUAL (s.front(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   false,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -373,7 +374,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
                                   dcp::Time (0, 0, 0, 0)
                                   ));
        BOOST_CHECK_EQUAL (s.back(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   false,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -391,7 +392,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
        s = subs.subtitles_at (dcp::Time (0, 2, 6, 210));
        BOOST_CHECK_EQUAL (s.size(), 2);
        BOOST_CHECK_EQUAL (s.front(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   true,
                                   dcp::Color (255, 255, 255),
                                   42,
@@ -406,7 +407,7 @@ BOOST_AUTO_TEST_CASE (subtitles2)
                                   dcp::Time (0, 0, 0, 0)
                                   ));
        BOOST_CHECK_EQUAL (s.back(), dcp::SubtitleString (
-                                  "Arial",
+                                  string ("theFont"),
                                   true,
                                   dcp::Color (255, 255, 255),
                                   42,
index a705d5f4cdfdba7deb375727afd259067c87dc27..0bf0df08e890947576edeab5a4b70f3acb47239f 100644 (file)
@@ -85,7 +85,7 @@ main_subtitle (shared_ptr<Reel> reel, bool list_subtitles)
                for (list<SubtitleString>::const_iterator k = subs.begin(); k != subs.end(); ++k) {
                        cout << "        " << k->text() << "\n";
                        cout << "          "
-                            << "font:" << k->font() << "; "
+                            << "font:" << k->font().get_value_or("[default]") << "; "
                             << "italic:" << k->italic() << "; "
                             << "color:" << k->color() << "; "
                             << "in:" << k->in() << "; "