/*
 * \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
 */

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"

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"

#pragma GCC diagnostic pop  /* restore -Wconversion warnings */

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] = (unsigned char)Genode::min(255l, y1>>8); };

				bezier(0, 0, 0, 130<<8, 256<<8, 260<<8, fill_segment, 7);
				value[0] = 0;
			}
		} _lut { };

	public:

		Allocator &alloc;

		using Opacity = Glyph_painter::Glyph::Opacity;

		/**
		 * Maximum number of opacity values that fit in the buffer
		 */
		size_t const capacity;

		size_t const _headroom = 5;

		size_t _num_bytes() const { return (capacity + _headroom)*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;

	/* x0 may be negative, clamp its lower bound to headroom of the buffer */
	x0 = Genode::max(-(int)_headroom, x0);

	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;
	unsigned char * const dst_ptr   = (unsigned char *)_values + _headroom;

	bzero(dst_ptr, dst_width*height);

	float sub_x = 0, sub_y = 0;
	stbtt_MakeCodepointBitmapSubpixelPrefilter(&font, dst_ptr + 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 * (float)advance,
	               .values  = _values + _headroom };
}


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);

	float const w = (float)(x1 - x0 + 1),
	            h = (float)(y1 - y0 + 1);

	if (w < 1 || h < 1)
		throw Ttf_font::Unsupported_data();

	return Text_painter::Area((unsigned)(w*scale) + 2*PAD_X,
	                          (unsigned)(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)((float)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)),
	_px(px),
	_scale(stbtt_ScaleForPixelHeight(&_stbtt_font_info, px)),
	_baseline(obtain_baseline(_stbtt_font_info, _scale)),
	_height((unsigned)(px + 0.5)),
	_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
{
	if (_px < 1.0) {
		Glyph const glyph { .width   = 0,
		                    .height  = 0,
		                    .vpos    = 0,
		                    .advance = 0,
		                    .values  = nullptr };
		fn.apply(glyph);
		return;
	}

	/* find vertical subpixel position that yields the sharpest glyph */
	float best_shift_y = 0;
	{
		unsigned sharpest = 0;
		for (float shift_y = -0.3f; shift_y < 0.3f; shift_y += 0.066f) {

			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);

	float const scaled_advance = _scale * (float)advance;

	return Font::Advance_info { .width   = (unsigned)scaled_advance,
	                            .advance = scaled_advance };
}

