mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-21 06:33:31 +00:00
gems: ttf_font library
This commit is contained in:
parent
f837df34b1
commit
23f07331c8
74
repos/gems/include/gems/ttf_font.h
Normal file
74
repos/gems/include/gems/ttf_font.h
Normal file
@ -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 <base/allocator.h>
|
||||
#include <nitpicker_gfx/text_painter.h>
|
||||
|
||||
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_ */
|
11
repos/gems/lib/mk/ttf_font.mk
Normal file
11
repos/gems/lib/mk/ttf_font.mk
Normal file
@ -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
|
300
repos/gems/src/lib/ttf_font/ttf_font.cc
Normal file
300
repos/gems/src/lib/ttf_font/ttf_font.cc
Normal file
@ -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 <base/log.h>
|
||||
#include <util/bezier.h>
|
||||
|
||||
/* gems includes */
|
||||
#include <gems/ttf_font.h>
|
||||
|
||||
/* libc includes */
|
||||
#include <math.h>
|
||||
|
||||
|
||||
/*
|
||||
* 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 };
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user