Index
/* ** Copyright 2018-2021 Double Precision, Inc. ** See COPYING for distribution information. */ #include "config.h" #include <x/mpobj.H> #include <x/exception.H> #include <x/destroy_callback.H> #include <x/ref.H> #include <x/obj.H> #include <x/appid.H> #include <x/w/main_window.H> #include <x/w/main_window_appearance.H> #include <x/w/gridlayoutmanager.H> #include <x/w/gridfactory.H> #include <x/w/element.H> #include <x/w/font.H> #include <x/w/impl/child_element.H> #include <x/w/impl/background_color_element.H> #include <x/w/impl/scratch_buffer_draw.H> #include <x/w/impl/container.H> #include <x/w/impl/fonts/fontcollection.H> #include <x/w/impl/fonts/composite_text_stream.H> #include <string> #include <iostream> #include <optional> #include "close_flag.H" std::string x::appid() noexcept { return "customfontrenderer.examples.w.libcxx.com"; } struct color1_tag; struct color2_tag; // Construct a child_element_init_params for our custom element. Factored // out for readability. // // The width of the text, the font's height (ascender+descender), and // the length of the shadow is already computed for us, we just add it up. // // Going out of our way to compute the initial metrics, here, is more // efficient than creating the custom element first, computing its metrics, // then setting the metrics. This avoid needless recalculations of the // child element's size, by its container. static inline auto create_child_element_init_params(x::w::dim_t width, x::w::dim_t ascender, x::w::dim_t descender, size_t shadow) { x::w::child_element_init_params init_params; init_params.scratch_buffer_id= "customfontrenderer@customfontrenderer.examples.w.libcxx.com"; // The type-safe integer classes employed by the library, // x::w::dim_t, x::w::coord_t, et. al. overload the + operator // to return larger int classes, to handle overflows. If we want // to keep the precision the same we need to explicitly truncate() // the result of the addition. More work, but avoids hard to track down // bugs, and produces "less awful" results if an overflow does occur. x::w::dim_t height=x::w::dim_t::truncate(ascender+descender); // Expand our metrics by the length of the drop-down shadow. width=x::w::dim_t::truncate(width+shadow); height=x::w::dim_t::truncate(height+shadow); init_params.initial_metrics={ {width, width, width}, {height, height, height} }; return init_params; } // The implementation class of our custom element. Uses the following // mixins: // // - scratch_buffer_draw, to give us a simplified draw(). // // - background_color_elementObj, to give us the two colors we'll use for // our custom-rendered text: the main color, and the shadow color. class my_font_renderer_implObj : public x::w::scratch_buffer_draw< x::w::background_color_elementObj<x::w::child_elementObj, color1_tag, color2_tag>> { // Alias for the superclass. typedef x::w::scratch_buffer_draw< x::w::background_color_elementObj<x::w::child_elementObj, color1_tag, color2_tag> > superclass_t; // The text. const std::u32string title; // The fontcollection for drawing the text. x::w::fontcollection title_fontcollection; // Saved metrics, for convenience. x::w::dim_t width; x::w::dim_t ascender; x::w::dim_t descender; size_t shadow; protected: using superclass_t::do_draw; public: my_font_renderer_implObj(const x::w::container_impl &parent_container, const std::u32string &title, const x::w::fontcollection &title_fontcollection, x::w::dim_t width, x::w::dim_t ascender, x::w::dim_t descender, size_t shadow) : superclass_t { // Background color will be a linear gradient x::w::black, x::w::silver, // Finally, the parameters to child_elementObjs constructor: parent_container, create_child_element_init_params(width, ascender, descender, shadow)}, title{title}, title_fontcollection{title_fontcollection}, width{width}, ascender{ascender}, descender{descender}, shadow{shadow} { } ~my_font_renderer_implObj()=default; // Implement do_draw(). void do_draw(ONLY IN_THREAD, const x::w::draw_info &di, const x::w::picture &area_picture, const x::w::pixmap &area_pixmap, const x::w::gc &area_gc, const x::w::clip_region_set &clipped, const x::w::rectangle &area_entire_rect) override; }; typedef x::ref<my_font_renderer_implObj> my_font_renderer_impl; // Draw the text, and its shadow. void my_font_renderer_implObj::do_draw(ONLY IN_THREAD, const x::w::draw_info &di, const x::w::picture &area_picture, const x::w::pixmap &area_pixmap, const x::w::gc &area_gc, const x::w::clip_region_set &clipped, const x::w::rectangle &area_entire_rect) { for (size_t i=shadow; i > 0; ) { --i; // Compute the starting coordinates for the text. // // We repeatedly render the same text, offset for each layer // of the shadow, and the final text drawn on top of the // shadow. x::w::coord_t x=i; x::w::coord_t y=x::w::coord_t::truncate(ascender+i); // Prepare a composite_text_stream, with the text. // // We expect to involve only one actual font, out of our // fontcollection. However prepare the eventuality that // there will be more than one font. The first lookup()-ed // font will construct the composite_text_stream, and any // extra ones will pile up on top of it. std::optional<x::w::composite_text_stream> composited_text; title_fontcollection->lookup (title.begin(), title.end(), [&] (const std::u32string::const_iterator &beg_iter, const std::u32string::const_iterator &end_iter, const x::w::freetypefont &f) { // First font. if (!composited_text) composited_text.emplace(f, title.size(), 0); f->glyphs_to_stream(beg_iter, end_iter, *composited_text, // Text position, // glyphs_to_stream() // updates it. x, y, // Previous character that // was rendered in the same // font (none). 0, // Replacement char used // in case there's an // unprintable character. U' '); }, ' '); if (!composited_text) continue; // Shouldn't happen, only happens if empty. // Use shadow color, or the final color. auto color= i == 0 ? background_color_element<color1_tag> ::get(IN_THREAD) : background_color_element<color2_tag> ::get(IN_THREAD); composited_text->composite(area_picture, color->get_current_color(IN_THREAD), 0, 0); } } // "Public" object subclasses the public element object. Not really needed // here, just for completeness sake. class my_font_rendererObj : public x::w::elementObj { public: const my_font_renderer_impl impl; // My implementation object. // Constructor my_font_rendererObj(const my_font_renderer_impl &impl) : x::w::elementObj{impl}, impl{impl} { } ~my_font_rendererObj()=default; }; typedef x::ref<my_font_rendererObj> my_font_renderer; // Create the custom font-rendering element. static my_font_renderer create_my_font_renderer(const x::w::container_impl &parent_container, const std::u32string &label) { // To avoid stuffing this whole thing into the constructor, we do // all the prep work here, then construct the internal implementation // object. // The font for the custom title. x::w::font title_font{"Liberation Serif", 32}; title_font.set_slant(title_font.slant_italic); // create_fontcollection() creates a font collection for the display // element. The display element is not yet created, but we have // the parent container, and we can use it to create_fontcollection(). // // Any display element, but only in the same top level window, // can create_fontcollection() for use by any other display element // in the same top level window. However it is not necessary to // try to optimize and share font collections. create_fontcollection() // caches its objects, internally. Using the same exact x::w::font // returns the same, cached, object. auto title_fontcollection=parent_container->container_element_impl() .create_fontcollection(title_font); // Load the glyphs for our unicode characters. title_fontcollection->load_glyphs(label.begin(), label.end(), ' '); // Compute font height above+below the baseline. x::w::dim_t max_ascender=0, max_descender=0; x::w::dim_t width=0; title_fontcollection->lookup(label.begin(), label.end(), [&] (const auto &, const auto &, const x::w::freetypefont &f) { if (f->ascender > max_ascender) max_ascender=f->ascender; if (f->descender > max_descender) max_descender=f->descender; }, ' '); // Compute the text's width. title_fontcollection->glyphs_size_and_kernings (label.begin(), label.end(), [&] (x::w::dim_t c_width, x::w::dim_t c_height, int16_t x_kerning, int16_t y_kerning) { width=x::w::dim_t::truncate(width+c_width+x_kerning); return true; }, 0, ' '); // Now we have everything that the implementation object needs to // construct itself... auto impl=my_font_renderer_impl ::create(parent_container, label, title_fontcollection, width, max_ascender, max_descender, // ... except the shadow's size, which we wing on the // fly, as 1/5th the font's height. x::w::dim_t::truncate((max_ascender+max_descender)/5 +1)); // And create the "public" object for the custom display element. return my_font_renderer::create(impl); } void testfontrenderer() { x::destroy_callback::base::guard guard; auto close_flag=close_flag_ref::create(); x::w::main_window_config config; // Customize the appearance of the main window // // White background color. config.appearance=config.appearance->modify ([] (const x::w::main_window_appearance &appearance) { appearance->background_color=x::w::white; }); auto main_window=x::w::main_window::create (config, [&] (const auto &main_window) { auto layout=main_window->gridlayout(); auto factory=layout->append_row(); // Obtain the parent container from the factory. x::w::container_impl parent_container= factory->get_container_impl(); // Create a custom display element, then tell the // container that it was created_internally(). factory->created_internally (create_my_font_renderer(parent_container, U"Hello World!")); }, x::w::new_gridlayoutmanager{}); main_window->set_window_title("Custom font renderer"); guard(main_window->connection_mcguffin()); main_window->on_disconnect([] { _exit(1); }); main_window->on_delete ([close_flag] (THREAD_CALLBACK, const auto &ignore) { close_flag->close(); }); main_window->show_all(); close_flag->wait(); } int main(int argc, char **argv) { try { testfontrenderer(); } catch (const x::exception &e) { e->caught(); exit(1); } return 0; }
customfontrenderer.C
gives a basic example of using
the following classes and templates:
An
x::w::fontcollection
is a cached object that represents a particular font.
An x::w::font
gets prepared first, which specifies the font's face, size, and
other attributes, then passed to
any internal
widget implementation object to construct a
font collection.
This class is called a
x::w::fontcollection
because it may
represent more than one actual font. Drawn text strings are
unicode strings, std::u32string
, and
it's possible that different
system fonts are required for rendering the entire unicode text.
x::w::fontcollection
and
x::w::freetypefont
operate on a sequence
of unicode text by a beginning and an ending iterator. Any
container of char32_t
, not just a
std::u32string
works as long as its
iterators are at least forward iterators.
An
x::w::freetypefont
is one individual font in a
x::w::fontcollection
.
The
x::w::fontcollection
iterates over the unicode string, and computes the
x::w::freetypefont
for each
part of the std::u32string
.
In nearly all cases, the
x::w::fontcollection
produces one, lonely
x::w::freetypefont
for the entire
std::u32string
;
but the
customfontrenderer.C
examples shows how
to deal with multiple
x::w::freetypefont
s.
Finally, an
x::w::composite_text_stream
is a helper object that prepares and renders the actual text.
Each x::w::freetypefont
takes a range
of unicode characters that it renders, and places them into a
x::w::composite_text_stream
.
Then, its composite
() methods composites
the text into a picture.
customfontrenderer.C
demonstrates the proper
sequence of events for rendering unicode text:
Use a
x::w::font
to create_fontcollection
().
Use the resulting
x::w::fontcollection
's
load_glyphs
() method to load the actual
glyphs, or characters (if they're not yet cached or loaded already).
Other x::w::fontcollection
methods return
various other metadata of the loaded glyphs, such as their size.
customfontrenderer.C
uses them to compute
the size of the custom widget, in advance, when creating
it. This can be done any time before drawing the actual text, and
customfontrenderer.C
does this work just once,
when creating the custom widget.
Using the same x::w::fontcollection
to draw different text requires the new text's glyphs to be
loaded; starting the whole process from the beginning. Although
it's necessary to load only any new character's glyphs, it's simpler
to just pass the entire new string to
load_glyphs
(), which keeps track of what's
been loaded, and quickly skips over previously-loaded glyphs.
Use the
x::w::composite_text_stream
to render the text into a picture buffer.