Rename some variables.
[dcpomatic.git] / src / lib / text_decoder.cc
1 /*
2     Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 #include "compose.hpp"
23 #include "log.h"
24 #include "text_content.h"
25 #include "text_decoder.h"
26 #include "util.h"
27 #include <sub/subtitle.h>
28 #include <boost/algorithm/string.hpp>
29 #include <iostream>
30
31
32 using std::cout;
33 using std::max;
34 using std::min;
35 using std::shared_ptr;
36 using std::string;
37 using std::vector;
38 using boost::optional;
39 using namespace dcpomatic;
40
41
42 TextDecoder::TextDecoder (
43         Decoder* parent,
44         shared_ptr<const TextContent> content,
45         ContentTime first
46         )
47         : DecoderPart (parent)
48         , _content (content)
49         , _position (first)
50 {
51
52 }
53
54
55 /** Called by subclasses when an image subtitle is starting.
56  *  @param from From time of the subtitle.
57  *  @param image Subtitle image.
58  *  @param rect Area expressed as a fraction of the video frame that this subtitle
59  *  is for (e.g. a width of 0.5 means the width of the subtitle is half the width
60  *  of the video frame)
61  */
62 void
63 TextDecoder::emit_bitmap_start (ContentBitmapText const& bitmap)
64 {
65         BitmapStart (bitmap);
66         _position = bitmap.from();
67 }
68
69
70 static
71 string
72 escape_text (string text)
73 {
74         /* We must escape some things, otherwise they might confuse our subtitle
75            renderer (which uses entities and some HTML-esque markup to do bold/italic etc.)
76         */
77         boost::algorithm::replace_all(text, "&", "&amp;");
78         boost::algorithm::replace_all(text, "<", "&lt;");
79         boost::algorithm::replace_all(text, ">", "&gt;");
80         return text;
81 }
82
83
84 static
85 void
86 set_forced_appearance(shared_ptr<const TextContent> content, dcp::SubtitleString& subtitle)
87 {
88         if (content->colour()) {
89                 subtitle.set_colour(*content->colour());
90         }
91         if (content->effect_colour()) {
92                 subtitle.set_effect_colour(*content->effect_colour());
93         }
94         if (content->effect()) {
95                 subtitle.set_effect(*content->effect());
96         }
97         if (content->fade_in()) {
98                 subtitle.set_fade_up_time(dcp::Time(content->fade_in()->seconds(), 1000));
99         }
100         if (content->fade_out()) {
101                 subtitle.set_fade_down_time (dcp::Time(content->fade_out()->seconds(), 1000));
102         }
103 }
104
105
106 void
107 TextDecoder::emit_plain_start (ContentTime from, vector<dcp::SubtitleString> subtitles)
108 {
109         for (auto& subtitle: subtitles) {
110                 subtitle.set_text(escape_text(subtitle.text()));
111                 set_forced_appearance(content(), subtitle);
112         }
113
114         PlainStart(ContentStringText(from, subtitles));
115         _position = from;
116 }
117
118
119 void
120 TextDecoder::emit_plain_start (ContentTime from, sub::Subtitle const & sub_subtitle)
121 {
122         /* See if our next subtitle needs to be vertically placed on screen by us */
123         bool needs_placement = false;
124         optional<int> bottom_line;
125         for (auto line: sub_subtitle.lines) {
126                 if (!line.vertical_position.reference || (line.vertical_position.line && !line.vertical_position.lines) || line.vertical_position.reference.get() == sub::TOP_OF_SUBTITLE) {
127                         needs_placement = true;
128                         if (!bottom_line || bottom_line.get() < line.vertical_position.line.get()) {
129                                 bottom_line = line.vertical_position.line.get();
130                         }
131                 }
132         }
133
134         /* Find the lowest proportional position */
135         optional<float> lowest_proportional;
136         for (auto line: sub_subtitle.lines) {
137                 if (line.vertical_position.proportional) {
138                         if (!lowest_proportional) {
139                                 lowest_proportional = line.vertical_position.proportional;
140                         } else {
141                                 lowest_proportional = min(lowest_proportional.get(), line.vertical_position.proportional.get());
142                         }
143                 }
144         }
145
146         vector<dcp::SubtitleString> dcp_subtitles;
147         for (auto line: sub_subtitle.lines) {
148                 for (auto block: line.blocks) {
149
150                         if (!block.font_size.specified()) {
151                                 /* Fallback default font size if no other has been specified */
152                                 block.font_size.set_points (48);
153                         }
154
155                         float v_position;
156                         dcp::VAlign v_align;
157                         if (needs_placement) {
158                                 DCPOMATIC_ASSERT (line.vertical_position.line);
159                                 double const multiplier = 1.2 * content()->line_spacing() * content()->y_scale() * block.font_size.proportional (72 * 11);
160                                 switch (line.vertical_position.reference.get_value_or(sub::BOTTOM_OF_SCREEN)) {
161                                 case sub::BOTTOM_OF_SCREEN:
162                                 case sub::TOP_OF_SUBTITLE:
163                                         /* This 1.015 is an arbitrary value to lift the bottom sub off the bottom
164                                            of the screen a bit to a pleasing degree.
165                                            */
166                                         v_position = 1.015 -
167                                                 (1 + bottom_line.get() - line.vertical_position.line.get()) * multiplier;
168
169                                         v_align = dcp::VAlign::TOP;
170                                         break;
171                                 case sub::TOP_OF_SCREEN:
172                                         /* This 0.1 is another fudge factor to bring the top line away from the top of the screen a little */
173                                         v_position = 0.12 + line.vertical_position.line.get() * multiplier;
174                                         v_align = dcp::VAlign::TOP;
175                                         break;
176                                 case sub::VERTICAL_CENTRE_OF_SCREEN:
177                                         v_position = line.vertical_position.line.get() * multiplier;
178                                         v_align = dcp::VAlign::CENTER;
179                                         break;
180                                 }
181                         } else {
182                                 DCPOMATIC_ASSERT (line.vertical_position.reference);
183                                 if (line.vertical_position.proportional) {
184                                         v_position = line.vertical_position.proportional.get();
185                                 } else {
186                                         DCPOMATIC_ASSERT (line.vertical_position.line);
187                                         DCPOMATIC_ASSERT (line.vertical_position.lines);
188                                         v_position = float(*line.vertical_position.line) / *line.vertical_position.lines;
189                                 }
190
191                                 if (lowest_proportional) {
192                                         /* Adjust line spacing */
193                                         v_position = ((v_position - lowest_proportional.get()) * content()->line_spacing()) + lowest_proportional.get();
194                                 }
195
196                                 switch (line.vertical_position.reference.get()) {
197                                 case sub::TOP_OF_SCREEN:
198                                         v_align = dcp::VAlign::TOP;
199                                         break;
200                                 case sub::VERTICAL_CENTRE_OF_SCREEN:
201                                         v_align = dcp::VAlign::CENTER;
202                                         break;
203                                 case sub::BOTTOM_OF_SCREEN:
204                                         v_align = dcp::VAlign::BOTTOM;
205                                         break;
206                                 default:
207                                         v_align = dcp::VAlign::TOP;
208                                         break;
209                                 }
210                         }
211
212                         dcp::HAlign h_align;
213                         float h_position = line.horizontal_position.proportional;
214                         switch (line.horizontal_position.reference) {
215                         case sub::LEFT_OF_SCREEN:
216                                 h_align = dcp::HAlign::LEFT;
217                                 h_position = max(h_position, 0.05f);
218                                 break;
219                         case sub::HORIZONTAL_CENTRE_OF_SCREEN:
220                                 h_align = dcp::HAlign::CENTER;
221                                 break;
222                         case sub::RIGHT_OF_SCREEN:
223                                 h_align = dcp::HAlign::RIGHT;
224                                 h_position = max(h_position, 0.05f);
225                                 break;
226                         default:
227                                 h_align = dcp::HAlign::CENTER;
228                                 break;
229                         }
230
231                         /* The idea here (rightly or wrongly) is that we set the appearance based on the
232                            values in the libsub objects, and these are overridden with values from the
233                            content by the other emit_plain_start() above.
234                         */
235
236                         dcp_subtitles.push_back(
237                                 dcp::SubtitleString (
238                                         string(TEXT_FONT_ID),
239                                         block.italic,
240                                         block.bold,
241                                         block.underline,
242                                         block.colour.dcp(),
243                                         block.font_size.points (72 * 11),
244                                         1.0,
245                                         dcp::Time (from.seconds(), 1000),
246                                         /* XXX: hmm; this is a bit ugly (we don't know the to time yet) */
247                                         dcp::Time (),
248                                         h_position,
249                                         h_align,
250                                         v_position,
251                                         v_align,
252                                         dcp::Direction::LTR,
253                                         block.text,
254                                         dcp::Effect::NONE,
255                                         block.effect_colour.get_value_or(sub::Colour(0, 0, 0)).dcp(),
256                                         /* Hack: we should use subtitle.fade_up and subtitle.fade_down here
257                                            but the times of these often don't have a frame rate associated
258                                            with them so the sub::Time won't convert them to milliseconds without
259                                            throwing an exception.  Since only DCP subs fill those in (and we don't
260                                            use libsub for DCP subs) we can cheat by just putting 0 in here.
261                                         */
262                                         dcp::Time (),
263                                         dcp::Time (),
264                                         0
265                                         )
266                                 );
267                 }
268         }
269
270         /* Pass these subs through the other emit_plain_start so that they get their forced settings applied */
271         emit_plain_start (from, dcp_subtitles);
272 }
273
274
275 void
276 TextDecoder::emit_stop (ContentTime to)
277 {
278         Stop (to);
279 }
280
281
282 void
283 TextDecoder::emit_plain (ContentTimePeriod period, vector<dcp::SubtitleString> subtitles)
284 {
285         emit_plain_start (period.from, subtitles);
286         emit_stop (period.to);
287 }
288
289
290 void
291 TextDecoder::emit_plain (ContentTimePeriod period, sub::Subtitle const& subtitles)
292 {
293         emit_plain_start (period.from, subtitles);
294         emit_stop (period.to);
295 }
296
297
298 /*  @param rect Area expressed as a fraction of the video frame that this subtitle
299  *  is for (e.g. a width of 0.5 means the width of the subtitle is half the width
300  *  of the video frame)
301  */
302 void
303 TextDecoder::emit_bitmap (ContentTimePeriod period, shared_ptr<const Image> image, dcpomatic::Rect<double> rect)
304 {
305         emit_bitmap_start ({ period.from, image, rect });
306         emit_stop (period.to);
307 }
308
309
310 void
311 TextDecoder::seek ()
312 {
313         _position = ContentTime ();
314 }