summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2023-11-15 20:14:55 +0100
committerCarl Hetherington <cth@carlh.net>2023-11-15 20:14:55 +0100
commite3fa86ef35f212b14b593dd36dbff66e845d37e4 (patch)
treec5d6ed58fc19b0fcd08c02a67df66d7023337728 /src
parent24ba38ed1d695a67aebc8a6084444345787112f9 (diff)
Simple pass-through of <Ruby> tags in subtitles.
Diffstat (limited to 'src')
-rw-r--r--src/ruby.cc57
-rw-r--r--src/ruby.h71
-rw-r--r--src/subtitle_asset.cc97
-rw-r--r--src/subtitle_asset.h8
-rw-r--r--src/subtitle_asset_internal.cc12
-rw-r--r--src/subtitle_asset_internal.h5
-rw-r--r--src/subtitle_string.cc17
-rw-r--r--src/subtitle_string.h14
-rw-r--r--src/types.h5
-rw-r--r--src/wscript2
10 files changed, 278 insertions, 10 deletions
diff --git a/src/ruby.cc b/src/ruby.cc
new file mode 100644
index 00000000..6adfbcbc
--- /dev/null
+++ b/src/ruby.cc
@@ -0,0 +1,57 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ This file is part of libdcp.
+
+ libdcp is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ libdcp is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with libdcp. If not, see <http://www.gnu.org/licenses/>.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of portions of this program with the
+ OpenSSL library under certain conditions as described in each
+ individual source file, and distribute linked combinations
+ including the two.
+
+ You must obey the GNU General Public License in all respects
+ for all of the code used other than OpenSSL. If you modify
+ file(s) with this exception, you may extend this exception to your
+ version of the file(s), but you are not obligated to do so. If you
+ do not wish to do so, delete this exception statement from your
+ version. If you delete this exception statement from all source
+ files in the program, then also delete it here.
+*/
+
+
+#include "ruby.h"
+#include "types.h"
+
+
+using namespace dcp;
+
+
+bool dcp::operator==(Ruby const& a, Ruby const& b)
+{
+ return a.base == b.base &&
+ a.annotation == b.annotation &&
+ fabs(a.size - b.size) < SIZE_EPSILON &&
+ a. position == b.position &&
+ fabs(a.offset - b.offset) < OFFSET_EPSILON &&
+ fabs(a.spacing - b.spacing) < SPACING_EPSILON &&
+ fabs(a.aspect_adjust - b.aspect_adjust) < ASPECT_ADJUST_EPSILON;
+}
+
+
+bool dcp::operator!=(Ruby const& a, Ruby const& b)
+{
+ return !(a == b);
+}
diff --git a/src/ruby.h b/src/ruby.h
new file mode 100644
index 00000000..343583dd
--- /dev/null
+++ b/src/ruby.h
@@ -0,0 +1,71 @@
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ This file is part of libdcp.
+
+ libdcp is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ libdcp is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with libdcp. If not, see <http://www.gnu.org/licenses/>.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of portions of this program with the
+ OpenSSL library under certain conditions as described in each
+ individual source file, and distribute linked combinations
+ including the two.
+
+ You must obey the GNU General Public License in all respects
+ for all of the code used other than OpenSSL. If you modify
+ file(s) with this exception, you may extend this exception to your
+ version of the file(s), but you are not obligated to do so. If you
+ do not wish to do so, delete this exception statement from your
+ version. If you delete this exception statement from all source
+ files in the program, then also delete it here.
+*/
+
+
+#include <string>
+
+
+namespace dcp {
+
+
+enum class RubyPosition
+{
+ BEFORE,
+ AFTER
+};
+
+
+class Ruby
+{
+public:
+ Ruby(std::string base_, std::string annotation_)
+ : base(base_)
+ , annotation(annotation_)
+ {}
+
+ std::string base;
+ std::string annotation;
+ float size = 0.5;
+ RubyPosition position = RubyPosition::BEFORE;
+ float offset = 0;
+ float spacing = 0;
+ float aspect_adjust = 1;
+};
+
+
+bool operator==(Ruby const& a, Ruby const& b);
+bool operator!=(Ruby const& a, Ruby const& b);
+
+
+}
+
diff --git a/src/subtitle_asset.cc b/src/subtitle_asset.cc
index 4deb366a..1cd4fc07 100644
--- a/src/subtitle_asset.cc
+++ b/src/subtitle_asset.cc
@@ -294,12 +294,83 @@ SubtitleAsset::parse_subtitles (xmlpp::Element const * node, vector<ParseState>&
float space_before = 0;
+ /* Collect <Ruby>s first */
+ auto get_text_content = [](xmlpp::Element const* element) {
+ string all_content;
+ for (auto child: element->get_children()) {
+ auto content = dynamic_cast<xmlpp::ContentNode const*>(child);
+ if (content) {
+ all_content += content->get_content();
+ }
+ }
+ return all_content;
+ };
+
+ vector<Ruby> rubies;
+ for (auto child: node->get_children()) {
+ auto element = dynamic_cast<xmlpp::Element const*>(child);
+ if (element && element->get_name() == "Ruby") {
+ optional<string> base;
+ optional<string> annotation;
+ optional<float> size;
+ optional<RubyPosition> position;
+ optional<float> offset;
+ optional<float> spacing;
+ optional<float> aspect_adjust;
+ for (auto ruby_child: element->get_children()) {
+ if (auto ruby_element = dynamic_cast<xmlpp::Element const*>(ruby_child)) {
+ if (ruby_element->get_name() == "Rb") {
+ base = get_text_content(ruby_element);
+ } else if (ruby_element->get_name() == "Rt") {
+ annotation = get_text_content(ruby_element);
+ size = optional_number_attribute<float>(ruby_element, "Size");
+ if (auto position_string = optional_string_attribute(ruby_element, "Position")) {
+ if (*position_string == "before") {
+ position = RubyPosition::BEFORE;
+ } else if (*position_string == "after") {
+ position = RubyPosition::AFTER;
+ } else {
+ DCP_ASSERT(false);
+ }
+ }
+ offset = optional_number_attribute<float>(ruby_element, "Offset");
+ spacing = optional_number_attribute<float>(ruby_element, "Spacing");
+ aspect_adjust = optional_number_attribute<float>(ruby_element, "AspectAdjust");
+ }
+ }
+ }
+ DCP_ASSERT(base);
+ DCP_ASSERT(annotation);
+ auto ruby = Ruby{*base, *annotation};
+ if (size) {
+ ruby.size = *size;
+ }
+ if (position) {
+ ruby.position = *position;
+ }
+ if (offset) {
+ ruby.offset = *offset;
+ }
+ if (spacing) {
+ ruby.spacing = *spacing;
+ }
+ if (aspect_adjust) {
+ ruby.aspect_adjust = *aspect_adjust;
+ }
+ rubies.push_back(ruby);
+ }
+ }
+
for (auto i: node->get_children()) {
+
+ /* Handle actual content e.g. text */
auto const v = dynamic_cast<xmlpp::ContentNode const *>(i);
if (v) {
- maybe_add_subtitle (v->get_content(), state, space_before, standard);
+ maybe_add_subtitle (v->get_content(), state, space_before, standard, rubies);
space_before = 0;
}
+
+ /* Handle other nodes */
auto const e = dynamic_cast<xmlpp::Element const *>(i);
if (e) {
if (e->get_name() == "Space") {
@@ -311,7 +382,7 @@ SubtitleAsset::parse_subtitles (xmlpp::Element const * node, vector<ParseState>&
boost::replace_all(size, "em", "");
}
space_before += raw_convert<float>(size);
- } else {
+ } else if (e->get_name() != "Ruby") {
parse_subtitles (e, state, tcr, standard);
}
}
@@ -322,7 +393,13 @@ SubtitleAsset::parse_subtitles (xmlpp::Element const * node, vector<ParseState>&
void
-SubtitleAsset::maybe_add_subtitle (string text, vector<ParseState> const & parse_state, float space_before, Standard standard)
+SubtitleAsset::maybe_add_subtitle(
+ string text,
+ vector<ParseState> const & parse_state,
+ float space_before,
+ Standard standard,
+ vector<Ruby> const& rubies
+ )
{
auto wanted = [](ParseState const& ps) {
return ps.type && (ps.type.get() == ParseState::Type::TEXT || ps.type.get() == ParseState::Type::IMAGE);
@@ -427,7 +504,8 @@ SubtitleAsset::maybe_add_subtitle (string text, vector<ParseState> const & parse
ps.effect_colour.get_value_or (dcp::Colour (0, 0, 0)),
ps.fade_up_time.get_value_or(Time()),
ps.fade_down_time.get_value_or(Time()),
- space_before
+ space_before,
+ rubies
)
);
break;
@@ -698,7 +776,16 @@ SubtitleAsset::subtitles_as_xml (xmlpp::Element* xml_root, int time_code_rate, S
fabs(last_z_position - is->z_position()) > ALIGN_EPSILON ||
last_direction != is->direction()
) {
- text = make_shared<order::Text>(subtitle, is->h_align(), is->h_position(), is->v_align(), is->v_position(), is->z_position(), is->direction());
+ text = make_shared<order::Text>(
+ subtitle,
+ is->h_align(),
+ is->h_position(),
+ is->v_align(),
+ is->v_position(),
+ is->z_position(),
+ is->direction(),
+ is->rubies()
+ );
subtitle->children.push_back (text);
last_h_align = is->h_align ();
diff --git a/src/subtitle_asset.h b/src/subtitle_asset.h
index 42030288..25758c2e 100644
--- a/src/subtitle_asset.h
+++ b/src/subtitle_asset.h
@@ -216,7 +216,13 @@ private:
friend struct ::pull_fonts_test2;
friend struct ::pull_fonts_test3;
- void maybe_add_subtitle (std::string text, std::vector<ParseState> const & parse_state, float space_before, Standard standard);
+ void maybe_add_subtitle(
+ std::string text,
+ std::vector<ParseState> const & parse_state,
+ float space_before,
+ Standard standard,
+ std::vector<Ruby> const& rubies
+ );
static void pull_fonts (std::shared_ptr<order::Part> part);
};
diff --git a/src/subtitle_asset_internal.cc b/src/subtitle_asset_internal.cc
index bb67b45a..99d8411b 100644
--- a/src/subtitle_asset_internal.cc
+++ b/src/subtitle_asset_internal.cc
@@ -223,6 +223,18 @@ order::Text::as_xml (xmlpp::Element* parent, Context& context) const
e->set_attribute ("Direction", direction_to_string (_direction));
}
+ for (auto const& ruby: _rubies) {
+ auto xml = e->add_child("Ruby");
+ xml->add_child("Rb")->add_child_text(ruby.base);
+ auto rt = xml->add_child("Rt");
+ rt->add_child_text(ruby.annotation);
+ rt->set_attribute("Size", dcp::raw_convert<string>(ruby.size, 6));
+ rt->set_attribute("Position", ruby.position == RubyPosition::BEFORE ? "before" : "after");
+ rt->set_attribute("Offset", dcp::raw_convert<string>(ruby.offset, 6));
+ rt->set_attribute("Spacing", dcp::raw_convert<string>(ruby.spacing, 6));
+ rt->set_attribute("AspectAdjust", dcp::raw_convert<string>(ruby.aspect_adjust, 6));
+ }
+
return e;
}
diff --git a/src/subtitle_asset_internal.h b/src/subtitle_asset_internal.h
index adf2c994..557db2e4 100644
--- a/src/subtitle_asset_internal.h
+++ b/src/subtitle_asset_internal.h
@@ -62,6 +62,7 @@ struct pull_fonts_test3;
namespace dcp {
+class Ruby;
class SubtitleString;
@@ -145,7 +146,7 @@ private:
class Text : public Part
{
public:
- Text (std::shared_ptr<Part> parent, HAlign h_align, float h_position, VAlign v_align, float v_position, float z_position, Direction direction)
+ Text(std::shared_ptr<Part> parent, HAlign h_align, float h_position, VAlign v_align, float v_position, float z_position, Direction direction, std::vector<Ruby> rubies)
: Part (parent)
, _h_align (h_align)
, _h_position (h_position)
@@ -153,6 +154,7 @@ public:
, _v_position (v_position)
, _z_position(z_position)
, _direction (direction)
+ , _rubies(rubies)
{}
xmlpp::Element* as_xml (xmlpp::Element* parent, Context& context) const override;
@@ -164,6 +166,7 @@ private:
float _v_position;
float _z_position;
Direction _direction;
+ std::vector<Ruby> _rubies;
};
diff --git a/src/subtitle_string.cc b/src/subtitle_string.cc
index b627240b..af61d928 100644
--- a/src/subtitle_string.cc
+++ b/src/subtitle_string.cc
@@ -49,6 +49,7 @@ using std::min;
using std::ostream;
using std::shared_ptr;
using std::string;
+using std::vector;
using boost::optional;
using namespace dcp;
@@ -74,7 +75,8 @@ SubtitleString::SubtitleString (
Colour effect_colour,
Time fade_up_time,
Time fade_down_time,
- float space_before
+ float space_before,
+ vector<Ruby> rubies
)
: Subtitle(in, out, h_position, h_align, v_position, v_align, z_position, fade_up_time, fade_down_time)
, _font (font)
@@ -89,6 +91,7 @@ SubtitleString::SubtitleString (
, _effect (effect)
, _effect_colour (effect_colour)
, _space_before (space_before)
+ , _rubies(rubies)
{
_aspect_adjust = max(min(_aspect_adjust, 4.0f), 0.25f);
}
@@ -130,7 +133,8 @@ dcp::operator== (SubtitleString const & a, SubtitleString const & b)
a.effect_colour() == b.effect_colour() &&
a.fade_up_time() == b.fade_up_time() &&
a.fade_down_time() == b.fade_down_time() &&
- fabs (a.space_before() - b.space_before()) < SPACE_BEFORE_EPSILON
+ fabs (a.space_before() - b.space_before()) < SPACE_BEFORE_EPSILON &&
+ a.rubies() == b.rubies()
);
}
@@ -175,6 +179,10 @@ dcp::operator<< (ostream& s, SubtitleString const & sub)
<< ", effect colour (" << sub.effect_colour().r << ", " << sub.effect_colour().g << ", " << sub.effect_colour().b << ")"
<< ", space before " << sub.space_before();
+ for (auto ruby: sub.rubies()) {
+ s << ", ruby " << ruby.base << " " << ruby.annotation;
+ }
+
return s;
}
@@ -254,6 +262,11 @@ SubtitleString::equals(shared_ptr<const Subtitle> other_sub, EqualityOptions con
same = false;
}
+ if (_rubies != other->_rubies) {
+ note(NoteType::ERROR, "rubies differ");
+ same = false;
+ }
+
return same;
}
diff --git a/src/subtitle_string.h b/src/subtitle_string.h
index 4eb64a68..1ef57ff2 100644
--- a/src/subtitle_string.h
+++ b/src/subtitle_string.h
@@ -42,6 +42,7 @@
#include "dcp_time.h"
+#include "ruby.h"
#include "subtitle.h"
#include <boost/optional.hpp>
#include <string>
@@ -100,7 +101,8 @@ public:
Colour effect_colour,
Time fade_up_time,
Time fade_down_time,
- float space_before
+ float space_before,
+ std::vector<Ruby> rubies
);
/** @return font ID */
@@ -158,6 +160,10 @@ public:
return _aspect_adjust;
}
+ std::vector<Ruby> const& rubies() const {
+ return _rubies;
+ }
+
void set_font (std::string id) {
_font = id;
}
@@ -190,6 +196,10 @@ public:
_effect_colour = c;
}
+ void set_rubies(std::vector<Ruby> rubies) {
+ _rubies = std::move(rubies);
+ }
+
bool equals(std::shared_ptr<const dcp::Subtitle> other_sub, EqualityOptions const& options, NoteHandler node) const override;
private:
@@ -213,6 +223,7 @@ private:
Effect _effect;
Colour _effect_colour;
float _space_before;
+ std::vector<Ruby> _rubies;
};
bool operator== (SubtitleString const & a, SubtitleString const & b);
@@ -224,3 +235,4 @@ std::ostream& operator<< (std::ostream& s, SubtitleString const & sub);
#endif
+
diff --git a/src/types.h b/src/types.h
index bcb6da5b..183ce638 100644
--- a/src/types.h
+++ b/src/types.h
@@ -277,6 +277,11 @@ constexpr float ALIGN_EPSILON = 1e-3;
constexpr float SPACE_BEFORE_EPSILON = 1e-3;
+constexpr float SIZE_EPSILON = 1e-3;
+constexpr float OFFSET_EPSILON = 1e-3;
+constexpr float SPACING_EPSILON = 1e-3;
+
+
enum class Marker {
FFOC, ///< first frame of composition
LFOC, ///< last frame of composition
diff --git a/src/wscript b/src/wscript
index e6bee6f2..3bc8537b 100644
--- a/src/wscript
+++ b/src/wscript
@@ -101,6 +101,7 @@ def build(bld):
reel_subtitle_asset.cc
ref.cc
rgb_xyz.cc
+ ruby.cc
s_gamut3_transfer_function.cc
search.cc
smpte_load_font_node.cc
@@ -205,6 +206,7 @@ def build(bld):
reel_stereo_picture_asset.h
reel_subtitle_asset.h
ref.h
+ ruby.h
s_gamut3_transfer_function.h
search.h
smpte_load_font_node.h