From 9bf2cf3509380663e6943f6b0d22dbec6002c332 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Mon, 2 Nov 2015 22:47:57 +0000 Subject: Fix incorrect preview of italic subtitles (#728). This requires fonts to be configured with their italic versions so that the italic font can be given to FontConfig and hence used to render text. Bold font configuration is also added here although bold subtitles aren't yet supported. --- src/lib/font.cc | 51 +++++++++++++++-- src/lib/font.h | 24 +++++--- src/lib/font_files.cc | 32 +++++++++++ src/lib/font_files.h | 50 +++++++++++++++++ src/lib/player_subtitles.cc | 2 +- src/lib/reel_writer.cc | 10 ++-- src/lib/render_subtitles.cc | 131 +++++++++++++++++++++++++++++++------------- src/lib/subtitle_content.cc | 4 +- src/lib/wscript | 1 + 9 files changed, 250 insertions(+), 55 deletions(-) create mode 100644 src/lib/font_files.cc create mode 100644 src/lib/font_files.h (limited to 'src/lib') diff --git a/src/lib/font.cc b/src/lib/font.cc index d729c2696..e9df66cf7 100644 --- a/src/lib/font.cc +++ b/src/lib/font.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2014 Carl Hetherington + Copyright (C) 2014-2015 Carl Hetherington 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 @@ -18,26 +18,67 @@ */ #include "font.h" +#include "dcpomatic_assert.h" #include +#include + +using std::string; + +static char const * names[] = { + "Normal", + "Italic", + "Bold" +}; Font::Font (cxml::NodePtr node) : _id (node->string_child ("Id")) - , _file (node->optional_string_child ("File")) { + DCPOMATIC_ASSERT (FontFiles::VARIANTS == 3); + BOOST_FOREACH (cxml::NodePtr i, node->node_children ("File")) { + string variant = i->optional_string_attribute("Variant").get_value_or ("Normal"); + for (int j = 0; j < FontFiles::VARIANTS; ++j) { + if (variant == names[j]) { + _files.set (static_cast(j), i->content()); + } + } + } } void Font::as_xml (xmlpp::Node* node) { + DCPOMATIC_ASSERT (FontFiles::VARIANTS == 3); + node->add_child("Id")->add_child_text (_id); - if (_file) { - node->add_child("File")->add_child_text (_file.get().string ()); + for (int i = 0; i < FontFiles::VARIANTS; ++i) { + if (_files.get(static_cast(i))) { + xmlpp::Element* e = node->add_child ("File"); + e->set_attribute ("Variant", names[i]); + e->add_child_text (_files.get(static_cast(i)).get().string ()); + } } } + +bool +operator== (Font const & a, Font const & b) +{ + if (a.id() != b.id()) { + return false; + } + + for (int i = 0; i < FontFiles::VARIANTS; ++i) { + if (a.file(static_cast(i)) != b.file(static_cast(i))) { + return false; + } + } + + return true; +} + bool operator!= (Font const & a, Font const & b) { - return (a.id() != b.id() || a.file() != b.file()); + return !(a == b); } diff --git a/src/lib/font.h b/src/lib/font.h index ca7650cc1..c165fea9f 100644 --- a/src/lib/font.h +++ b/src/lib/font.h @@ -20,6 +20,7 @@ #ifndef DCPOMATIC_FONT_H #define DCPOMATIC_FONT_H +#include "font_files.h" #include #include #include @@ -40,12 +41,21 @@ public: return _id; } - boost::optional file () const { - return _file; + boost::optional file (FontFiles::Variant variant) const { + return _files.get (variant); } - void set_file (boost::filesystem::path file) { - _file = file; + void set_file (FontFiles::Variant variant, boost::filesystem::path file) { + _files.set (variant, file); + Changed (); + } + + FontFiles files () const { + return _files; + } + + void set_files (FontFiles files) { + _files = files; Changed (); } @@ -54,10 +64,10 @@ public: private: /** Font ID, used to describe it in the subtitle content */ std::string _id; - boost::optional _file; + FontFiles _files; }; -bool -operator!= (Font const & a, Font const & b); +bool operator!= (Font const & a, Font const & b); +bool operator== (Font const & a, Font const & b); #endif diff --git a/src/lib/font_files.cc b/src/lib/font_files.cc new file mode 100644 index 000000000..65b67f7b3 --- /dev/null +++ b/src/lib/font_files.cc @@ -0,0 +1,32 @@ +/* + Copyright (C) 2015 Carl Hetherington + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "font_files.h" + +bool +operator!= (FontFiles const & a, FontFiles const & b) +{ + for (int i = 0; i < FontFiles::VARIANTS; ++i) { + if (a.get(static_cast(i)) != b.get(static_cast(i))) { + return false; + } + } + + return true; +} diff --git a/src/lib/font_files.h b/src/lib/font_files.h new file mode 100644 index 000000000..68e7ac03f --- /dev/null +++ b/src/lib/font_files.h @@ -0,0 +1,50 @@ +/* + Copyright (C) 2015 Carl Hetherington + + 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef DCPOMATIC_FONT_FILES_H +#define DCPOMATIC_FONT_FILES_H + +#include +#include + +class FontFiles +{ +public: + enum Variant { + NORMAL, + ITALIC, + BOLD, + VARIANTS + }; + + void set (Variant variant, boost::filesystem::path file) { + _file[variant] = file; + } + + boost::optional get (Variant variant) const { + return _file[variant]; + } + +private: + boost::optional _file[VARIANTS]; +}; + +bool operator!= (FontFiles const & a, FontFiles const & b); + +#endif diff --git a/src/lib/player_subtitles.cc b/src/lib/player_subtitles.cc index da5714349..8838b2f64 100644 --- a/src/lib/player_subtitles.cc +++ b/src/lib/player_subtitles.cc @@ -30,7 +30,7 @@ PlayerSubtitles::add_fonts (list > fonts_) BOOST_FOREACH (shared_ptr i, fonts_) { bool got = false; BOOST_FOREACH (shared_ptr j, fonts) { - if (i->file() == j->file()) { + if (*i == *j) { got = true; } } diff --git a/src/lib/reel_writer.cc b/src/lib/reel_writer.cc index 743f73dff..803c2a8ed 100644 --- a/src/lib/reel_writer.cc +++ b/src/lib/reel_writer.cc @@ -363,17 +363,19 @@ ReelWriter::create_reel (list const & refs, list j, fonts) { - _subtitle_asset->add_font (j->id(), j->file().get_value_or (liberation)); + _subtitle_asset->add_font (j->id(), j->file(FontFiles::NORMAL).get_value_or(liberation_normal)); } if (dynamic_pointer_cast (_subtitle_asset)) { diff --git a/src/lib/render_subtitles.cc b/src/lib/render_subtitles.cc index f507238fb..9b5f585e5 100644 --- a/src/lib/render_subtitles.cc +++ b/src/lib/render_subtitles.cc @@ -22,6 +22,7 @@ #include "image.h" #include "cross.h" #include "font.h" +#include "dcpomatic_assert.h" #include #include #include @@ -39,23 +40,30 @@ using boost::shared_ptr; using boost::optional; static FcConfig* fc_config = 0; -static list > fc_config_fonts; +static list > fc_config_fonts; +/** @param subtitles A list of subtitles that are all on the same line */ static PositionImage -render_subtitle (dcp::SubtitleString const & subtitle, list > fonts, dcp::Size target) +render_line (list subtitles, list > fonts, dcp::Size target) { + /* XXX: this method can only handle italic / bold changes mid-line, + nothing else yet. + */ + + DCPOMATIC_ASSERT (!subtitles.empty ()); + /* Calculate x and y scale factors. These are only used to stretch the font away from its normal aspect ratio. */ float xscale = 1; float yscale = 1; - if (fabs (subtitle.aspect_adjust() - 1.0) > dcp::ASPECT_ADJUST_EPSILON) { - if (subtitle.aspect_adjust() < 1) { - xscale = max (0.25f, subtitle.aspect_adjust ()); + if (fabs (subtitles.front().aspect_adjust() - 1.0) > dcp::ASPECT_ADJUST_EPSILON) { + if (subtitles.front().aspect_adjust() < 1) { + xscale = max (0.25f, subtitles.front().aspect_adjust ()); yscale = 1; } else { xscale = 1; - yscale = 1 / min (4.0f, subtitle.aspect_adjust ()); + yscale = 1 / min (4.0f, subtitles.front().aspect_adjust ()); } } @@ -64,7 +72,7 @@ render_subtitle (dcp::SubtitleString const & subtitle, list > f */ /* Basic guess on height... */ - int height = subtitle.size() * target.height / (11 * 72); + int height = subtitles.front().size() * target.height / (11 * 72); /* ...scaled... */ height *= yscale; /* ...and add a bit more for luck */ @@ -87,22 +95,36 @@ render_subtitle (dcp::SubtitleString const & subtitle, list > f fc_config = FcConfigCreate (); } - boost::filesystem::path font_file; + FontFiles font_files; + try { - font_file = shared_path () / "LiberationSans-Regular.ttf"; + font_files.set (FontFiles::NORMAL, shared_path () / "LiberationSans-Regular.ttf"); + font_files.set (FontFiles::ITALIC, shared_path () / "LiberationSans-Italic.ttf"); + font_files.set (FontFiles::BOLD, shared_path () / "LiberationSans-Bold.ttf"); } catch (boost::filesystem::filesystem_error& e) { - /* Hack: try the debian/ubuntu location if getting the shared path failed */ - font_file = "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"; + + } + + /* Hack: try the debian/ubuntu locations if getting the shared path failed */ + + if (!font_files.get(FontFiles::NORMAL) || !boost::filesystem::exists(font_files.get(FontFiles::NORMAL).get())) { + font_files.set (FontFiles::NORMAL, "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"); + } + if (!font_files.get(FontFiles::ITALIC) || !boost::filesystem::exists(font_files.get(FontFiles::ITALIC).get())) { + font_files.set (FontFiles::ITALIC, "/usr/share/fonts/truetype/liberation/LiberationSans-Italic.ttf"); + } + if (!font_files.get(FontFiles::BOLD) || !boost::filesystem::exists(font_files.get(FontFiles::BOLD).get())) { + font_files.set (FontFiles::BOLD, "/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf"); } BOOST_FOREACH (shared_ptr i, fonts) { - if (i->id() == subtitle.font() && i->file ()) { - font_file = i->file().get (); + if (i->id() == subtitles.front().font() && i->file(FontFiles::NORMAL)) { + font_files = i->files (); } } - list >::const_iterator existing = fc_config_fonts.begin (); - while (existing != fc_config_fonts.end() && existing->first != font_file) { + list >::const_iterator existing = fc_config_fonts.begin (); + while (existing != fc_config_fonts.end() && existing->first != font_files) { ++existing; } @@ -111,9 +133,16 @@ render_subtitle (dcp::SubtitleString const & subtitle, list > f font_name = existing->second; } else { /* Make this font available to DCP-o-matic */ - FcConfigAppFontAddFile (fc_config, reinterpret_cast (font_file.string().c_str ())); + for (int i = 0; i < FontFiles::VARIANTS; ++i) { + if (font_files.get(static_cast(i))) { + FcConfigAppFontAddFile ( + fc_config, + reinterpret_cast (font_files.get(static_cast(i)).get().string().c_str()) + ); + } + } - FcPattern* pattern = FcPatternBuild (0, FC_FILE, FcTypeString, font_file.string().c_str(), static_cast (0)); + FcPattern* pattern = FcPatternBuild (0, FC_FILE, FcTypeString, font_files.get(FontFiles::NORMAL).get().string().c_str(), static_cast (0)); FcObjectSet* object_set = FcObjectSetBuild (FC_FAMILY, FC_STYLE, FC_LANG, FC_FILE, static_cast (0)); FcFontSet* font_set = FcFontList (fc_config, pattern, object_set); if (font_set) { @@ -137,7 +166,7 @@ render_subtitle (dcp::SubtitleString const & subtitle, list > f FcObjectSetDestroy (object_set); FcPatternDestroy (pattern); - fc_config_fonts.push_back (make_pair (font_file, font_name)); + fc_config_fonts.push_back (make_pair (font_files, font_name)); } FcConfigSetCurrent (fc_config); @@ -151,12 +180,29 @@ render_subtitle (dcp::SubtitleString const & subtitle, list > f /* Render the subtitle at the top left-hand corner of image */ Pango::FontDescription font (font_name); - font.set_absolute_size (subtitle.size_in_pixels (target.height) * PANGO_SCALE); - if (subtitle.italic ()) { - font.set_style (Pango::STYLE_ITALIC); - } + font.set_absolute_size (subtitles.front().size_in_pixels (target.height) * PANGO_SCALE); layout->set_font_description (font); - layout->set_text (subtitle.text ()); + + string marked_up; + bool italic = false; + BOOST_FOREACH (dcp::SubtitleString const & i, subtitles) { + if (i.italic() != italic) { + if (i.italic()) { + marked_up += ""; + } else { + marked_up += ""; + } + italic = i.italic (); + } + + marked_up += i.text (); + } + + if (italic) { + marked_up += ""; + } + + layout->set_markup (marked_up); /* Compute fade factor */ /* XXX */ @@ -166,9 +212,9 @@ render_subtitle (dcp::SubtitleString const & subtitle, list > f context->scale (xscale, yscale); - if (subtitle.effect() == dcp::SHADOW) { + if (subtitles.front().effect() == dcp::SHADOW) { /* Drop-shadow effect */ - dcp::Colour const ec = subtitle.effect_colour (); + dcp::Colour const ec = subtitles.front().effect_colour (); context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor); context->move_to (4, 4); layout->add_to_cairo_context (context); @@ -177,15 +223,15 @@ render_subtitle (dcp::SubtitleString const & subtitle, list > f /* The actual subtitle */ - dcp::Colour const c = subtitle.colour (); + dcp::Colour const c = subtitles.front().colour (); context->set_source_rgba (float(c.r) / 255, float(c.g) / 255, float(c.b) / 255, fade_factor); context->move_to (0, 0); layout->add_to_cairo_context (context); context->fill (); - if (subtitle.effect() == dcp::BORDER) { + if (subtitles.front().effect() == dcp::BORDER) { /* Border effect */ - dcp::Colour ec = subtitle.effect_colour (); + dcp::Colour ec = subtitles.front().effect_colour (); context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor); context->move_to (0, 0); layout->add_to_cairo_context (context); @@ -197,34 +243,34 @@ render_subtitle (dcp::SubtitleString const & subtitle, list > f layout->get_size (layout_width, layout_height); int x = 0; - switch (subtitle.h_align ()) { + switch (subtitles.front().h_align ()) { case dcp::HALIGN_LEFT: /* h_position is distance between left of frame and left of subtitle */ - x = subtitle.h_position() * target.width; + x = subtitles.front().h_position() * target.width; break; case dcp::HALIGN_CENTER: /* h_position is distance between centre of frame and centre of subtitle */ - x = (0.5 + subtitle.h_position()) * target.width - layout_width / (PANGO_SCALE * 2); + x = (0.5 + subtitles.front().h_position()) * target.width - layout_width / (PANGO_SCALE * 2); break; case dcp::HALIGN_RIGHT: /* h_position is distance between right of frame and right of subtitle */ - x = (1.0 - subtitle.h_position()) * target.width - layout_width / PANGO_SCALE; + x = (1.0 - subtitles.front().h_position()) * target.width - layout_width / PANGO_SCALE; break; } int y = 0; - switch (subtitle.v_align ()) { + switch (subtitles.front().v_align ()) { case dcp::VALIGN_TOP: /* v_position is distance between top of frame and top of subtitle */ - y = subtitle.v_position() * target.height; + y = subtitles.front().v_position() * target.height; break; case dcp::VALIGN_CENTER: /* v_position is distance between centre of frame and centre of subtitle */ - y = (0.5 + subtitle.v_position()) * target.height - layout_height / (PANGO_SCALE * 2); + y = (0.5 + subtitles.front().v_position()) * target.height - layout_height / (PANGO_SCALE * 2); break; case dcp::VALIGN_BOTTOM: /* v_position is distance between bottom of frame and bottom of subtitle */ - y = (1.0 - subtitle.v_position()) * target.height - layout_height / PANGO_SCALE; + y = (1.0 - subtitles.front().v_position()) * target.height - layout_height / PANGO_SCALE; break; } @@ -234,9 +280,20 @@ render_subtitle (dcp::SubtitleString const & subtitle, list > f list render_subtitles (list subtitles, list > fonts, dcp::Size target) { + list pending; list images; + BOOST_FOREACH (dcp::SubtitleString const & i, subtitles) { - images.push_back (render_subtitle (i, fonts, target)); + if (!pending.empty() && fabs (i.v_position() - pending.back().v_position()) > 1e-4) { + images.push_back (render_line (pending, fonts, target)); + pending.clear (); + } + pending.push_back (i); } + + if (!pending.empty ()) { + images.push_back (render_line (pending, fonts, target)); + } + return images; } diff --git a/src/lib/subtitle_content.cc b/src/lib/subtitle_content.cc index 2f4c88975..d779a7642 100644 --- a/src/lib/subtitle_content.cc +++ b/src/lib/subtitle_content.cc @@ -275,7 +275,9 @@ SubtitleContent::identifier () const types of subtitle content involve fonts. */ BOOST_FOREACH (shared_ptr f, _fonts) { - s << f->file().get_value_or ("Default"); + for (int i = 0; i < FontFiles::VARIANTS; ++i) { + s << "_" << f->file(static_cast(i)).get_value_or ("Default"); + } } /* The language is for metadata only, and doesn't affect diff --git a/src/lib/wscript b/src/lib/wscript index c6c73662b..263d222c2 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -72,6 +72,7 @@ sources = """ film.cc filter.cc font.cc + font_files.cc frame_rate_change.cc internet.cc image.cc -- cgit v1.2.3