GCC Code Coverage Report
Directory: ../src/ Exec Total Coverage
File: /home/joels/Current/lispbm/src/extensions/ttf_extensions.c Lines: 0 538 0.0 %
Date: 2025-04-09 11:39:30 Branches: 0 286 0.0 %

Line Branch Exec Source
1
/*
2
  Copyright 2025 Joel Svensson              svenssonjoel@yahoo.se
3
4
  LispBM is free software: you can redistribute it and/or modify
5
  it under the terms of the GNU General Public License as published by
6
  the Free Software Foundation, either version 3 of the License, or
7
  (at your option) any later version.
8
9
  LispBM is distributed in the hope that it will be useful,
10
  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
  GNU General Public License for more details.
13
14
  You should have received a copy of the GNU General Public License
15
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
*/
17
18
#include <extensions/ttf_extensions.h>
19
#include <extensions.h>
20
#include <buffer.h>
21
22
#include "schrift.h"
23
24
25
static bool mk_font_raw(SFT_Font *ft, lbm_value font_val) {
26
  lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(font_val);
27
  if (arr) {
28
    ft->memory = (uint8_t*)arr->data;
29
    ft->size = (uint_fast32_t)arr->size;
30
    ft->unitsPerEm = 0;
31
    ft->locaFormat = 0;
32
    ft->numLongHmtx = 0;
33
    return (init_font(ft) >= 0) ? true : false;
34
  }
35
  return false;
36
}
37
38
static SFT mk_sft(SFT_Font *ft, float x_scale, float y_scale) {
39
  SFT sft;
40
  sft.font = ft;
41
  sft.xScale = x_scale;
42
  sft.yScale = y_scale;
43
  sft.xOffset = 0;
44
  sft.yOffset = 0;
45
  sft.flags = SFT_DOWNWARD_Y;
46
47
  return sft;
48
}
49
50
// If we are not bin searching then sorting the UTF32 codes is not needed.
51
52
#define FONT_MAX_ID_STRING_LENGTH   10
53
#define FONT_VERSION                0
54
#define FONT_MAGIC_STRING           "font"
55
#define FONT_LINE_METRICS_STRING    "lmtx"
56
#define FONT_KERNING_STRING         "kern"
57
#define FONT_GLYPHS_STRING          "glyphs"
58
59
// sizeof when used on string literals include the the terminating 0
60
#define FONT_PREAMBLE_SIZE          (sizeof(uint16_t) * 2 + sizeof(FONT_MAGIC_STRING))
61
#define FONT_LINE_METRICS_SIZE      (sizeof(uint32_t) + (sizeof(float) * 3) + sizeof(FONT_LINE_METRICS_STRING))
62
63
// "header sizes" excluding data payload
64
#define FONT_KERN_PAIR_SIZE         (uint32_t)(4 + 4 + 4)
65
#define FONT_KERN_ROW_SIZE          (uint32_t)(4 + 4)
66
#define FONT_KERN_TABLE_SIZE        (uint32_t)(sizeof(FONT_KERNING_STRING) + 4 + 4)
67
#define FONT_GLYPH_TABLE_SIZE       (uint32_t)(sizeof(FONT_GLYPHS_STRING) + 4 + 4 + 4)
68
#define FONT_GLYPH_SIZE             (uint32_t)(6*4)
69
70
static int num_kern_pairs_row(SFT *sft, uint32_t utf32, uint32_t *codes, uint32_t num_codes) {
71
72
  int num = 0;
73
74
  SFT_Glyph lgid;
75
  if (sft_lookup(sft, utf32, &lgid) < 0) {
76
    return -1;
77
  }
78
79
  for (uint32_t i = 0; i < num_codes; i ++) {
80
    uint32_t right_utf32 = codes[i];
81
    SFT_Kerning kern;
82
    kern.xShift = 0.0;
83
    kern.yShift = 0.0;
84
85
    SFT_Glyph rgid;
86
    if (sft_lookup(sft, right_utf32, &rgid) < 0) {
87
      return -1;
88
    }
89
90
    if (sft->font->pairAdjustOffset) {
91
      sft_gpos_kerning(sft, lgid, rgid, &kern); //TODO: can it fail?
92
    }
93
    if (kern.xShift == 0.0 && kern.yShift == 0.0) {
94
      sft_kerning(sft, lgid, rgid, &kern); //TODO: can it fail?
95
    }
96
    if (kern.xShift != 0.0 || kern.yShift != 0.0) {
97
      num++;
98
    }
99
  }
100
  return num;
101
}
102
103
static bool kern_table_dims(SFT *sft, uint32_t *codes, uint32_t num_codes, int *rows, int *tot_pairs) {
104
105
  int num_rows = 0;
106
  int tot_kern_pairs = 0;
107
  for (uint32_t i = 0; i < num_codes; i ++) {
108
    int r = num_kern_pairs_row(sft, codes[i], codes, num_codes);
109
    if (r > 0) {
110
      num_rows ++;
111
      tot_kern_pairs += r;
112
    } else if (r < 0) {
113
      return false;
114
    }
115
  }
116
  *rows = num_rows;
117
  *tot_pairs = tot_kern_pairs;
118
  return true;
119
}
120
121
static int kern_table_size_bytes(SFT *sft, uint32_t *codes, uint32_t num_codes) {
122
  int rows = 0;
123
  int tot_pairs = 0;
124
125
  int size_bytes;
126
  if (kern_table_dims(sft, codes, num_codes, &rows, &tot_pairs)) {
127
    size_bytes =
128
      (int)(FONT_KERN_PAIR_SIZE * (uint32_t)tot_pairs +
129
            FONT_KERN_ROW_SIZE * (uint32_t)rows +
130
            FONT_KERN_TABLE_SIZE);
131
  } else {
132
    return -1;
133
  }
134
  return size_bytes;
135
}
136
137
138
static void buffer_append_string(uint8_t *buffer, char *str, int32_t *index) {
139
  size_t n = strlen(str);
140
  memcpy(&buffer[*index], str, n + 1); // include the 0
141
  *index = *index + (int32_t)n + 1;
142
}
143
144
static void buffer_append_font_preamble(uint8_t *buffer, int32_t *index) {
145
  buffer_append_uint16(buffer, 0, index); // 2 leading zero bytes
146
  buffer_append_uint16(buffer, 0, index); // version 0
147
  buffer_append_string(buffer, FONT_MAGIC_STRING, index);
148
}
149
150
static void buffer_append_line_metrics(uint8_t *buffer, float ascender, float descender, float line_gap, int32_t *index) {
151
  buffer_append_string(buffer, FONT_LINE_METRICS_STRING, index);
152
  buffer_append_uint32(buffer, sizeof(float) * 3, index);
153
  buffer_append_float32_auto(buffer, ascender, index);
154
  buffer_append_float32_auto(buffer, descender, index);
155
  buffer_append_float32_auto(buffer, line_gap, index);
156
}
157
158
159
static bool buffer_append_kerning_table(uint8_t *buffer, SFT *sft, uint32_t *codes, uint32_t num_codes, int32_t *index) {
160
161
  int num_rows = 0;
162
  int tot_pairs = 0;
163
164
  if (kern_table_dims(sft, codes, num_codes, &num_rows, &tot_pairs)) {
165
166
    // TODO: compute size of "payload" only
167
    uint32_t size_bytes =
168
      FONT_KERN_PAIR_SIZE * (uint32_t)tot_pairs +
169
      FONT_KERN_ROW_SIZE * (uint32_t)num_rows +
170
      + 4; // number of rows field
171
172
    buffer_append_string(buffer, FONT_KERNING_STRING, index);
173
    buffer_append_uint32(buffer, size_bytes, index); // distance to jump ahead from index if not interested in kerning.
174
    buffer_append_uint32(buffer, (uint32_t)num_rows, index);
175
176
    for (uint32_t left_ix = 0; left_ix < num_codes; left_ix ++) { // loop over all codes
177
      int32_t row_len = num_kern_pairs_row(sft, codes[left_ix], codes, num_codes);
178
      if ( row_len > 0) {
179
        SFT_Glyph lgid;
180
        if (sft_lookup(sft, codes[left_ix], &lgid) < 0) {
181
          return false;
182
        }
183
184
        // format kerning table row
185
        // - UTF32 : leftGlyph
186
        // - uint32 : numKernPairs
187
        // - KernPair[]
188
189
        buffer_append_uint32(buffer, codes[left_ix],index);
190
        buffer_append_uint32(buffer, (uint32_t)row_len, index);
191
192
        for (uint32_t right_ix = 0; right_ix < num_codes; right_ix ++) { // and all codes
193
          uint32_t right_utf32 = codes[right_ix];
194
          SFT_Kerning kern;
195
          kern.xShift = 0.0;
196
          kern.yShift = 0.0;
197
198
          // format KernPair
199
          // - UTF32 : rightGlyph
200
          // - float : xShift
201
          // - float : yShift
202
203
          SFT_Glyph rgid;
204
          if (sft_lookup(sft, right_utf32, &rgid) < 0) {
205
            return false;
206
          }
207
208
          if (sft->font->pairAdjustOffset) {
209
            sft_gpos_kerning(sft, lgid, rgid, &kern); //TODO: can it fail?
210
          }
211
          if (kern.xShift == 0.0 && kern.yShift == 0.0) {
212
            sft_kerning(sft, lgid, rgid, &kern); //TODO: can it fail?
213
          }
214
          if (kern.xShift != 0.0 || kern.yShift != 0.0) {
215
            buffer_append_uint32(buffer, right_utf32, index);
216
            buffer_append_float32_auto(buffer, kern.xShift, index);
217
            buffer_append_float32_auto(buffer, kern.yShift, index);
218
          }
219
        }
220
      }
221
    }
222
  }
223
  return true;
224
}
225
226
int glyphs_img_data_size(SFT *sft, color_format_t fmt, uint32_t *codes, uint32_t num_codes) {
227
  int total_size = 0;
228
  for (uint32_t i = 0; i < num_codes; i ++) {
229
    SFT_Glyph gid;
230
    if (sft_lookup(sft, codes[i], &gid) < 0)  return -1;
231
    SFT_GMetrics gmtx;
232
    if (sft_gmetrics(sft, gid, &gmtx) < 0) return -1;
233
    total_size += (int)image_dims_to_size_bytes(fmt, (uint16_t)gmtx.minWidth, (uint16_t)gmtx.minHeight);
234
  }
235
  return total_size;
236
}
237
238
static int buffer_append_glyph(uint8_t *buffer, SFT *sft, color_format_t fmt, uint32_t utf32, int32_t *index){
239
  SFT_Glyph gid;
240
  if (sft_lookup(sft, utf32, &gid) < 0)  return -1;
241
  SFT_GMetrics gmtx;
242
  if (sft_gmetrics(sft, gid, &gmtx) < 0) return -1;
243
244
  buffer_append_uint32(buffer, utf32, index);
245
  buffer_append_float32_auto(buffer, gmtx.advanceWidth, index);
246
  buffer_append_float32_auto(buffer, gmtx.leftSideBearing, index);
247
  buffer_append_int32(buffer,gmtx.yOffset,index);
248
  buffer_append_int32(buffer,gmtx.minWidth, index);
249
  buffer_append_int32(buffer,gmtx.minHeight, index);
250
251
  image_buffer_t img;
252
  img.width = (uint16_t)gmtx.minWidth;
253
  img.height = (uint16_t)gmtx.minHeight;
254
  img.fmt = fmt;
255
  img.mem_base = &buffer[*index];
256
  img.data = &buffer[*index];
257
258
  int r = sft_render(sft, gid, &img);
259
  *index += (int32_t)image_dims_to_size_bytes(fmt, (uint16_t)gmtx.minWidth, (uint16_t)gmtx.minHeight);
260
  return r;
261
}
262
263
static int buffer_append_glyph_table(uint8_t *buffer, SFT *sft, color_format_t fmt, uint32_t *codes, uint32_t num_codes, int32_t *index) {
264
265
  uint32_t size_bytes =
266
    4 + // number of glyphs
267
    4 + // image format
268
    num_codes * 24 + // glyph metrics
269
    (uint32_t)glyphs_img_data_size(sft,fmt,codes,num_codes);
270
271
  buffer_append_string(buffer, FONT_GLYPHS_STRING, index);
272
  buffer_append_uint32(buffer, size_bytes, index); // distance to jump ahead from index if not interested in kerning.
273
  buffer_append_uint32(buffer, num_codes, index);
274
  buffer_append_uint32(buffer, (uint32_t)fmt, index);
275
276
  int r = 0;
277
  for (uint32_t i = 0; i < num_codes; i ++) {
278
    r = buffer_append_glyph(buffer,sft,fmt,codes[i], index);
279
    if (r < 0) return r;
280
  }
281
  return r;
282
}
283
284
//returns the increment for n
285
static int insert_nub(uint32_t *arr, uint32_t n, uint32_t new_elt) {
286
  uint32_t i;
287
  for (i = 0; i < n; i ++) {
288
    if (arr[i] == new_elt) return 0;
289
    if (arr[i] > new_elt) {
290
      memmove(&arr[i+1], &arr[i], (n - i) * 4);
291
      arr[i] = new_elt;
292
      return 1;
293
    }
294
  }
295
  arr[i] = new_elt;
296
  return 1;
297
}
298
299
// (ttf-prepare-bin font font-scale img-fmt chars-string)
300
lbm_value ext_ttf_prepare_bin(lbm_value *args, lbm_uint argn) {
301
  if (argn == 4 &&
302
      lbm_is_array_r(args[0]) && // font file data
303
      lbm_is_number(args[1])  &&
304
      lbm_is_symbol(args[2])  &&
305
      lbm_is_array_r(args[3])) {
306
307
    float x_scale = lbm_dec_as_float(args[1]);
308
    float y_scale = x_scale;
309
310
    color_format_t fmt = sym_to_color_format(args[2]);
311
312
    lbm_value result_array_cell = lbm_heap_allocate_cell(LBM_TYPE_CONS, ENC_SYM_NIL, ENC_SYM_ARRAY_TYPE);
313
314
    if (result_array_cell == ENC_SYM_MERROR) return result_array_cell;
315
    lbm_array_header_t *result_array_header = (lbm_array_header_t *)lbm_malloc(sizeof(lbm_array_header_t));
316
    if (!result_array_header) return ENC_SYM_MERROR;
317
318
    lbm_array_header_t *utf8_array_header = (lbm_array_header_t*)(lbm_car(args[3]));
319
320
    // Try to keep the utf8 array as nubbed as possible or there will be waste of mem.
321
    // Unfortunate dynamic tmp storage...
322
    uint32_t* unique_utf32 = lbm_malloc(utf8_array_header->size * sizeof(uint32_t));
323
324
    if (unique_utf32) {
325
326
      SFT_Font ft;
327
      if (!mk_font_raw(&ft,args[0])) {
328
        lbm_free(unique_utf32);
329
        return ENC_SYM_EERROR;
330
      }
331
      SFT sft = mk_sft(&ft, x_scale, y_scale);
332
333
      // We know which glyphs to prerender...
334
      // So time to start collecting information to put into the binary prerender format
335
      // and to figure out how much prerender space to allocate!
336
337
      uint32_t i = 0;
338
      uint32_t next_i = 0;
339
      uint32_t utf32;
340
      uint32_t n = 0;
341
342
      while (get_utf32((uint8_t*)utf8_array_header->data, &utf32, i, &next_i)) {
343
        n += (uint32_t)insert_nub(unique_utf32, n, utf32);
344
        i = next_i;
345
      }
346
347
      // There could be zero kerning pairs and then we dont
348
      // need the kerning table at all.
349
      // TODO: Fix this.
350
      int kern_tab_bytes = kern_table_size_bytes(&sft, unique_utf32, n);
351
      if (kern_tab_bytes <=  0) {
352
        lbm_free(unique_utf32);
353
        return ENC_SYM_EERROR;
354
      }
355
356
      int glyph_gfx_size = glyphs_img_data_size(&sft, fmt, unique_utf32, n);
357
      if (glyph_gfx_size <= 0) {
358
        lbm_free(unique_utf32);
359
        return ENC_SYM_EERROR;
360
      }
361
362
      uint32_t bytes_required =
363
        FONT_PREAMBLE_SIZE +
364
        FONT_LINE_METRICS_SIZE +
365
        (uint32_t)kern_tab_bytes +
366
        FONT_GLYPH_TABLE_SIZE +
367
        n * FONT_GLYPH_SIZE + // per glyph metrics
368
        (uint32_t)glyph_gfx_size;
369
370
      uint8_t *buffer = (uint8_t*)lbm_malloc(bytes_required);
371
      if (!buffer) {
372
        lbm_free(unique_utf32);
373
        return ENC_SYM_MERROR;
374
      }
375
      memset(buffer,0, bytes_required);
376
377
      SFT_LMetrics lmtx;
378
      if (sft_lmetrics(&sft, &lmtx) < 0) {
379
        lbm_free(unique_utf32);
380
        return ENC_SYM_EERROR;
381
      }
382
      int32_t index = 0;
383
384
      buffer_append_font_preamble(buffer, &index);
385
      buffer_append_line_metrics(buffer,
386
                                 lmtx.ascender,
387
                                 lmtx.descender,
388
                                 lmtx.lineGap,
389
                                 &index);
390
      buffer_append_kerning_table(buffer, &sft, unique_utf32, n, &index);
391
392
      int r = buffer_append_glyph_table(buffer, &sft, fmt, unique_utf32, n, &index);
393
      if ( r == SFT_MEM_ERROR) {
394
        lbm_free(unique_utf32);
395
        lbm_free(buffer);
396
        lbm_set_car_and_cdr(result_array_cell, ENC_SYM_NIL, ENC_SYM_NIL);
397
        return ENC_SYM_MERROR;
398
      } else if (r < 0) {
399
        lbm_free(unique_utf32);
400
        lbm_free(buffer);
401
        lbm_set_car_and_cdr(result_array_cell, ENC_SYM_NIL, ENC_SYM_NIL);
402
        return ENC_SYM_EERROR;
403
      }
404
405
      lbm_free(unique_utf32); // tmp data nolonger needed
406
      result_array_header->size = (lbm_uint)index;
407
      result_array_header->data = (lbm_uint*)buffer;
408
      lbm_set_car(result_array_cell, (lbm_uint)result_array_header);
409
      result_array_cell = lbm_set_ptr_type(result_array_cell, LBM_TYPE_ARRAY);
410
      return result_array_cell;
411
    } else {
412
      return ENC_SYM_MERROR;
413
    }
414
  }
415
  return ENC_SYM_TERROR;
416
}
417
418
bool buffer_get_font_preamble(uint8_t* buffer, uint16_t *version, int32_t *index) {
419
420
  uint16_t zero = buffer_get_uint16(buffer, index);
421
  if (zero == 0) {
422
    *version = buffer_get_uint16(buffer, index);
423
    if (strncmp((const char *)&buffer[*index], "font", 4) == 0) {
424
      *index += (int32_t)sizeof("font"); // includes 0 for constant string
425
      return true;
426
    }
427
  }
428
  return false;
429
}
430
431
static bool font_get_line_metrics(uint8_t *buffer, int32_t buffer_size, float *ascender, float *descender, float *line_gap ,int32_t index) {
432
433
  while(index < buffer_size) {
434
    char *str = (char*)&buffer[index];
435
    if (strncmp(str, "lmtx", 4) == 0) {
436
      int32_t i = index + 5 + 4; // skip over string and size field;
437
      *ascender = buffer_get_float32_auto(buffer, &i);
438
      *descender = buffer_get_float32_auto(buffer, &i);
439
      *line_gap = buffer_get_float32_auto(buffer, &i);
440
      return true;
441
    }
442
    index += (int32_t)(strlen(str) + 1);
443
    index += (int32_t)buffer_get_uint32(buffer,&index); // just to next position
444
  }
445
  return false;
446
}
447
448
static bool font_get_kerning_table_index(uint8_t *buffer, int32_t buffer_size, int32_t *res_index, int32_t index) {
449
450
  while (index < buffer_size) {
451
    char *str = (char*)&buffer[index];
452
    if (strncmp(str, "kern", 4) == 0) {
453
      *res_index = index + 5 + 4;
454
      return true;
455
    }
456
    index += (int32_t)(strlen(str) + 1);
457
    index += (int32_t)buffer_get_uint32(buffer,&index); // jump to next position
458
  }
459
  return false;
460
}
461
462
static bool font_get_glyphs_table_index(uint8_t *buffer, int32_t buffer_size, int32_t *res_index, uint32_t *num_codes, uint32_t *fmt, int32_t index) {
463
  while (index < buffer_size) {
464
    char *str = (char*)&buffer[index];
465
    if (strncmp(str, "glyphs", 6) == 0) {
466
      int32_t i = index + 7 + 4;
467
      *num_codes = buffer_get_uint32(buffer,&i);
468
      *fmt = buffer_get_uint32(buffer,&i);
469
      *res_index = i;
470
      return true;
471
    }
472
    index += (int32_t)(strlen(str) + 1);
473
    index += (int32_t)buffer_get_uint32(buffer,&index);
474
  }
475
  return false;
476
}
477
478
static bool font_get_glyph(uint8_t *buffer,
479
                           float *advance_width,
480
                           float *left_side_bearing,
481
                           int32_t *y_offset,
482
                           int32_t *width,
483
                           int32_t *height,
484
                           uint8_t **gfx,
485
                           uint32_t utf32,
486
                           uint32_t num_codes,
487
                           color_format_t fmt,
488
                           int32_t index) {
489
490
  uint32_t i = 0;
491
  while (i < num_codes) {
492
    uint32_t c = buffer_get_uint32(buffer, &index);
493
    if (c == utf32) {
494
      *advance_width = buffer_get_float32_auto(buffer, &index);
495
      *left_side_bearing = buffer_get_float32_auto(buffer, &index);
496
      *y_offset = buffer_get_int32(buffer, &index);
497
      *width = buffer_get_int32(buffer, &index);
498
      *height = buffer_get_int32(buffer,&index);
499
      *gfx = &buffer[index];
500
      return true;
501
    } else {
502
      index += 12;
503
      int32_t w = buffer_get_int32(buffer, &index);
504
      int32_t h = buffer_get_int32(buffer, &index);
505
      index += (int32_t)image_dims_to_size_bytes(fmt, (uint16_t)w, (uint16_t)h);
506
    }
507
    i++;
508
  }
509
  return false;
510
}
511
512
bool font_get_kerning(uint8_t *buffer, uint32_t left, uint32_t right, float *x_shift, float *y_shift, int32_t index) {
513
514
  uint32_t num_rows = buffer_get_uint32(buffer, &index);
515
516
  for (uint32_t row = 0; row < num_rows; row ++) {
517
518
    uint32_t row_code = buffer_get_uint32(buffer, &index);
519
    uint32_t row_len  = buffer_get_uint32(buffer, &index);
520
521
    if (row_code == left) {
522
      for (uint32_t col = 0; col < row_len; col ++) {
523
        uint32_t col_code = buffer_get_uint32(buffer, &index);
524
        if (col_code == right) {
525
          *x_shift = buffer_get_float32_auto(buffer, &index);
526
          *y_shift = buffer_get_float32_auto(buffer, &index);
527
          return true;
528
        } else {
529
          index += 8;
530
        }
531
      }
532
    } else {
533
      index += (int32_t)(row_len * FONT_KERN_PAIR_SIZE);
534
    }
535
  }
536
  return false;
537
}
538
539
lbm_value ttf_text_bin(lbm_value *args, lbm_uint argn) {
540
  lbm_value res = ENC_SYM_TERROR;
541
  lbm_array_header_t *img_arr;
542
  lbm_value font;
543
  char *utf8_str;
544
  uint32_t colors[16];
545
  uint32_t next_arg = 0;
546
  if (argn >= 6 &&
547
      (img_arr = get_image_buffer(args[0])) &&
548
      lbm_is_number(args[1]) &&  // x position
549
      lbm_is_number(args[2]) &&  // y position
550
      lbm_is_cons(args[3]) &&    // list of colors
551
      lbm_is_array_r(args[4]) && // Binary font
552
      lbm_is_array_r(args[5])) { // sequence of utf8 characters
553
    lbm_value curr = args[3];
554
    int i = 0;
555
    while(lbm_is_cons(curr) && i < 16) {
556
      colors[i] = lbm_dec_as_u32(lbm_car(curr));
557
      curr = lbm_cdr(curr);
558
      i ++;
559
    }
560
    font = args[4];
561
    utf8_str = lbm_dec_str(args[5]);
562
    next_arg = 6;
563
  } else {
564
    return res;
565
  }
566
567
  int x_pos = lbm_dec_as_i32(args[1]);
568
  int y_pos = lbm_dec_as_i32(args[2]);
569
570
  float line_spacing = 1.0f;
571
  bool up = false;
572
  bool down = false;
573
  for (uint32_t i = next_arg; i < argn; i ++) {
574
    if (lbm_is_symbol(args[i])) {
575
      up = display_is_symbol_up(args[i]);
576
      down = display_is_symbol_down(args[i]);
577
    } else if (lbm_is_number(args[i])) {
578
      line_spacing = lbm_dec_as_float(args[i]);
579
    }
580
  }
581
582
  lbm_array_header_t *font_arr = lbm_dec_array_r(font);
583
  if (font_arr->size < 10) return ENC_SYM_EERROR;
584
585
  int32_t index = 0;
586
  uint16_t version;
587
588
  if (!buffer_get_font_preamble((uint8_t*)font_arr->data, &version, &index)) {
589
    return ENC_SYM_EERROR;
590
  }
591
592
  float ascender;
593
  float descender;
594
  float line_gap;
595
596
  if(!font_get_line_metrics((uint8_t*)font_arr->data, (int32_t)font_arr->size, &ascender, &descender, &line_gap , index)) {
597
    return ENC_SYM_EERROR;
598
  }
599
600
  int32_t kern_index = 0;
601
602
  if (!font_get_kerning_table_index((uint8_t*)font_arr->data, (int32_t)font_arr->size, &kern_index, index)) {
603
    return ENC_SYM_EERROR;
604
  }
605
606
  int32_t glyphs_index = 0;
607
  uint32_t num_codes;
608
  uint32_t color_fmt;
609
610
  if (!font_get_glyphs_table_index((uint8_t*)font_arr->data, (int32_t)font_arr->size, &glyphs_index, &num_codes, &color_fmt, index)) {
611
    return ENC_SYM_EERROR;
612
  }
613
614
  color_format_t fmt = (color_format_t)color_fmt;
615
  float x = 0.0;
616
  float y = 0.0;
617
618
  image_buffer_t tgt;
619
  tgt.width = image_buffer_width((uint8_t*)img_arr->data);
620
  tgt.height = image_buffer_height((uint8_t*)img_arr->data);
621
  tgt.fmt = image_buffer_format((uint8_t*)img_arr->data);
622
  tgt.mem_base = (uint8_t*)img_arr->data;
623
  tgt.data = image_buffer_data((uint8_t*)img_arr->data);
624
625
  uint32_t utf32;
626
  uint32_t prev;
627
  bool has_prev = false;
628
  uint32_t i = 0;
629
  uint32_t next_i = 0;
630
  while (get_utf32((uint8_t*)utf8_str, &utf32, i, &next_i)) {
631
    if (utf32 == '\n') {
632
      x = 0.0;
633
      y += line_spacing * (ascender - descender + line_gap);
634
      i++;
635
      continue; // next iteration
636
    }
637
638
    float x_n = x;
639
    float y_n = y;
640
641
    float advance_width;
642
    float left_side_bearing;
643
    int32_t y_offset;
644
    int32_t width;
645
    int32_t height;
646
    uint8_t *gfx;
647
648
    if (font_get_glyph((uint8_t*)font_arr->data,
649
                       &advance_width,
650
                       &left_side_bearing,
651
                       &y_offset,
652
                       &width,
653
                       &height,
654
                       &gfx,
655
                       utf32,
656
                       num_codes,
657
                       fmt,
658
                       glyphs_index)) {
659
660
      float x_shift = 0;
661
      float y_shift = 0;
662
      if (has_prev) {
663
        font_get_kerning((uint8_t*)font_arr->data,
664
                         prev,
665
                         utf32,
666
                         &x_shift,
667
                         &y_shift,
668
                         kern_index);
669
      }
670
      x_n += x_shift;
671
      y_n += y_shift;
672
      y_n += y_offset;
673
674
      image_buffer_t src;
675
      src.width = (uint16_t)width;
676
      src.height = (uint16_t)height;
677
      src.fmt = fmt;
678
      //src.mem_base = gfx;
679
      src.data = gfx;
680
681
      uint32_t num_colors = 1 << src.fmt;
682
      for (int j = 0; j < src.height; j++) {
683
        for (int i = 0; i < src.width; i ++) {
684
          // the bearing should not be accumulated into the advances
685
686
          uint32_t p = getpixel(&src, i, j);
687
          if (p) { // only draw colored
688
            uint32_t c = colors[p & (num_colors-1)]; // ceiled
689
            if (up) {
690
              putpixel(&tgt, x_pos + (j + (int)y_n), y_pos - (i + (int)(x_n + left_side_bearing)), c);
691
            } else if (down) {
692
              putpixel(&tgt, x_pos - (j + (int)y_n), y_pos + (i + (int)(x_n + left_side_bearing)), c);
693
            } else {
694
              putpixel(&tgt, x_pos + (i + (int)(x_n + left_side_bearing)), y_pos + (j + (int)y_n), c);
695
            }
696
          }
697
        }
698
      }
699
    } else {
700
      lbm_set_error_reason("Character is not one of those listed in ttf-prepare\n");
701
      return ENC_SYM_EERROR;
702
    }
703
    x = x_n + advance_width;
704
    i = next_i;
705
    prev = utf32;
706
    has_prev = true;
707
  }
708
  return ENC_SYM_TRUE;
709
}
710
711
lbm_value ext_ttf_wh(lbm_value *args, lbm_uint argn) {
712
  lbm_value res = ENC_SYM_TERROR;
713
  lbm_value font;
714
  char *utf8_str;
715
  uint32_t next_arg = 0;
716
  if (argn >= 2 &&
717
      lbm_is_array_r(args[0]) && // Binary font
718
      lbm_is_array_r(args[1])) { // sequence of utf8 characters
719
    font = args[0];
720
    utf8_str = lbm_dec_str(args[1]);
721
    next_arg = 2;
722
  } else {
723
    return res;
724
  }
725
726
  float line_spacing = 1.0f;
727
  bool up = false;
728
  bool down = false;
729
  for (uint32_t i = next_arg; i < argn; i ++) {
730
    if (lbm_is_symbol(args[i])) {
731
      up = display_is_symbol_up(args[i]);
732
      down = display_is_symbol_down(args[i]);
733
    } else if (lbm_is_number(args[i])) {
734
      line_spacing = lbm_dec_as_float(args[i]);
735
    }
736
  }
737
738
  lbm_value r_list = lbm_heap_allocate_list(2);
739
  if (lbm_is_symbol(r_list)) return r_list;
740
741
  lbm_array_header_t *font_arr = lbm_dec_array_r(font);
742
  if (font_arr->size < 10) return ENC_SYM_EERROR;
743
744
  int32_t index = 0;
745
  uint16_t version;
746
747
  if (!buffer_get_font_preamble((uint8_t*)font_arr->data, &version, &index)) {
748
    return ENC_SYM_EERROR;
749
  }
750
751
  float ascender;
752
  float descender;
753
  float line_gap;
754
755
  if(!font_get_line_metrics((uint8_t*)font_arr->data, (int32_t)font_arr->size, &ascender, &descender, &line_gap , index)) {
756
    return ENC_SYM_EERROR;
757
  }
758
759
  int32_t kern_index = 0;
760
761
  if (!font_get_kerning_table_index((uint8_t*)font_arr->data, (int32_t)font_arr->size, &kern_index, index)) {
762
    return ENC_SYM_EERROR;
763
  }
764
765
  int32_t glyphs_index = 0;
766
  uint32_t num_codes;
767
  uint32_t color_fmt;
768
769
  if (!font_get_glyphs_table_index((uint8_t*)font_arr->data, (int32_t)font_arr->size, &glyphs_index, &num_codes, &color_fmt, index)) {
770
    return ENC_SYM_EERROR;
771
  }
772
773
  float x = 0.0;
774
  float y = 0.0;
775
  float max_x = 0.0;
776
777
  uint32_t utf32;
778
  uint32_t prev;
779
  bool has_prev = false;
780
  uint32_t i = 0;
781
  uint32_t next_i = 0;
782
  while (get_utf32((uint8_t*)utf8_str, &utf32, i, &next_i)) {
783
    if (utf32 == '\n') {
784
      if (x > max_x) max_x = x;
785
      x = 0.0;
786
      y += line_spacing * (ascender - descender + line_gap);
787
      i++;
788
      continue; // next iteration
789
    }
790
791
    float x_n = x;
792
793
    float advance_width;
794
    float left_side_bearing;
795
    int32_t y_offset;
796
    int32_t width;
797
    int32_t height;
798
    uint8_t *gfx;
799
800
    if (font_get_glyph((uint8_t*)font_arr->data,
801
                       &advance_width,
802
                       &left_side_bearing,
803
                       &y_offset,
804
                       &width,
805
                       &height,
806
                       &gfx,
807
                       utf32,
808
                       num_codes,
809
                       (color_format_t)color_fmt,
810
                       glyphs_index)) {
811
812
      float x_shift = 0;
813
      float y_shift = 0;
814
      if (has_prev) {
815
        font_get_kerning((uint8_t*)font_arr->data,
816
                         prev,
817
                         utf32,
818
                         &x_shift,
819
                         &y_shift,
820
                         kern_index);
821
      }
822
      x_n += x_shift;
823
    } else {
824
      return ENC_SYM_EERROR;
825
    }
826
    x = x_n + advance_width;
827
    i = next_i;
828
    prev = utf32;
829
    has_prev = true;
830
  }
831
  if (max_x < x) max_x = x;
832
  lbm_value rest = lbm_cdr(r_list);
833
  if (up || down) {
834
    lbm_set_car(r_list, lbm_enc_u((uint32_t)(y + line_spacing * (ascender - descender + line_gap))));
835
    lbm_set_car(rest, lbm_enc_u((uint32_t)max_x));
836
  } else {
837
    lbm_set_car(r_list, lbm_enc_u((uint32_t)max_x));
838
    lbm_set_car(rest, lbm_enc_u((uint32_t)(y + line_spacing * (ascender - descender + line_gap))));
839
  }
840
  return r_list;
841
}
842
843
lbm_value ext_ttf_glyph_dims(lbm_value *args, lbm_uint argn) {
844
  if (argn == 2 &&
845
      lbm_is_array_r(args[0]) &&
846
      lbm_is_array_r(args[1])) { // string utf8,
847
848
    lbm_array_header_t *font_arr = lbm_dec_array_r(args[0]);
849
    if (!font_arr) return ENC_SYM_FATAL_ERROR;
850
    if (font_arr->size < 10) return ENC_SYM_EERROR;
851
852
    int32_t index = 0;
853
    uint16_t version;
854
855
    if (!buffer_get_font_preamble((uint8_t*)font_arr->data, &version, &index)) {
856
      return ENC_SYM_EERROR;
857
    }
858
859
    int32_t glyphs_index = 0;
860
    uint32_t num_codes;
861
    uint32_t color_fmt;
862
863
    if (!font_get_glyphs_table_index((uint8_t*)font_arr->data, (int32_t)font_arr->size, &glyphs_index, &num_codes, &color_fmt, index)) {
864
      return ENC_SYM_EERROR;
865
    }
866
867
    lbm_array_header_t *utf8_array_header = (lbm_array_header_t*)(lbm_car(args[1]));
868
    if (!utf8_array_header) return ENC_SYM_FATAL_ERROR;
869
870
    uint32_t next_i = 0;
871
    uint32_t utf32 = 0;
872
    get_utf32((uint8_t*)utf8_array_header->data, &utf32, 0, &next_i);
873
874
    float advance_width;
875
    float left_side_bearing;
876
    int32_t y_offset;
877
    int32_t width;
878
    int32_t height;
879
    uint8_t *gfx;
880
881
    if (font_get_glyph((uint8_t*)font_arr->data,
882
                       &advance_width,
883
                       &left_side_bearing,
884
                       &y_offset,
885
                       &width,
886
                       &height,
887
                       &gfx,
888
                       utf32,
889
                       num_codes,
890
                       (color_format_t)color_fmt,
891
                       glyphs_index)) {
892
893
      return lbm_heap_allocate_list_init(2,
894
                                        lbm_enc_u((uint32_t)(width)),
895
                                        lbm_enc_u((uint32_t)height));
896
    }
897
  }
898
  return ENC_SYM_TERROR;
899
}
900
901
lbm_value ext_ttf_line_height(lbm_value *args, lbm_uint argn) {
902
  lbm_value res = ENC_SYM_TERROR;
903
  if (argn == 1 &&
904
      lbm_is_array_r(args[0])) {
905
906
    lbm_array_header_t *font_arr = lbm_dec_array_r(args[0]);
907
    if (!font_arr) return ENC_SYM_FATAL_ERROR;
908
    if (font_arr->size < 10) return ENC_SYM_EERROR;
909
910
    int32_t index = 0;
911
    uint16_t version;
912
913
    if (!buffer_get_font_preamble((uint8_t*)font_arr->data, &version, &index)) {
914
      return ENC_SYM_EERROR;
915
    }
916
917
    float ascender;
918
    float descender;
919
    float line_gap;
920
921
    if(!font_get_line_metrics((uint8_t*)font_arr->data, (int32_t)font_arr->size, &ascender, &descender, &line_gap , index)) {
922
      return ENC_SYM_EERROR;
923
    }
924
925
    res = lbm_enc_float(ascender - descender + line_gap);
926
  }
927
  return res;
928
}
929
930
lbm_value ext_ttf_ascender(lbm_value *args, lbm_uint argn) {
931
  lbm_value res = ENC_SYM_TERROR;
932
  if (argn == 1 &&
933
      lbm_is_array_r(args[0])) {
934
935
    lbm_array_header_t *font_arr = lbm_dec_array_r(args[0]);
936
    if (!font_arr) return ENC_SYM_FATAL_ERROR;
937
    if (font_arr->size < 10) return ENC_SYM_EERROR;
938
939
    int32_t index = 0;
940
    uint16_t version;
941
942
    if (!buffer_get_font_preamble((uint8_t*)font_arr->data, &version, &index)) {
943
      return ENC_SYM_EERROR;
944
    }
945
946
    float ascender;
947
    float descender;
948
    float line_gap;
949
950
    if(!font_get_line_metrics((uint8_t*)font_arr->data, (int32_t)font_arr->size, &ascender, &descender, &line_gap , index)) {
951
      return ENC_SYM_EERROR;
952
    }
953
954
    res = lbm_enc_float(ascender);
955
  }
956
  return res;
957
}
958
959
lbm_value ext_ttf_descender(lbm_value *args, lbm_uint argn) {
960
  lbm_value res = ENC_SYM_TERROR;
961
  if (argn == 1 &&
962
      lbm_is_array_r(args[0])) {
963
964
    lbm_array_header_t *font_arr = lbm_dec_array_r(args[0]);
965
    if (!font_arr) return ENC_SYM_FATAL_ERROR;
966
    if (font_arr->size < 10) return ENC_SYM_EERROR;
967
968
    int32_t index = 0;
969
    uint16_t version;
970
971
    if (!buffer_get_font_preamble((uint8_t*)font_arr->data, &version, &index)) {
972
      return ENC_SYM_EERROR;
973
    }
974
975
    float ascender;
976
    float descender;
977
    float line_gap;
978
979
    if(!font_get_line_metrics((uint8_t*)font_arr->data, (int32_t)font_arr->size, &ascender, &descender, &line_gap , index)) {
980
      return ENC_SYM_EERROR;
981
    }
982
983
    res = lbm_enc_float(descender);
984
  }
985
  return res;
986
}
987
988
lbm_value ext_ttf_line_gap(lbm_value *args, lbm_uint argn) {
989
  lbm_value res = ENC_SYM_TERROR;
990
  if (argn == 1 &&
991
      lbm_is_array_r(args[0])) {
992
993
    lbm_array_header_t *font_arr = lbm_dec_array_r(args[0]);
994
    if (!font_arr) return ENC_SYM_FATAL_ERROR;
995
    if (font_arr->size < 10) return ENC_SYM_EERROR;
996
997
    int32_t index = 0;
998
    uint16_t version;
999
1000
    if (!buffer_get_font_preamble((uint8_t*)font_arr->data, &version, &index)) {
1001
      return ENC_SYM_EERROR;
1002
    }
1003
1004
    float ascender;
1005
    float descender;
1006
    float line_gap;
1007
1008
    if(!font_get_line_metrics((uint8_t*)font_arr->data, (int32_t)font_arr->size, &ascender, &descender, &line_gap , index)) {
1009
      return ENC_SYM_EERROR;
1010
    }
1011
1012
    res = lbm_enc_float(line_gap);
1013
  }
1014
  return res;
1015
}
1016
1017
void lbm_ttf_extensions_init(void) {
1018
1019
  // metrics
1020
  lbm_add_extension("ttf-line-height", ext_ttf_line_height);
1021
  lbm_add_extension("ttf-ascender", ext_ttf_ascender);
1022
  lbm_add_extension("ttf-descender", ext_ttf_descender);
1023
  lbm_add_extension("ttf-line-gap", ext_ttf_line_gap);
1024
  lbm_add_extension("ttf-text-dims",ext_ttf_wh);
1025
  lbm_add_extension("ttf-glyph-dims",ext_ttf_glyph_dims);
1026
1027
  // Prepare
1028
  lbm_add_extension("ttf-prepare", ext_ttf_prepare_bin);
1029
1030
  // Draw text.
1031
  lbm_add_extension("ttf-text", ttf_text_bin);
1032
}