Fix STL italic / underline (from master).
[libsub.git] / src / stl_binary_writer.cc
index cb6c71a67c547c6bd73d8abbb8d23d17831bb085..05a01dc8e5d4dabb2632f0c3b8e2d68bfb9c2e08 100644 (file)
 
 */
 
+/** @file  src/stl_binary_writer.cc
+ *  @brief Writer for STL binary files.
+ */
+
 #include "stl_binary_writer.h"
 #include "subtitle.h"
 #include "iso6937.h"
+#include "stl_util.h"
 #include "compose.hpp"
+#include "sub_assert.h"
 #include <boost/locale.hpp>
 #include <list>
 #include <cmath>
@@ -39,6 +45,15 @@ using std::cout;
 using boost::locale::conv::utf_to_utf;
 using namespace sub;
 
+/** Arbitrary number which to divide the screen into rows; e.g.
+ *  64 here would mean that there are 64 addressable vertical positions
+ *  on the screen, each 1/64th of the screen height tall.
+ *
+ *  The magic 23 makes our output agree more closely with
+ *  AnnotationEdit, which makes life easier when testing.
+ */
+static int const ROWS = 23;
+
 static void
 put_string (char* p, string s)
 {
@@ -48,8 +63,8 @@ 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 ());
 }
@@ -57,12 +72,12 @@ put_string (char* p, unsigned int n, string s)
 static void
 put_int_as_string (char* p, int v, unsigned int n)
 {
-       std::stringstream s;
+       locked_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);
+       SUB_ASSERT (s.str().length() == n);
        put_string (p, s.str ());
 }
 
@@ -96,34 +111,30 @@ sub::write_stl_binary (
        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);
-       
+       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 = new char[1024];
        memset (buffer, 0, 1024);
        ofstream output (file_name.string().c_str ());
        STLBinaryTables tables;
 
-       /* Find the longest subtitle in characters and the number of rows */
+       /* Find the longest subtitle in characters and the number of lines */
 
        int longest = 0;
+       int lines = 0;
 
-       set<float> check_top;
-       set<float> check_centre;
-       set<float> check_bottom;
-       set<int> check_rows;
-       
        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) {
                        int t = 0;
@@ -131,31 +142,14 @@ sub::write_stl_binary (
                                t += k->text.size ();
                        }
                        longest = max (longest, t);
-                       
-                       if (j->vertical_position.proportional) {
-                               switch (j->vertical_position.reference.get ()) {
-                               case TOP:
-                                       check_top.insert (j->vertical_position.proportional.get ());
-                                       break;
-                               case CENTRE:
-                                       check_centre.insert (j->vertical_position.proportional.get ());
-                                       break;
-                               case BOTTOM:
-                                       check_bottom.insert (j->vertical_position.proportional.get ());
-                                       break;
-                               }
-                       } else {
-                               check_rows.insert (j->vertical_position.line.get ());
-                       }
+                       ++lines;
                }
        }
 
-       int const rows = check_top.size() + check_centre.size() + check_bottom.size() + check_rows.size();
-       
        /* Code page: 850 */
        put_string (buffer + 0, "850");
        /* Disk format code */
-       put_string (buffer + 3, String::compose ("STL%1.01", rint (frames_per_second)));
+       put_string (buffer + 3, stl_frame_rate_to_dfc (frames_per_second));
        /* Display standard code: open subtitling */
        put_string (buffer + 11, "0");
        /* Character code table: Latin (ISO 6937) */
@@ -173,15 +167,15 @@ 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, subtitles.size (), 5);
+       put_int_as_string (buffer + 238, lines, 5);
        /* Total number of subtitles */
-       put_int_as_string (buffer + 243, subtitles.size (), 5);
+       put_int_as_string (buffer + 243, lines, 5);
        /* Total number of subtitle groups */
        put_string (buffer + 248, "001");
        /* Maximum number of displayable characters in any text row */
        put_int_as_string (buffer + 251, longest, 2);
        /* Maximum number of displayable rows */
-       put_int_as_string (buffer + 253, rows, 2);
+       put_int_as_string (buffer + 253, ROWS, 2);
        /* Time code status */
        put_string (buffer + 255, "1");
        /* Start-of-programme time code */
@@ -199,46 +193,76 @@ sub::write_stl_binary (
 
        output.write (buffer, 1024);
 
-       int N = 0;
        for (list<Subtitle>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
-
-               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.frame(frames_per_second).hours (), 1);
-               put_int_as_int (buffer + 6, i->from.frame(frames_per_second).minutes (), 1);
-               put_int_as_int (buffer + 7, i->from.frame(frames_per_second).seconds (), 1);
-               put_int_as_int (buffer + 8, i->from.frame(frames_per_second).frames (), 1);
-               /* Time code out */
-               put_int_as_int (buffer + 9, i->to.frame(frames_per_second).hours (), 1);
-               put_int_as_int (buffer + 10, i->to.frame(frames_per_second).minutes (), 1);
-               put_int_as_int (buffer + 11, i->to.frame(frames_per_second).seconds (), 1);
-               put_int_as_int (buffer + 12, i->to.frame(frames_per_second).frames (), 1);
-               /* Vertical position */
-               /* XXX */
-               put_int_as_int (buffer + 13, 0, 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;
-               
+               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";
@@ -259,18 +283,30 @@ sub::write_stl_binary (
                        }
 
                        text += "\x8A";
-               }
 
-               if (text.length() > 111) {
-                       text = text.substr (111);
-               }
+                       /* Turn italic/underline off before the end of this subtitle */
 
-               while (text.length() < 112) {
-                       text += "\x8F";
-               }
+                       if (underline) {
+                               text += "\x83";
+                       }
+
+                       if (italic) {
+                               text += "\x81";
+                       }
+
+                       if (text.length() > 111) {
+                               text = text.substr (111);
+                       }
 
-               put_string (buffer + 16, text);
-               output.write (buffer, 128);
+                       while (text.length() < 112) {
+                               text += "\x8F";
+                       }
+
+                       put_string (buffer + 16, text);
+                       output.write (buffer, 128);
+
+                       ++N;
+               }
        }
 
        delete[] buffer;