f1674789650742f5b85da7b33b149b91fec337f5
[libdcp.git] / src / subtitle_asset_internal.cc
1 /*
2     Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp 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     libdcp 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 libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34
35 /** @file  src/subtitle_asset_internal.cc
36  *  @brief Internal SubtitleAsset helpers
37  */
38
39
40 #include "subtitle_asset_internal.h"
41 #include "subtitle_string.h"
42 #include "compose.hpp"
43 #include <cmath>
44
45
46 using std::string;
47 using std::map;
48 using std::shared_ptr;
49 using namespace dcp;
50
51
52 string
53 order::Context::xmlns () const
54 {
55         return standard == Standard::SMPTE ? "dcst" : "";
56 }
57
58
59 order::Font::Font (shared_ptr<SubtitleString> s, Standard standard)
60 {
61         if (s->font()) {
62                 if (standard == Standard::SMPTE) {
63                         _values["ID"] = s->font().get ();
64                 } else {
65                         _values["Id"] = s->font().get ();
66                 }
67         }
68         _values["Italic"] = s->italic() ? "yes" : "no";
69         _values["Color"] = s->colour().to_argb_string();
70         _values["Size"] = raw_convert<string> (s->size());
71         _values["AspectAdjust"] = raw_convert<string>(s->aspect_adjust(), 1, true);
72         _values["Effect"] = effect_to_string (s->effect());
73         _values["EffectColor"] = s->effect_colour().to_argb_string();
74         _values["Script"] = "normal";
75         if (standard == Standard::SMPTE) {
76                 _values["Underline"] = s->underline() ? "yes" : "no";
77         } else {
78                 _values["Underlined"] = s->underline() ? "yes" : "no";
79         }
80         _values["Weight"] = s->bold() ? "bold" : "normal";
81 }
82
83
84 xmlpp::Element*
85 order::Font::as_xml (xmlpp::Element* parent, Context& context) const
86 {
87         xmlpp::Element* e = parent->add_child ("Font", context.xmlns());
88         for (map<string, string>::const_iterator i = _values.begin(); i != _values.end(); ++i) {
89                 e->set_attribute (i->first, i->second);
90         }
91         return e;
92 }
93
94
95 /** Modify our values so that they contain only those that are common to us and
96  *  other.
97  */
98 void
99 order::Font::take_intersection (Font other)
100 {
101         map<string, string> inter;
102
103         for (auto const& i: other._values) {
104                 auto t = _values.find (i.first);
105                 if (t != _values.end() && t->second == i.second) {
106                         inter.insert (i);
107                 }
108         }
109
110         _values = inter;
111 }
112
113
114 /** Modify our values so that it contains only those keys that are not in other */
115 void
116 order::Font::take_difference (Font other)
117 {
118         map<string, string> diff;
119         for (auto const& i: _values) {
120                 if (other._values.find (i.first) == other._values.end()) {
121                         diff.insert (i);
122                 }
123         }
124
125         _values = diff;
126 }
127
128
129 bool
130 order::Font::empty () const
131 {
132         return _values.empty ();
133 }
134
135
136 xmlpp::Element*
137 order::Part::as_xml (xmlpp::Element* parent, Context &) const
138 {
139         return parent;
140 }
141
142
143 xmlpp::Element*
144 order::String::as_xml (xmlpp::Element* parent, Context &) const
145 {
146         parent->add_child_text (text);
147         return 0;
148 }
149
150
151 void
152 order::Part::write_xml (xmlpp::Element* parent, order::Context& context) const
153 {
154         if (!font.empty ()) {
155                 parent = font.as_xml (parent, context);
156         }
157
158         parent = as_xml (parent, context);
159
160         for (auto i: children) {
161                 i->write_xml (parent, context);
162         }
163 }
164
165
166 static void
167 position_align (xmlpp::Element* e, order::Context& context, HAlign h_align, float h_position, VAlign v_align, float v_position)
168 {
169         if (h_align != HAlign::CENTER) {
170                 if (context.standard == Standard::SMPTE) {
171                         e->set_attribute ("Halign", halign_to_string (h_align));
172                 } else {
173                         e->set_attribute ("HAlign", halign_to_string (h_align));
174                 }
175         }
176
177         if (fabs(h_position) > ALIGN_EPSILON) {
178                 if (context.standard == Standard::SMPTE) {
179                         e->set_attribute ("Hposition", raw_convert<string> (h_position * 100, 6));
180                 } else {
181                         e->set_attribute ("HPosition", raw_convert<string> (h_position * 100, 6));
182                 }
183         }
184
185         if (context.standard == Standard::SMPTE) {
186                 e->set_attribute ("Valign", valign_to_string (v_align));
187         } else {
188                 e->set_attribute ("VAlign", valign_to_string (v_align));
189         }
190
191         if (fabs(v_position) > ALIGN_EPSILON) {
192                 if (context.standard == Standard::SMPTE) {
193                         e->set_attribute ("Vposition", raw_convert<string> (v_position * 100, 6));
194                 } else {
195                         e->set_attribute ("VPosition", raw_convert<string> (v_position * 100, 6));
196                 }
197         } else {
198                 if (context.standard == Standard::SMPTE) {
199                         e->set_attribute ("Vposition", "0");
200                 } else {
201                         e->set_attribute ("VPosition", "0");
202                 }
203         }
204 }
205
206
207 xmlpp::Element*
208 order::Text::as_xml (xmlpp::Element* parent, Context& context) const
209 {
210         auto e = parent->add_child ("Text", context.xmlns());
211
212         position_align (e, context, _h_align, _h_position, _v_align, _v_position);
213
214         /* Interop only supports "horizontal" or "vertical" for direction, so only write this
215            for SMPTE.
216         */
217         if (_direction != Direction::LTR && context.standard == Standard::SMPTE) {
218                 e->set_attribute ("Direction", direction_to_string (_direction));
219         }
220
221         return e;
222 }
223
224
225 xmlpp::Element*
226 order::Subtitle::as_xml (xmlpp::Element* parent, Context& context) const
227 {
228         auto e = parent->add_child ("Subtitle", context.xmlns());
229         e->set_attribute ("SpotNumber", raw_convert<string> (context.spot_number++));
230         e->set_attribute ("TimeIn", _in.rebase(context.time_code_rate).as_string(context.standard));
231         e->set_attribute ("TimeOut", _out.rebase(context.time_code_rate).as_string(context.standard));
232         if (context.standard == Standard::SMPTE) {
233                 e->set_attribute ("FadeUpTime", _fade_up.rebase(context.time_code_rate).as_string(context.standard));
234                 e->set_attribute ("FadeDownTime", _fade_down.rebase(context.time_code_rate).as_string(context.standard));
235         } else {
236                 e->set_attribute ("FadeUpTime", raw_convert<string> (_fade_up.as_editable_units(context.time_code_rate)));
237                 e->set_attribute ("FadeDownTime", raw_convert<string> (_fade_down.as_editable_units(context.time_code_rate)));
238         }
239         return e;
240 }
241
242
243 bool
244 order::Font::operator== (Font const & other) const
245 {
246         return _values == other._values;
247 }
248
249
250 void
251 order::Font::clear ()
252 {
253         _values.clear ();
254 }
255
256
257 xmlpp::Element *
258 order::Image::as_xml (xmlpp::Element* parent, Context& context) const
259 {
260         auto e = parent->add_child ("Image", context.xmlns());
261
262         position_align (e, context, _h_align, _h_position, _v_align, _v_position);
263         if (context.standard == Standard::SMPTE) {
264                 e->add_child_text (_id);
265         } else {
266                 e->add_child_text (_id + ".png");
267         }
268
269         return e;
270 }