diff --git a/repos/gems/include/gems/ttf_font.h b/repos/gems/include/gems/ttf_font.h new file mode 100644 index 0000000000..f628d1f9b5 --- /dev/null +++ b/repos/gems/include/gems/ttf_font.h @@ -0,0 +1,74 @@ +/* + * \brief TrueType 'Text_painter::Font' + * \author Norman Feske + * \date 2018-03-20 + */ + +/* + * Copyright (C) 2018 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _INCLUDE__GEMS__TTF_FONT_T_ +#define _INCLUDE__GEMS__TTF_FONT_T_ + +#include +#include + +class Ttf_font : public Text_painter::Font +{ + private: + + typedef Genode::int32_t int32_t; + typedef Genode::Allocator Allocator; + + typedef Text_painter::Codepoint Codepoint; + typedef Text_painter::Area Area; + typedef Text_painter::Glyph Glyph; + + struct Stbtt_font_info; + + static Stbtt_font_info &_create_stbtt_font_info(Allocator &, void const *); + + Stbtt_font_info &_stbtt_font_info; + + float const _scale; + unsigned const _baseline; + Area const _bounding_box; + + struct Glyph_buffer; + + Glyph_buffer &_glyph_buffer; + + public: + + struct Invalid_allocator : Genode::Exception { }; + struct Unsupported_data : Genode::Exception { }; + + /** + * Constructor + * + * \param alloc allocator for dynamic allocations + * \param ttf TrueType font data + * \param px size in pixels + * + * \throw Invalid_allocator 'alloc' is an allocator that needs + * the block size for freeing a blocki + * \throw Unsupported_data unable to parse 'ttf' data + */ + Ttf_font(Allocator &alloc, void const *ttf, float px); + + ~Ttf_font(); + + void _apply_glyph(Codepoint, Apply_fn const &) const override; + + Advance_info advance_info(Codepoint) const override; + + unsigned baseline() const override { return _baseline; } + + Area bounding_box() const override { return _bounding_box; } +}; + +#endif /* _INCLUDE__GEMS__TTF_FONT_T_ */ diff --git a/repos/gems/lib/mk/ttf_font.mk b/repos/gems/lib/mk/ttf_font.mk new file mode 100644 index 0000000000..ce67d91af4 --- /dev/null +++ b/repos/gems/lib/mk/ttf_font.mk @@ -0,0 +1,11 @@ +STB_PORT_DIR := $(call select_from_ports,stb) + +SRC_CC := ttf_font.cc +INC_DIR += $(STB_PORT_DIR)/include +LIBS += libc libm + +# disable warnings caused by stb library +CC_OPT += -Wno-unused-parameter -Wno-unused-function -Wno-unused-value \ + -Wno-unused-variable + +vpath %.cc $(REP_DIR)/src/lib/ttf_font diff --git a/repos/gems/src/lib/ttf_font/ttf_font.cc b/repos/gems/src/lib/ttf_font/ttf_font.cc new file mode 100644 index 0000000000..c0c3623e79 --- /dev/null +++ b/repos/gems/src/lib/ttf_font/ttf_font.cc @@ -0,0 +1,300 @@ +/* + * \brief TrueType implementation of 'Text_painter::Font' interface + * \author Norman Feske + * \date 2018-03-20 + */ + +/* + * Copyright (C) 2018 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +/* Genode includes */ +#include +#include + +/* gems includes */ +#include + +/* libc includes */ +#include + + +/* + * STB TrueType library + */ + +static void *local_malloc(Genode::size_t, void *); +static void local_free(void *, void *); +#define STBTT_malloc local_malloc +#define STBTT_free local_free + +#define STBTT_assert(cond) do { if (!(cond)) { \ + Genode::error("assertion " #cond " failed at stb_truetype.h:", __LINE__); \ + for (;;); } } while (0) + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" + +static void *STBTT_malloc(size_t size, void *userdata) +{ + return ((Genode::Allocator *)userdata)->alloc(size); +} + +static void STBTT_free(void *ptr, void *userdata) +{ + if (ptr) + ((Genode::Allocator *)userdata)->free(ptr, 0); +} + + +/* + * Horizontal and vertical padding around the glyphs + */ +enum { PAD_X = 1, PAD_Y = 1 }; + + +/** + * Buffer for storing the opacity value of a single glyph + * + * Allocated once at 'Ttf_font' construction time, reused for every glyph. + */ +struct Ttf_font::Glyph_buffer +{ + private: + + /* + * Noncopyable + */ + Glyph_buffer(Glyph_buffer const &); + Glyph_buffer &operator = (Glyph_buffer const &); + + /** + * Lookup table applied to opacity values to achieve a more even + * intensity of glyphs at different subpixel positions. + */ + struct Lut + { + unsigned char value[256]; + + Lut() + { + auto fill_segment = [&] (long x1, long y1, long x2, long) { + for (long i = x1>>8; i < x2>>8; i++) + value[i] = Genode::min(255, y1>>8); }; + + bezier(0, 0, 0, 130<<8, 256<<8, 260<<8, fill_segment, 7); + value[0] = 0; + } + } _lut { }; + + public: + + Allocator &alloc; + + typedef Glyph_painter::Glyph::Opacity Opacity; + + /** + * Maximum number of opacity values that fit in the buffer + */ + size_t const capacity; + + size_t _num_bytes() const { return capacity*sizeof(Opacity); } + + Opacity * const _values = (Opacity *)alloc.alloc(_num_bytes()); + + Glyph_buffer(Allocator &alloc, Area bounding_box) + : + alloc(alloc), + + /* glyphs are horizontally stretched by factor 4 */ + capacity(4*(bounding_box.w() + PAD_X)*(bounding_box.h() + PAD_Y)) + { } + + ~Glyph_buffer() { alloc.free(_values, _num_bytes()); } + + Glyph render_shifted(Codepoint, stbtt_fontinfo const &, float scale, + unsigned baseline, float shift_y, bool apply_lut); +}; + + +/** + * Compute quality value for the vertical sharpness of the glyph + */ +static unsigned vertical_sharpness(Glyph_painter::Glyph const &glyph) +{ + unsigned const w = glyph.width; + unsigned sum = 0; + + unsigned char const * const values = (unsigned char const *)glyph.values; + + for (unsigned j = 0; j < glyph.height - 1; j++) { + for (unsigned i = 0; i < w - 1; i++) { + int const dy = (int)values[w*(j + 1) + i] - (int)values[w*j + i]; + sum += dy*dy; + } + } + return sum; +} + + +Ttf_font::Glyph +Ttf_font::Glyph_buffer::render_shifted(Codepoint const c, + stbtt_fontinfo const &font, + float const scale, + unsigned const baseline, + float const shift_y, + bool const apply_lut) +{ + float const shift_x = 0; + + int const filter_x = 4, filter_y = 1; + + int x0 = 0, y0 = 0, x1 = 0, y1 = 0; + stbtt_GetCodepointBitmapBoxSubpixel(&font, c.value, + scale, scale, + shift_x, shift_y, + &x0, &y0, &x1, &y1); + + /* clamp glyph dimensions to the area of the glyph image */ + if (y0 < -(int)baseline) + y0 = -(int)baseline; + + unsigned const dx = x1 - x0; + unsigned const dy = y1 - y0; + + unsigned const width = dx + 1 + PAD_X; + unsigned const height = dy + 1 + PAD_Y; + + unsigned const dst_width = filter_x*width; + + ::memset(_values, 0, dst_width*height); + + float sub_x = 0, sub_y = 0; + stbtt_MakeCodepointBitmapSubpixelPrefilter(&font, + (unsigned char *)_values + x0, + dst_width, dy + 1, dst_width, + scale*4, scale, + shift_x, shift_y, + filter_x, filter_y, + &sub_x, &sub_y, + c.value); + + int advance = 0, lsb = 0; + stbtt_GetCodepointHMetrics(&font, c.value, &advance, &lsb); + + /* apply non-linear transfer function */ + if (apply_lut) + for (unsigned i = 0; i < dst_width*height; i++) + _values[i].value = _lut.value[_values[i].value]; + + return Glyph { .width = width, + .height = height, + .vpos = (unsigned)((int)baseline + y0), + .advance = scale*advance, + .values = _values }; +} + + +struct Ttf_font::Stbtt_font_info : stbtt_fontinfo +{ + Stbtt_font_info() : stbtt_fontinfo() { } +}; + + +static Text_painter::Area obtain_bounding_box(stbtt_fontinfo const &font, float scale) +{ + int x0 = 0, y0 = 0, x1 = 0, y1 = 0; + stbtt_GetFontBoundingBox(&font, &x0, &y0, &x1, &y1); + + int const w = x1 - x0 + 1, + h = y1 - y0 + 1; + + if (w < 1 || h < 1) + throw Ttf_font::Unsupported_data(); + + return Text_painter::Area(w*scale + 2*PAD_X, h*scale + 2*PAD_Y); +} + + +static unsigned obtain_baseline(stbtt_fontinfo const &font, float scale) +{ + int ascent = 0; + stbtt_GetFontVMetrics(&font, &ascent, 0, 0); + + return (unsigned)(ascent*scale); +} + + +Ttf_font::Stbtt_font_info & +Ttf_font::_create_stbtt_font_info(Allocator &alloc, void const *ttf) +{ + if (alloc.need_size_for_free()) + throw Invalid_allocator(); + + Stbtt_font_info &stbtt_font_info = *new (alloc) Stbtt_font_info(); + stbtt_font_info.userdata = &alloc; + stbtt_InitFont(&stbtt_font_info, (unsigned char *)ttf, 0); + + return stbtt_font_info; +} + + +Ttf_font::Ttf_font(Allocator &alloc, void const *ttf, float px) +: + _stbtt_font_info(_create_stbtt_font_info(alloc, ttf)), + _scale(stbtt_ScaleForPixelHeight(&_stbtt_font_info, px)), + _baseline(obtain_baseline(_stbtt_font_info, _scale)), + _bounding_box(obtain_bounding_box(_stbtt_font_info, _scale)), + _glyph_buffer(*new (alloc) Glyph_buffer(alloc, _bounding_box)) +{ } + + +Ttf_font::~Ttf_font() +{ + Allocator &alloc = *(Allocator *)(_stbtt_font_info.userdata); + + destroy(alloc, &_glyph_buffer); + destroy(alloc, &_stbtt_font_info); +} + + +void Ttf_font::_apply_glyph(Codepoint c, Apply_fn const &fn) const +{ + /* find vertical subpixel position that yields the sharpest glyph */ + float best_shift_y = 0; + { + unsigned sharpest = 0; + for (float shift_y = -0.3; shift_y < 0.3; shift_y += 0.066) { + + Glyph const glyph = + _glyph_buffer.render_shifted(c, _stbtt_font_info, _scale, + _baseline, shift_y, false); + unsigned const s = vertical_sharpness(glyph); + if (s > sharpest) { + sharpest = s; + best_shift_y = shift_y; + } + } + } + best_shift_y = 0; + + /* re-render the best result with the LUT applied */ + Glyph const glyph = + _glyph_buffer.render_shifted(c, _stbtt_font_info, _scale, + _baseline, best_shift_y, true); + fn.apply(glyph); +} + + +Text_painter::Font::Advance_info Ttf_font::advance_info(Codepoint c) const +{ + int advance = 0, lsb = 0; + stbtt_GetCodepointHMetrics(&_stbtt_font_info, c.value, &advance, &lsb); + + return Font::Advance_info { .width = (unsigned)(_scale*advance), + .advance = _scale*advance }; +} +