From 61b073a82c3c2ecfceabcfbdd60931862dfc7d8c Mon Sep 17 00:00:00 2001 From: Helge Norberg Date: Mon, 12 Oct 2015 15:36:00 +0200 Subject: [PATCH] * Implemented FLS command (font list). * Fixed some resource leaks in text_producer. * Load font list each time a text_producer is created to discover fonts uploaded after start. --- core/CMakeLists.txt | 2 + core/producer/text/text_producer.cpp | 76 +++++++++---------- core/producer/text/text_producer.h | 2 + core/producer/text/utils/freetype_library.cpp | 64 ++++++++++++++++ core/producer/text/utils/freetype_library.h | 40 ++++++++++ core/producer/text/utils/texture_font.cpp | 30 +++----- protocol/amcp/AMCPCommandsImpl.cpp | 25 ++++++ 7 files changed, 177 insertions(+), 62 deletions(-) create mode 100644 core/producer/text/utils/freetype_library.cpp create mode 100644 core/producer/text/utils/freetype_library.h diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 17c354778..3828f434f 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -37,6 +37,7 @@ set(SOURCES producer/separated/separated_producer.cpp producer/text/text_producer.cpp + producer/text/utils/freetype_library.cpp producer/text/utils/texture_atlas.cpp producer/text/utils/texture_font.cpp @@ -99,6 +100,7 @@ set(HEADERS producer/scene/xml_scene_producer.h producer/text/utils/color.h + producer/text/utils/freetype_library.h producer/text/utils/string_metrics.h producer/text/utils/text_info.h producer/text/utils/texture_atlas.h diff --git a/core/producer/text/text_producer.cpp b/core/producer/text/text_producer.cpp index 89756f555..23076a4e5 100644 --- a/core/producer/text/text_producer.cpp +++ b/core/producer/text/text_producer.cpp @@ -57,6 +57,7 @@ #include "utils/texture_atlas.h" #include "utils/texture_font.h" +#include "utils/freetype_library.h" class font_comparer { const std::wstring& lhs; @@ -70,59 +71,48 @@ namespace caspar { namespace core { namespace text { using namespace boost::filesystem; -std::map g_fonts; - std::map enumerate_fonts() { std::map result; - FT_Library lib; - FT_Error err = FT_Init_FreeType(&lib); - if(err) - return result; - - auto fonts = directory_iterator(env::font_folder()); - auto end = directory_iterator(); - for(; fonts != end; ++fonts) + for(auto iter = directory_iterator(env::font_folder()), end = directory_iterator(); iter != end; ++iter) { - auto file = (*fonts); + auto file = (*iter); if(is_regular_file(file.path())) { - FT_Face face; - err = FT_New_Face(lib, u8(file.path().native()).c_str(), 0, &face); - if(err) - continue; - - const char* fontname = FT_Get_Postscript_Name(face); //this doesn't work for .fon fonts. Ignoring those for now + auto face = get_new_face(u8(file.path().native())); + const char* fontname = FT_Get_Postscript_Name(face.get()); //this doesn't work for .fon fonts. Ignoring those for now if(fontname != nullptr) { std::string fontname_str(fontname); result.insert(std::make_pair(u16(fontname_str), u16(file.path().native()))); } - - FT_Done_Face(face); } } - FT_Done_FreeType(lib); - return result; } +std::vector> list_fonts() +{ + auto fonts = enumerate_fonts(); + return std::vector>(fonts.begin(), fonts.end()); +} + void describe_text_producer(help_sink&, const help_repository&); spl::shared_ptr create_text_producer(const frame_producer_dependencies&, const std::vector&); void init(module_dependencies dependencies) { - g_fonts = enumerate_fonts(); dependencies.producer_registry->register_producer_factory(L"Text Producer", create_text_producer, describe_text_producer); } text_info& find_font_file(text_info& info) { auto& font_name = info.font; - auto it = std::find_if(g_fonts.begin(), g_fonts.end(), font_comparer(font_name)); - info.font_file = (it != g_fonts.end()) ? (*it).second : L""; + auto fonts = enumerate_fonts(); + auto it = std::find_if(fonts.begin(), fonts.end(), font_comparer(font_name)); + info.font_file = (it != fonts.end()) ? (*it).second : L""; return info; } @@ -131,27 +121,30 @@ text_info& find_font_file(text_info& info) struct text_producer::impl { - monitor::subject monitor_subject_; - spl::shared_ptr frame_factory_; - constraints constraints_; - int x_, y_, parent_width_, parent_height_; - bool standalone_; - variable_impl text_; - std::shared_ptr text_subscription_; - variable_impl tracking_; - std::shared_ptr tracking_subscription_; - variable_impl current_bearing_y_; - variable_impl current_protrude_under_y_; - draw_frame frame_; - text::texture_atlas atlas_ { 512, 512, 4 }; - text::texture_font font_; - bool dirty_ = false; + monitor::subject monitor_subject_; + spl::shared_ptr frame_factory_; + int x_; + int y_; + int parent_width_; + int parent_height_; + bool standalone_; + constraints constraints_ { parent_width_, parent_height_ }; + variable_impl text_; + std::shared_ptr text_subscription_; + variable_impl tracking_; + std::shared_ptr tracking_subscription_; + variable_impl current_bearing_y_; + variable_impl current_protrude_under_y_; + draw_frame frame_; + text::texture_atlas atlas_ { 512, 512, 4 }; + text::texture_font font_; + bool dirty_ = false; public: explicit impl(const spl::shared_ptr& frame_factory, int x, int y, const std::wstring& str, text::text_info& text_info, long parent_width, long parent_height, bool standalone) : frame_factory_(frame_factory) - , constraints_(parent_width, parent_height) - , x_(x), y_(y), parent_width_(parent_width), parent_height_(parent_height) + , x_(x), y_(y) + , parent_width_(parent_width), parent_height_(parent_height) , standalone_(standalone) , font_(atlas_, text::find_font_file(text_info), !standalone) { @@ -340,6 +333,7 @@ void describe_text_producer(help_sink& sink, const help_repository& repo) sink.para()->text(L"Examples:"); sink.example(L">> PLAY 1-10 [TEXT] \"John Doe\" 0 0 FONT ArialMT SIZE 30 COLOR #1b698d STANDALONE 1"); sink.example(L">> CALL 1-10 \"Jane Doe\"", L"for modifying the text while playing."); + sink.para()->text(L"See ")->see(L"FLS")->text(L" for listing the available fonts."); } spl::shared_ptr create_text_producer(const frame_producer_dependencies& dependencies, const std::vector& params) diff --git a/core/producer/text/text_producer.h b/core/producer/text/text_producer.h index 065eebf2f..93c0474d1 100644 --- a/core/producer/text/text_producer.h +++ b/core/producer/text/text_producer.h @@ -29,6 +29,7 @@ #include #include #include +#include #include "utils/color.h" #include "utils/string_metrics.h" @@ -38,6 +39,7 @@ namespace caspar { namespace core { namespace text { void init(module_dependencies dependencies); + std::vector> list_fonts(); } class text_producer : public frame_producer_base diff --git a/core/producer/text/utils/freetype_library.cpp b/core/producer/text/utils/freetype_library.cpp new file mode 100644 index 000000000..a30d750df --- /dev/null +++ b/core/producer/text/utils/freetype_library.cpp @@ -0,0 +1,64 @@ +/* +* Copyright (c) 2011 Sveriges Television AB +* +* This file is part of CasparCG (www.casparcg.com). +* +* CasparCG 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 3 of the License, or +* (at your option) any later version. +* +* CasparCG 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 CasparCG. If not, see . +* +* Author: Helge Norberg, helge.norberg@svt.se +*/ + +#include "freetype_library.h" + +#include + +namespace caspar { namespace core { namespace text { + +spl::shared_ptr::type> get_lib_for_thread() +{ + typedef std::remove_pointer::type non_ptr_type; + static boost::thread_specific_ptr> libs; + + auto result = libs.get(); + + if (!result) + { + FT_Library raw_lib; + + if (FT_Init_FreeType(&raw_lib)) + CASPAR_THROW_EXCEPTION(freetype_exception() << msg_info("Failed to initialize freetype")); + + auto lib = spl::shared_ptr(raw_lib, FT_Done_FreeType); + result = new spl::shared_ptr(std::move(lib)); + + libs.reset(result); + } + + return *result; +} + +spl::shared_ptr::type> get_new_face(const std::string& font_file) +{ + auto lib = get_lib_for_thread(); + FT_Face face; + if (FT_New_Face(lib.get(), u8(font_file).c_str(), 0, &face)) + CASPAR_THROW_EXCEPTION(freetype_exception() << msg_info("Failed to load font file \"" + font_file + "\"")); + + return spl::shared_ptr::type>(face, [lib](auto* p) + { + FT_Done_Face(p); + }); +} + +}}} diff --git a/core/producer/text/utils/freetype_library.h b/core/producer/text/utils/freetype_library.h new file mode 100644 index 000000000..92fa5bdda --- /dev/null +++ b/core/producer/text/utils/freetype_library.h @@ -0,0 +1,40 @@ +/* +* Copyright (c) 2011 Sveriges Television AB +* +* This file is part of CasparCG (www.casparcg.com). +* +* CasparCG 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 3 of the License, or +* (at your option) any later version. +* +* CasparCG 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 CasparCG. If not, see . +* +* Author: Helge Norberg, helge.norberg@svt.se +*/ + +#pragma once + +#include +#include + +#include +#include FT_FREETYPE_H + +#include +#include + +namespace caspar { namespace core { namespace text { + +struct freetype_exception : virtual caspar_exception { }; + +spl::shared_ptr::type> get_lib_for_thread(); +spl::shared_ptr::type> get_new_face(const std::string& font_file); + +}}} diff --git a/core/producer/text/utils/texture_font.cpp b/core/producer/text/utils/texture_font.cpp index 5a81e30e3..9c07f6c0c 100644 --- a/core/producer/text/utils/texture_font.cpp +++ b/core/producer/text/utils/texture_font.cpp @@ -2,6 +2,7 @@ #include "texture_atlas.h" #include "texture_font.h" +#include "freetype_library.h" #include #include @@ -11,8 +12,6 @@ namespace caspar { namespace core { namespace text { -struct freetype_exception : virtual caspar_exception { }; - struct unicode_range { unicode_range() : first(0), last(0) {} @@ -37,8 +36,7 @@ private: int width, height; }; - std::shared_ptr lib_; - std::shared_ptr face_; + spl::shared_ptr face_; texture_atlas atlas_; double size_; double tracking_; @@ -46,22 +44,13 @@ private: std::map glyphs_; public: - impl(texture_atlas& atlas, const text_info& info, bool normalize_coordinates) : atlas_(atlas), size_(info.size), tracking_(info.size*info.tracking/1000.0), normalize_(normalize_coordinates) + impl(texture_atlas& atlas, const text_info& info, bool normalize_coordinates) + : face_(get_new_face(u8(info.font_file))) + , atlas_(atlas) + , size_(info.size) + , tracking_(info.size*info.tracking/1000.0) + , normalize_(normalize_coordinates) { - FT_Library lib; - - if (FT_Init_FreeType(&lib)) - CASPAR_THROW_EXCEPTION(freetype_exception() << msg_info("Failed to initialize freetype")); - - lib_.reset(lib, [](FT_Library ptr) { FT_Done_FreeType(ptr); }); - - FT_Face face; - - if (FT_New_Face(lib_.get(), u8(info.font_file).c_str(), 0, &face)) - CASPAR_THROW_EXCEPTION(freetype_exception() << msg_info(L"Failed to load font " + info.font)); - - face_.reset(face, [](FT_Face ptr) { FT_Done_Face(ptr); }); - if (FT_Set_Char_Size(face_.get(), static_cast(size_*64), 0, 72, 72)) CASPAR_THROW_EXCEPTION(freetype_exception() << msg_info("Failed to set font size")); } @@ -83,7 +72,6 @@ public: void load_glyphs(unicode_block block, const color& col) { - FT_Error err; int flags = FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_NORMAL; unicode_range range = get_range(block); @@ -93,7 +81,7 @@ public: if(!glyph_index) //ignore codes that doesn't have a glyph for now. Might want to map these to a special glyph later. continue; - err = FT_Load_Glyph(face_.get(), glyph_index, flags); + FT_Error err = FT_Load_Glyph(face_.get(), glyph_index, flags); if(err) continue; //igonore glyphs that fail to load const FT_Bitmap& bitmap = face_->glyph->bitmap; //shorthand notation diff --git a/protocol/amcp/AMCPCommandsImpl.cpp b/protocol/amcp/AMCPCommandsImpl.cpp index 0bce9354c..57ad98ad2 100644 --- a/protocol/amcp/AMCPCommandsImpl.cpp +++ b/protocol/amcp/AMCPCommandsImpl.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -2224,6 +2225,29 @@ std::wstring cls_command(command_context& ctx) return boost::to_upper_copy(replyString.str()); } +void fls_describer(core::help_sink& sink, const core::help_repository& repo) +{ + sink.short_description(L"List all fonts."); + sink.syntax(L"FLS"); + sink.para() + ->text(L"Lists all font files in the ")->code(L"fonts")->text(L" folder. Use the command ") + ->see(L"INFO PATHS")->text(L" to get the path to the ")->code(L"fonts")->text(L" folder."); + sink.para()->text(L"Columns in order from left to right are: Font name and font path."); +} + +std::wstring fls_command(command_context& ctx) +{ + std::wstringstream replyString; + replyString << L"200 FLS OK\r\n"; + + for (auto& font : core::text::list_fonts()) + replyString << L"\"" << font.first << L"\" \"" << font.second << L"\"\r\n"; + + replyString << L"\r\n"; + + return replyString.str(); +} + void tls_describer(core::help_sink& sink, const core::help_repository& repo) { sink.short_description(L"List all templates."); @@ -2845,6 +2869,7 @@ void register_commands(amcp_command_repository& repo) repo.register_command( L"Query Commands", L"CINF", cinf_describer, cinf_command, 1); repo.register_command( L"Query Commands", L"CLS", cls_describer, cls_command, 0); + repo.register_command( L"Query Commands", L"FLS", fls_describer, fls_command, 0); repo.register_command( L"Query Commands", L"TLS", tls_describer, tls_command, 0); repo.register_command( L"Query Commands", L"VERSION", version_describer, version_command, 0); repo.register_command( L"Query Commands", L"INFO", info_describer, info_command, 0); -- 2.39.2