aboutsummaryrefslogtreecommitdiffstats
path: root/src/gdisp/mcufont/mf_kerning.c
blob: 0163fbd9a5e2276e224d0d41aed7b3c138ccecf5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*
 * This file is subject to the terms of the GFX License. If a copy of
 * the license was not distributed with this file, you can obtain one at:
 *
 *              http://ugfx.org/license.html
 */

#include "mf_kerning.h"
#include <stdbool.h>

#if MF_USE_KERNING

/* Structure for keeping track of the edge of the glyph as it is rendered. */
struct kerning_state_s
{
    uint8_t edgepos[MF_KERNING_ZONES];
    uint8_t zoneheight;
};

/* Pixel callback for analyzing the left edge of a glyph. */
static void fit_leftedge(int16_t x, int16_t y, uint8_t count, uint8_t alpha,
                         void *state)
{
    struct kerning_state_s *s = state;
    
    if (alpha > 7)
    {
        uint8_t zone = y / s->zoneheight;
        if (x < s->edgepos[zone])
            s->edgepos[zone] = x;
    }
}

/* Pixel callback for analyzing the right edge of a glyph. */
static void fit_rightedge(int16_t x, int16_t y, uint8_t count, uint8_t alpha,
                         void *state)
{
    struct kerning_state_s *s = state;
    
    if (alpha > 7)
    {
        uint8_t zone = y / s->zoneheight;
        x += count - 1;
        if (x > s->edgepos[zone])
            s->edgepos[zone] = x;
    }
}

/* Should kerning be done against this character? */
static bool do_kerning(mf_char c)
{
    /* Just a speed optimization, spaces would be ignored anyway. */
    if (c == ' ' || c == '\n' || c == '\r' || c == '\t')
        return false;
    
    /* Do not kern against digits, in order to keep values in tables nicely
     * aligned. Most fonts have constant width for digits. */
    if (c >= '0' && c <= '9')
        return false;
    
    return true;
}

static int16_t min16(int16_t a, int16_t b) { return (a < b) ? a : b; }
static int16_t max16(int16_t a, int16_t b) { return (a > b) ? a : b; }
static int16_t avg16(int16_t a, int16_t b) { return (a + b) / 2; }

int8_t mf_compute_kerning(const struct mf_font_s *font,
                          mf_char c1, mf_char c2)
{
    struct kerning_state_s leftedge, rightedge;
    uint8_t w1, w2, i, min_space;
    int16_t normal_space, adjust, max_adjust;
    
    if (font->flags & MF_FONT_FLAG_MONOSPACE)
        return 0; /* No kerning for monospace fonts */
    
    if (!do_kerning(c1) || !do_kerning(c2))
        return 0;
    
    /* Compute the height of one kerning zone in pixels */
    i = (font->height + MF_KERNING_ZONES - 1) / MF_KERNING_ZONES;
    if (i < 1) i = 1;
    
    /* Initialize structures */
    leftedge.zoneheight = rightedge.zoneheight = i;
    for (i = 0; i < MF_KERNING_ZONES; i++)
    {
        leftedge.edgepos[i] = 255;
        rightedge.edgepos[i] = 0;
    }
    
    /* Analyze the edges of both glyphs. */
    w1 = mf_render_character(font, 0, 0, c1, fit_rightedge, &rightedge);
    w2 = mf_render_character(font, 0, 0, c2, fit_leftedge, &leftedge);
    
    /* Find the minimum horizontal space between the glyphs. */
    min_space = 255;
    for (i = 0; i < MF_KERNING_ZONES; i++)
    {
        uint8_t space;
        if (leftedge.edgepos[i] == 255 || rightedge.edgepos[i] == 0)
            continue; /* Outside glyph area. */
        
        space = w1 - rightedge.edgepos[i] + leftedge.edgepos[i];
        if (space < min_space)
            min_space = space;
    }
    
    if (min_space == 255)
        return 0; /* One of the characters is space, or both are punctuation. */
    
    /* Compute the adjustment of the glyph position. */
    normal_space = avg16(w1, w2) * MF_KERNING_SPACE_PERCENT / 100;
    normal_space += MF_KERNING_SPACE_PIXELS;
    adjust = normal_space - min_space;
    max_adjust = -max16(w1, w2) * MF_KERNING_LIMIT / 100;
    
    if (adjust > 0) adjust = 0;
    if (adjust < max_adjust) adjust = max_adjust;
    
    return adjust;
}

#endif