+
+struct SubtitleSorter
+{
+ bool operator() (shared_ptr<Subtitle> a, shared_ptr<Subtitle> b) {
+ if (a->in() != b->in()) {
+ return a->in() < b->in();
+ }
+ return a->v_position() < b->v_position();
+ }
+};
+
+void
+SubtitleAsset::pull_fonts (shared_ptr<order::Part> part)
+{
+ if (part->children.empty ()) {
+ return;
+ }
+
+ /* Pull up from children */
+ BOOST_FOREACH (shared_ptr<order::Part> i, part->children) {
+ pull_fonts (i);
+ }
+
+ if (part->parent) {
+ /* Establish the common font features that each of part's children have;
+ these features go into part's font.
+ */
+ part->font = part->children.front()->font;
+ BOOST_FOREACH (shared_ptr<order::Part> i, part->children) {
+ part->font.take_intersection (i->font);
+ }
+
+ /* Remove common values from part's children's fonts */
+ BOOST_FOREACH (shared_ptr<order::Part> i, part->children) {
+ i->font.take_difference (part->font);
+ }
+ }
+
+ /* Merge adjacent children with the same font */
+ list<shared_ptr<order::Part> >::const_iterator i = part->children.begin();
+ list<shared_ptr<order::Part> > merged;
+
+ while (i != part->children.end()) {
+
+ if ((*i)->font.empty ()) {
+ merged.push_back (*i);
+ ++i;
+ } else {
+ list<shared_ptr<order::Part> >::const_iterator j = i;
+ ++j;
+ while (j != part->children.end() && (*i)->font == (*j)->font) {
+ ++j;
+ }
+ if (std::distance (i, j) == 1) {
+ merged.push_back (*i);
+ ++i;
+ } else {
+ shared_ptr<order::Part> group (new order::Part (part, (*i)->font));
+ for (list<shared_ptr<order::Part> >::const_iterator k = i; k != j; ++k) {
+ (*k)->font.clear ();
+ group->children.push_back (*k);
+ }
+ merged.push_back (group);
+ i = j;
+ }
+ }
+ }
+
+ part->children = merged;
+}
+
+/** @param standard Standard (INTEROP or SMPTE); this is used rather than putting things in the child
+ * class because the differences between the two are fairly subtle.
+ */
+void
+SubtitleAsset::subtitles_as_xml (xmlpp::Element* xml_root, int time_code_rate, Standard standard) const
+{
+ list<shared_ptr<Subtitle> > sorted = _subtitles;
+ sorted.sort (SubtitleSorter ());
+
+ /* Gather our subtitles into a hierarchy of Subtitle/Text/String objects, writing
+ font information into the bottom level (String) objects.
+ */
+
+ shared_ptr<order::Part> root (new order::Part (shared_ptr<order::Part> ()));
+ shared_ptr<order::Subtitle> subtitle;
+ shared_ptr<order::Text> text;
+
+ Time last_in;
+ Time last_out;
+ Time last_fade_up_time;
+ Time last_fade_down_time;
+ HAlign last_h_align;
+ float last_h_position;
+ VAlign last_v_align;
+ float last_v_position;
+ Direction last_direction;
+
+ BOOST_FOREACH (shared_ptr<Subtitle> i, sorted) {
+ if (!subtitle ||
+ (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.reset (new order::Subtitle (root, i->in(), i->out(), i->fade_up_time(), i->fade_down_time()));
+ root->children.push_back (subtitle);
+
+ last_in = i->in ();
+ last_out = i->out ();
+ last_fade_up_time = i->fade_up_time ();
+ last_fade_down_time = i->fade_down_time ();
+ text.reset ();
+ }
+
+ shared_ptr<SubtitleString> is = dynamic_pointer_cast<SubtitleString>(i);
+ if (is) {
+ if (!text ||
+ last_h_align != is->h_align() ||
+ fabs(last_h_position - is->h_position()) > ALIGN_EPSILON ||
+ last_v_align != is->v_align() ||
+ fabs(last_v_position - is->v_position()) > ALIGN_EPSILON ||
+ last_direction != is->direction()
+ ) {
+ text.reset (new order::Text (subtitle, is->h_align(), is->h_position(), is->v_align(), is->v_position(), is->direction()));
+ subtitle->children.push_back (text);
+
+ last_h_align = is->h_align ();
+ last_h_position = is->h_position ();
+ last_v_align = is->v_align ();
+ last_v_position = is->v_position ();
+ last_direction = is->direction ();
+ }
+
+ text->children.push_back (shared_ptr<order::String> (new order::String (text, order::Font (is, standard), is->text())));
+ }
+
+ shared_ptr<SubtitleImage> ii = dynamic_pointer_cast<SubtitleImage>(i);
+ if (ii) {
+ text.reset ();
+ subtitle->children.push_back (
+ shared_ptr<order::Image> (new order::Image (subtitle, ii->id(), ii->png_image(), ii->h_align(), ii->h_position(), ii->v_align(), ii->v_position()))
+ );
+ }
+ }
+
+ /* Pull font changes as high up the hierarchy as we can */
+
+ pull_fonts (root);
+
+ /* Write XML */
+
+ order::Context context;
+ context.time_code_rate = time_code_rate;
+ context.standard = standard;
+ context.spot_number = 1;
+
+ root->write_xml (xml_root, context);
+}
+
+map<string, Data>
+SubtitleAsset::fonts_with_load_ids () const
+{
+ map<string, Data> out;
+ BOOST_FOREACH (Font const & i, _fonts) {
+ out[i.load_id] = i.data;
+ }
+ return out;
+}
+
+/** Replace empty IDs in any <LoadFontId> and <Font> tags with
+ * a dummy string. Some systems give errors with empty font IDs
+ * (see DCP-o-matic bug #1689).
+ */
+void
+SubtitleAsset::fix_empty_font_ids ()
+{
+ bool have_empty = false;
+ list<string> ids;
+ BOOST_FOREACH (shared_ptr<LoadFontNode> i, load_font_nodes()) {
+ if (i->id == "") {
+ have_empty = true;
+ } else {
+ ids.push_back (i->id);
+ }
+ }
+
+ if (!have_empty) {
+ return;
+ }
+
+ string const empty_id = unique_string (ids, "font");
+
+ BOOST_FOREACH (shared_ptr<LoadFontNode> i, load_font_nodes()) {
+ if (i->id == "") {
+ i->id = empty_id;
+ }
+ }
+
+ BOOST_FOREACH (shared_ptr<Subtitle> i, _subtitles) {
+ shared_ptr<SubtitleString> j = dynamic_pointer_cast<SubtitleString> (i);
+ if (j && j->font() && j->font().get() == "") {
+ j->set_font (empty_id);
+ }
+ }
+}