summaryrefslogtreecommitdiff
path: root/src/text.cc
blob: 4306f91f2c574ddf1b58addafc4321d35d18a990 (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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#include "text.h"

#include <fcntl.h>
#include <mimalloc.h>
#include <unistd.h>
#include <utils.h>

#include "cglm/types.h"
#include "cglm/vec2.h"
#include "freetype/freetype.h"
#include "hash.h"
#include "rendering.h"
#include "utils.h"

static bool wayc_font_cache_lookup(struct font_s* font, codepoint_t codepoint,
                                   struct glyph_s* out) {
  struct glyph_s glyph;
  bool ok = wayc_hashmap_get(&font->cache, &codepoint, &glyph);
  if (ok) *out = glyph;
  return ok;
}

static void wayc_font_cache_insert(struct font_s* font, codepoint_t codepoint,
                                   struct glyph_s glyph) {
  wayc_notnull(font);
  wayc_hashmap_insert(&font->cache, &codepoint, &glyph);
}

enum font_context_error_e wayc_font_context_init(struct font_context_s* ctx) {
  wayc_notnull(ctx);

  FT_Error err = FT_Init_FreeType(&ctx->library);
  if (err) return FONT_CONTEXT_ERROR_LIBRARY_LOADING;
  return FONT_CONTEXT_ERROR_NONE;
}

void wayc_font_context_deinit(struct font_context_s* ctx) {
  wayc_notnull(ctx);

  FT_Done_FreeType(ctx->library);
}

enum font_error_e wayc_font_init(struct font_s* font,
                                 struct font_context_s* ctx, u32 fsize,
                                 const char* path) {
  wayc_notnull(font);
  wayc_notnull(ctx);
  wayc_notnull(path);

  bool success = false;

  i32 fd = open(path, O_RDONLY);
  if (fd < 0) return FONT_ERROR_FILE_LOAD;
  wayc_defer(close(fd));

  isize size = lseek(fd, 0, SEEK_END);
  if (size < 0) return FONT_ERROR_FILE_LOAD;
  lseek(fd, 0, SEEK_SET);

  u8* data = (u8*)mi_malloc(size);
  wayc_defer_cond({ mi_free(data); }, success, true);

  isize _read = read(fd, data, size);
  if (_read < 0 || _read != size) return FONT_ERROR_FILE_LOAD;

  FT_Error err = FT_New_Memory_Face(ctx->library, data, size, 0, &font->face);
  if (err) return FONT_ERROR_FONT_LOAD;
  wayc_defer_cond(FT_Done_Face(font->face);, success, true);

  err = FT_Set_Pixel_Sizes(font->face, 0, fsize);
  if (err) return FONT_ERROR_FONT_LOAD;

  font->data = data;
  wayc_hashmap_init(&font->cache);

  success = true;
  return FONT_ERROR_NONE;
}

enum font_error_e wayc_font_lookup(struct font_s* font, struct atlas_s* atlas,
                                   struct atlas_packer_s* packer,
                                   codepoint_t codepoint, struct glyph_s* out) {
  wayc_notnull(font);
  wayc_notnull(atlas);
  wayc_notnull(out);

  FT_Face face = font->face;

  bool found = wayc_font_cache_lookup(font, codepoint, out);
  if (found) return FONT_ERROR_NONE;

  FT_UInt glyph_index = FT_Get_Char_Index(face, codepoint);
  if (glyph_index == 0) return FONT_ERROR_NOT_MATCH;

  FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
  FT_GlyphSlot slot = face->glyph;
  FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL);

  FT_Bitmap bitmap = slot->bitmap;
  if (bitmap.pitch != (i32)bitmap.width) wayc_panic("unsupported pitch");

  ivec2 size = {(i32)bitmap.width, (i32)bitmap.rows};
  ivec2 uv0;

  bool ok = wayc_atlas_packer_allocate(packer, WAYC_X(size), WAYC_Y(size), uv0);
  if (!ok) return FONT_ERROR_ATLAS_FULL;

  ok = wayc_atlas_set(atlas, IMAGE_DATA_TYPE_UNSIGNED_BYTE, uv0, WAYC_X(size),
                      WAYC_Y(size), IMAGE_FORMAT_RED_ALIGNMENT, bitmap.buffer);
  if (!ok) return FONT_ERROR_ATLAS_FULL;

  struct glyph_s glyph = {};
  glyph.bearing_x = slot->bitmap_left;
  glyph.bearing_y = slot->bitmap_top;
  glyph.advance = (f32)slot->advance.x / WAYC_SCALE;

  wayc_font_cache_insert(font, codepoint, glyph);

  *out = glyph;
  return FONT_ERROR_NONE;
}

void wayc_font_deinit(struct font_s* font) {
  wayc_notnull(font);

  wayc_hashmap_deinit(&font->cache);
  FT_Done_Face(font->face);
  mi_free(font->data);
}

enum text_error_e wayc_text_vertices(struct text_s* text,
                                     struct text_vertex_s** out) {
  wayc_notnull(text);
  wayc_notnull(out);

  bool success = false;

  usize nverts = WAYC_LETTER_NVERTICES * wayc_string_length(&text->string);
  usize verts_bytes = nverts * sizeof(struct text_vertex_s);

  struct text_vertex_s* verts = (struct text_vertex_s*)mi_malloc(verts_bytes);
  wayc_defer_cond(mi_free(verts), success, true);

  vec2 pen = GLM_VEC2_ZERO_INIT;
  usize idx = 0;
  for (usize i = 0; i < wayc_string_length(&text->string); i++) {
    codepoint_t codep =
        wayc_string_data(&text->string)[i];  // TODO: do proper UTF-8 decoding

    struct glyph_s glyph;
    enum font_error_e err =
        wayc_font_lookup(text->font, text->atlas, text->packer, codep, &glyph);
    if (err != FONT_ERROR_NONE) return TEXT_ERROR_ATLAS;

    f32 x0 = WAYC_X(pen) + glyph.bearing_x;
    f32 y0 = WAYC_Y(pen) - glyph.bearing_y;
    f32 x1 = x0 + WAYC_X(glyph.uv1) - WAYC_X(glyph.uv0);
    f32 y1 = y0 + WAYC_Y(glyph.uv1) - WAYC_Y(glyph.uv0);

    f32 u0 = (f32)WAYC_X(glyph.uv0) / text->atlas->width;
    f32 v0 = (f32)WAYC_Y(glyph.uv0) / text->atlas->height;
    f32 u1 = (f32)WAYC_X(glyph.uv1) / text->atlas->width;
    f32 v1 = (f32)WAYC_Y(glyph.uv1) / text->atlas->height;

    struct text_vertex_s ps0 = {{x0, y0}, {u0, v0}};
    struct text_vertex_s ps1 = {{x0, y1}, {u0, v1}};
    struct text_vertex_s ps2 = {{x1, y1}, {u1, v1}};
    struct text_vertex_s ps3 = {{x1, y0}, {u1, v0}};

    verts[idx++] = ps0;
    verts[idx++] = ps1;
    verts[idx++] = ps2;
    verts[idx++] = ps0;
    verts[idx++] = ps2;
    verts[idx++] = ps3;
  }

  success = true;
  *out = verts;
  return TEXT_ERROR_NONE;
}