Remove use of boost foreach.
[libsub.git] / src / stl_binary_writer.cc
index b9bd7e01783c5c1894c6cabf9a865adde88f4c52..1b1d52f99226f4c75e81cb46f1e6cfcfc7682dd0 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2014-2020 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
 #include "iso6937.h"
 #include "stl_util.h"
 #include "compose.hpp"
+#include "sub_assert.h"
 #include <boost/locale.hpp>
-#include <list>
+#include <boost/algorithm/string.hpp>
 #include <cmath>
 #include <fstream>
 #include <iomanip>
 #include <set>
+#include <vector>
 
-using std::list;
 using std::set;
 using std::ofstream;
 using std::string;
@@ -41,7 +42,9 @@ using std::setw;
 using std::setfill;
 using std::max;
 using std::cout;
+using std::vector;
 using boost::locale::conv::utf_to_utf;
+using boost::optional;
 using namespace sub;
 
 /** Arbitrary number which to divide the screen into rows; e.g.
@@ -62,22 +65,38 @@ put_string (char* p, string s)
 static void
 put_string (char* p, unsigned int n, string s)
 {
-       assert (s.length() <= n);
+       SUB_ASSERT (s.length() <= n);
 
        memcpy (p, s.c_str (), s.length ());
        memset (p + s.length(), ' ', n - s.length ());
 }
 
+/** @param v Value
+ *  @param n Width to zero-pad v to.
+ */
 static void
 put_int_as_string (char* p, int v, unsigned int n)
 {
-       std::stringstream s;
-       /* Be careful to ensure we get no thousands separators */
-       s.imbue (std::locale::classic ());
-       s << setw (n) << setfill ('0');
-       s << v;
-       assert (s.str().length() == n);
-       put_string (p, s.str ());
+       char buffer[64];
+
+       switch (n) {
+       case 2:
+               snprintf (buffer, sizeof(buffer), "%02d", v);
+               break;
+       case 5:
+               snprintf (buffer, sizeof(buffer), "%05d", v);
+               break;
+       default:
+               SUB_ASSERT (false);
+       }
+
+       string s = buffer;
+
+       struct lconv* lc = localeconv ();
+       boost::algorithm::replace_all (s, lc->thousands_sep, "");
+       boost::algorithm::replace_all (s, lc->decimal_point, ".");
+
+       put_string (p, s);
 }
 
 static void
@@ -88,63 +107,238 @@ put_int_as_int (char* p, int v, unsigned int n)
        }
 }
 
+static int
+vertical_position (sub::Line const & line)
+{
+       int vp = 0;
+       if (line.vertical_position.proportional) {
+               switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
+               case TOP_OF_SCREEN:
+                       vp = rint (line.vertical_position.proportional.get() * ROWS);
+                       break;
+               case VERTICAL_CENTRE_OF_SCREEN:
+                       vp = rint (line.vertical_position.proportional.get() * ROWS + (ROWS / 2.0));
+                       break;
+               case BOTTOM_OF_SCREEN:
+                       vp = rint (ROWS - (line.vertical_position.proportional.get() * ROWS));
+                       break;
+               default:
+                       break;
+               }
+       } else if (line.vertical_position.line) {
+               float const prop = float (line.vertical_position.line.get()) / line.vertical_position.lines.get ();
+               switch (line.vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
+               case TOP_OF_SCREEN:
+                       vp = prop * ROWS;
+                       break;
+               case VERTICAL_CENTRE_OF_SCREEN:
+                       vp = (prop + 0.5) * ROWS;
+                       break;
+               case BOTTOM_OF_SCREEN:
+                       vp = (1 - prop) * ROWS;
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       return vp;
+}
+
+vector<char*>
+make_tti_blocks (vector<Subtitle> const& subtitles, STLBinaryTables const& tables, float frames_per_second)
+{
+       static int const tti_size = 128;
+       vector<char*> tti;
+
+       /* Buffer to build the TTI blocks in */
+       char buffer[tti_size];
+
+       for (auto const& i: subtitles) {
+
+               /* Find the top vertical position of this subtitle */
+               optional<int> top;
+               for (auto const& j: i.lines) {
+                       int const vp = vertical_position (j);
+                       if (!top || vp < top.get ()) {
+                               top = vp;
+                       }
+               }
+
+               /* Work out the text */
+               string text;
+               bool italic = false;
+               bool underline = false;
+               optional<int> last_vp;
+
+               for (auto const& j: i.lines) {
+
+                       /* CR/LF down to this line */
+                       int const vp = vertical_position (j);
+
+                       if (last_vp) {
+                               for (int k = last_vp.get(); k < vp; ++k) {
+                                       text += "\x8A";
+                               }
+                       }
+
+                       last_vp = vp;
+
+                       for (auto const& k: j.blocks) {
+                               if (k.underline && !underline) {
+                                       text += "\x82";
+                                       underline = true;
+                               } else if (underline && !k.underline) {
+                                       text += "\x83";
+                                       underline = false;
+                               }
+                               if (k.italic && !italic) {
+                                       text += "\x80";
+                                       italic = true;
+                               } else if (italic && !k.italic) {
+                                       text += "\x81";
+                                       italic = false;
+                               }
+
+                               text += utf16_to_iso6937 (utf_to_utf<wchar_t> (k.text));
+                       }
+               }
+
+               /* Turn italic/underline off before the end of this subtitle */
+               if (underline) {
+                       text += "\x83";
+               }
+               if (italic) {
+                       text += "\x81";
+               }
+
+               /* Make sure there's at least one end-of-line */
+               text += "\x8F";
+
+               /* Now write this text in 112 byte chunks (TTI blocks).  Only the first TTI
+                  block's cumulative status, timecodes, vertical position, justification code
+                  and comment flag are taken into account by the reader.
+                  */
+
+               /* Set up the first part of the block */
+
+               /* XXX: these should increment, surely! */
+               /* Subtitle group number */
+               put_int_as_int (buffer + 0, 1, 1);
+               /* Subtitle number */
+               put_int_as_int (buffer + 1, 0, 2);
+               /* Cumulative status */
+               put_int_as_int (buffer + 4, tables.cumulative_status_enum_to_file (CUMULATIVE_STATUS_NOT_CUMULATIVE), 1);
+               /* Time code in */
+               put_int_as_int (buffer + 5, i.from.hours(), 1);
+               put_int_as_int (buffer + 6, i.from.minutes(), 1);
+               put_int_as_int (buffer + 7, i.from.seconds(), 1);
+               put_int_as_int (buffer + 8, i.from.frames_at(sub::Rational(frames_per_second * 1000, 1000)), 1);
+               /* Time code out */
+               put_int_as_int (buffer + 9, i.to.hours(), 1);
+               put_int_as_int (buffer + 10, i.to.minutes(), 1);
+               put_int_as_int (buffer + 11, i.to.seconds(), 1);
+               put_int_as_int (buffer + 12, i.to.frames_at(sub::Rational(frames_per_second * 1000, 1000)), 1);
+               /* Vertical position */
+               put_int_as_int (buffer + 13, top.get(), 1);
+
+               /* Justification code */
+               /* XXX: this assumes the first line has the right value */
+               switch (i.lines.front().horizontal_position.reference) {
+                       case LEFT_OF_SCREEN:
+                               put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_LEFT), 1);
+                               break;
+                       case HORIZONTAL_CENTRE_OF_SCREEN:
+                               put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_CENTRE), 1);
+                               break;
+                       case RIGHT_OF_SCREEN:
+                               put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_RIGHT), 1);
+                               break;
+               }
+
+               /* Comment flag */
+               put_int_as_int (buffer + 15, tables.comment_enum_to_file (COMMENT_NO), 1);
+
+               /* Now make as many blocks as are needed to add all the text */
+               size_t const block_size = 112;
+               size_t offset = 0;
+               int block_number = 0;
+               while (offset < text.length()) {
+                       size_t this_time = std::min(block_size, text.length() - offset);
+                       put_string (buffer + 16, text.substr(offset, this_time) + string(block_size - this_time, '\x8f'));
+                       offset += this_time;
+
+                       /* Extension block number.  Count up from 0 but use 0xff for the last one */
+                       put_int_as_int (buffer + 3, offset == text.length() ? 0xff : block_number, 1);
+                       ++block_number;
+
+                       char* finished = new char[tti_size];
+                       memcpy (finished, buffer, tti_size);
+                       tti.push_back (finished);
+               }
+       }
+
+       return tti;
+}
+
+
+
 /** @param language ISO 3-character country code for the language of the subtitles */
 void
 sub::write_stl_binary (
-       list<Subtitle> subtitles,
-       float frames_per_second,
-       Language language,
-       string original_programme_title,
-       string original_episode_title,
-       string translated_programme_title,
-       string translated_episode_title,
-       string translator_name,
-       string translator_contact_details,
-       string creation_date,
-       string revision_date,
-       int revision_number,
-       string country_of_origin,
-       string publisher,
-       string editor_name,
-       string editor_contact_details,
-       boost::filesystem::path file_name
-       )
+               vector<Subtitle> subtitles,
+               float frames_per_second,
+               Language language,
+               string original_programme_title,
+               string original_episode_title,
+               string translated_programme_title,
+               string translated_episode_title,
+               string translator_name,
+               string translator_contact_details,
+               string creation_date,
+               string revision_date,
+               int revision_number,
+               string country_of_origin,
+               string publisher,
+               string editor_name,
+               string editor_contact_details,
+               boost::filesystem::path file_name
+               )
 {
-       assert (original_programme_title.size() <= 32);
-       assert (original_episode_title.size() <= 32);
-       assert (translated_programme_title.size() <= 32);
-       assert (translated_episode_title.size() <= 32);
-       assert (translator_name.size() <= 32);
-       assert (translator_contact_details.size() <= 32);
-       assert (creation_date.size() == 6);
-       assert (revision_date.size() == 6);
-       assert (revision_number <= 99);
-       assert (country_of_origin.size() == 3);
-       assert (publisher.size() <= 32);
-       assert (editor_name.size() <= 32);
-       assert (editor_contact_details.size() <= 32);
-
-       char* buffer = new char[1024];
+       SUB_ASSERT (original_programme_title.size() <= 32);
+       SUB_ASSERT (original_episode_title.size() <= 32);
+       SUB_ASSERT (translated_programme_title.size() <= 32);
+       SUB_ASSERT (translated_episode_title.size() <= 32);
+       SUB_ASSERT (translator_name.size() <= 32);
+       SUB_ASSERT (translator_contact_details.size() <= 32);
+       SUB_ASSERT (creation_date.size() == 6);
+       SUB_ASSERT (revision_date.size() == 6);
+       SUB_ASSERT (revision_number <= 99);
+       SUB_ASSERT (country_of_origin.size() == 3);
+       SUB_ASSERT (publisher.size() <= 32);
+       SUB_ASSERT (editor_name.size() <= 32);
+       SUB_ASSERT (editor_contact_details.size() <= 32);
+
+       char buffer[1024];
        memset (buffer, 0, 1024);
-       ofstream output (file_name.string().c_str ());
        STLBinaryTables tables;
 
-       /* Find the longest subtitle in characters and the number of lines */
+       /* Find the longest subtitle in characters */
 
        int longest = 0;
-       int lines = 0;
 
-       for (list<Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
-               for (list<Line>::const_iterator j = i->lines.begin(); j != i->lines.end(); ++j) {
+       for (auto const& i: subtitles) {
+               for (auto const& j: i.lines) {
                        int t = 0;
-                       for (list<Block>::const_iterator k = j->blocks.begin(); k != j->blocks.end(); ++k) {
-                               t += k->text.size ();
+                       for (auto const& k: j.blocks) {
+                               t += k.text.size ();
                        }
-                       longest = max (longest, t);
-                       ++lines;
+                       longest = std::max (longest, t);
                }
        }
 
+       vector<char*> tti_blocks = make_tti_blocks (subtitles, tables, frames_per_second);
+
        /* Code page: 850 */
        put_string (buffer + 0, "850");
        /* Disk format code */
@@ -166,9 +360,9 @@ sub::write_stl_binary (
        put_string (buffer + 230, revision_date);
        put_int_as_string (buffer + 236, revision_number, 2);
        /* TTI blocks */
-       put_int_as_string (buffer + 238, lines, 5);
+       put_int_as_string (buffer + 238, tti_blocks.size(), 5);
        /* Total number of subtitles */
-       put_int_as_string (buffer + 243, lines, 5);
+       put_int_as_string (buffer + 243, subtitles.size(), 5);
        /* Total number of subtitle groups */
        put_string (buffer + 248, "001");
        /* Maximum number of displayable characters in any text row */
@@ -190,113 +384,10 @@ sub::write_stl_binary (
        put_string (buffer + 309, 32, editor_name);
        put_string (buffer + 341, 32, editor_contact_details);
 
+       ofstream output (file_name.string().c_str());
        output.write (buffer, 1024);
-
-       for (list<Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
-               int N = 0;
-               for (list<Line>::const_iterator j = i->lines.begin(); j != i->lines.end(); ++j) {
-
-                       memset (buffer, 0, 1024);
-
-                       /* Subtitle group number */
-                       put_int_as_int (buffer + 0, 1, 1);
-                       /* Subtitle number */
-                       put_int_as_int (buffer + 1, N, 2);
-                       /* Extension block number.  Use 0xff here to indicate that it is the last TTI
-                          block in this subtitle "set", as we only ever use one.
-                       */
-                       put_int_as_int (buffer + 3, 255, 1);
-                       /* Cumulative status */
-                       put_int_as_int (buffer + 4, tables.cumulative_status_enum_to_file (CUMULATIVE_STATUS_NOT_CUMULATIVE), 1);
-                       /* Time code in */
-                       put_int_as_int (buffer + 5, i->from.hours (), 1);
-                       put_int_as_int (buffer + 6, i->from.minutes (), 1);
-                       put_int_as_int (buffer + 7, i->from.seconds (), 1);
-                       put_int_as_int (buffer + 8, i->from.frames_at (sub::Rational (frames_per_second * 1000, 1000)), 1);
-                       /* Time code out */
-                       put_int_as_int (buffer + 9, i->to.hours (), 1);
-                       put_int_as_int (buffer + 10, i->to.minutes (), 1);
-                       put_int_as_int (buffer + 11, i->to.seconds (), 1);
-                       put_int_as_int (buffer + 12, i->to.frames_at (sub::Rational (frames_per_second * 1000, 1000)), 1);
-                       /* Vertical position */
-                       int vp = 0;
-                       if (j->vertical_position.proportional) {
-                               switch (j->vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
-                               case TOP_OF_SCREEN:
-                                       vp = rint (j->vertical_position.proportional.get() * ROWS);
-                                       break;
-                               case CENTRE_OF_SCREEN:
-                                       vp = rint (j->vertical_position.proportional.get() * ROWS + (ROWS / 2.0));
-                                       break;
-                               case BOTTOM_OF_SCREEN:
-                                       vp = rint (ROWS - (j->vertical_position.proportional.get() * ROWS));
-                                       break;
-                               default:
-                                       break;
-                               }
-                       } else if (j->vertical_position.line) {
-                               float const prop = float (j->vertical_position.line.get()) / j->vertical_position.lines.get ();
-                               switch (j->vertical_position.reference.get_value_or (TOP_OF_SCREEN)) {
-                               case TOP_OF_SCREEN:
-                                       vp = prop * ROWS;
-                                       break;
-                               case CENTRE_OF_SCREEN:
-                                       vp = (prop + 0.5) * ROWS;
-                                       break;
-                               case BOTTOM_OF_SCREEN:
-                                       vp = (1 - prop) * ROWS;
-                                       break;
-                               default:
-                                       break;
-                               }
-                       }
-                       put_int_as_int (buffer + 13, vp, 1);
-                       /* Justification code */
-                       /* XXX */
-                       put_int_as_int (buffer + 14, tables.justification_enum_to_file (JUSTIFICATION_NONE), 1);
-                       /* Comment flag */
-                       put_int_as_int (buffer + 15, tables.comment_enum_to_file (COMMENT_NO), 1);
-
-                       /* Text */
-                       string text;
-                       bool italic = false;
-                       bool underline = false;
-
-                       for (list<Block>::const_iterator k = j->blocks.begin(); k != j->blocks.end(); ++k) {
-                               if (k->underline && !underline) {
-                                       text += "\x82";
-                                       underline = true;
-                               } else if (underline && !k->underline) {
-                                       text += "\x83";
-                                       underline = false;
-                               }
-                               if (k->italic && !italic) {
-                                       text += "\x80";
-                                       italic = true;
-                               } else if (italic && !k->italic) {
-                                       text += "\x81";
-                                       italic = false;
-                               }
-
-                               text += utf16_to_iso6937 (utf_to_utf<wchar_t> (k->text));
-                       }
-
-                       text += "\x8A";
-
-                       if (text.length() > 111) {
-                               text = text.substr (111);
-                       }
-
-                       while (text.length() < 112) {
-                               text += "\x8F";
-                       }
-
-                       put_string (buffer + 16, text);
-                       output.write (buffer, 128);
-
-                       ++N;
-               }
+       for (auto i: tti_blocks) {
+               output.write (i, 128);
+               delete[] i;
        }
-
-       delete[] buffer;
 }