roo_display
API Documentation for roo_display
Loading...
Searching...
No Matches
smooth_font_v2.cpp
Go to the documentation of this file.
1// SmoothFontV2 binary format (version 0x0200)
2//
3// All multi-byte integers are big-endian. Offsets are from the start of the
4// font data buffer.
5//
6// Header (variable size; depends on font_metric_bytes and encoding_bytes):
7// u16 version = 0x0200
8// u8 alpha_bits (must be 4)
9// u8 encoding_bytes (1=ASCII, 2=Unicode BMP)
10// u8 font_metric_bytes (1 or 2)
11// u8 offset_bytes (1..3)
12// u8 compression_method (0=none, 1=RLE)
13// u16 glyph_count
14// u16 cmap_entries_count
15// s8/s16 metrics[11] (xMin, yMin, xMax, yMax, ascent, descent,
16// linegap, min_advance, max_advance,
17// max_right_overhang, default_space_width)
18// u8/u16 default_glyph_code
19// u8 kerning_flags (bits 1..0: 0=none, 1=pairs, 2=classes)
20// u16 glyph_metrics_offset (absolute offset to glyph metrics section)
21// u24 glyph_data_offset (absolute offset to glyph data)
22// u8 reserved (must be 0)
23// u8 reserved (must be 0)
24//
25// Cmap section (variable):
26// Repeated cmap_entries_count times (12 bytes each):
27// u16 range_start
28// u16 range_length
29// u16 glyph_id_offset
30// u16 data_entries_count
31// u24 data_offset (to sparse indirection table)
32// u8 format (0=dense, 1=sparse)
33// Sparse indirection tables: for each sparse entry, data_entries_count
34// u16 values listing glyph offsets in the range.
35//
36// Glyph metrics section (offset = glyph_metrics_offset):
37// glyph_count entries, each:
38// s8/s16 xMin (top two bits encode compression flag; see encoder)
39// s8/s16 yMin
40// s8/s16 xMax
41// s8/s16 yMax
42// s8/s16 advance
43// u8/u16/u24 data_offset (offset into glyph data)
44//
45// Kerning section (starts immediately after glyph metrics):
46// Kerning header (type-specific):
47// format 1 (pairs): u16 kerning_pairs_count
48// format 2 (classes):
49// u16 kerning_class_count
50// u16 kerning_source_count
51// u16 kerning_class_entries_count
52// format 0: no data
53// format 1 (pairs): kerning_pairs_count entries (glyph indices):
54// u8/u16 left_glyph_index
55// u8/u16 right_glyph_index
56// u8 weight
57// format 2 (classes, glyph indices):
58// source table (kerning_source_count entries):
59// u8/u16 glyph_index, u8 class_id
60// class table (kerning_class_count entries):
61// u16 entry_offset, u16 entry_count
62// class entries (kerning_class_entries_count entries):
63// u8/u16 dest_glyph_index, u8 weight
64//
65// Glyph data section (offset = glyph_data_offset):
66// Concatenated glyph bitmaps, each compressed or uncompressed per
67// glyph metadata flag.
68
70
75#include "roo_io/core/input_iterator.h"
76#include "roo_io/text/unicode.h"
77#include "roo_logging.h"
78
79namespace roo_display {
80
81namespace {
82constexpr uint8_t kKerningFormatNone = 0;
83constexpr uint8_t kKerningFormatPairs = 1;
84constexpr uint8_t kKerningFormatClasses = 2;
85} // namespace
86
87class FontMetricReader {
88 public:
90 roo_io::UnsafeGenericMemoryIterator<const roo::byte PROGMEM*>& reader,
92 : reader_(reader), font_metric_bytes_(font_metric_bytes) {}
93
94 int read() {
95 return font_metric_bytes_ == 1 ? roo_io::ReadS8(reader_)
96 : roo_io::ReadBeS16(reader_);
97 }
98
99 private:
100 roo_io::UnsafeGenericMemoryIterator<const roo::byte PROGMEM*>& reader_;
101 int font_metric_bytes_;
102};
103
104static int8_t readByte(const roo::byte* PROGMEM ptr) {
105 return pgm_read_byte(ptr);
106}
107
108static int16_t readWord(const roo::byte* PROGMEM ptr) {
109 return (pgm_read_byte(ptr) << 8) | pgm_read_byte(ptr + 1);
110}
111
112static uint16_t readUWord(const roo::byte* PROGMEM ptr) {
113 return ((uint16_t)pgm_read_byte(ptr) << 8) | pgm_read_byte(ptr + 1);
114}
115
116static uint32_t readTriWord(const roo::byte* PROGMEM ptr) {
117 return (uint32_t)pgm_read_byte(ptr) << 16 |
118 (uint32_t)pgm_read_byte(ptr + 1) << 8 |
120}
121
123 public:
125 : font_(font),
126 ptr_(font.glyph_metadata_begin_ +
127 glyph_index * font.glyph_metadata_size_) {}
128
130 int16_t glyphXMin, glyphYMin, glyphXMax, glyphYMax, x_advance;
131 if (font_.font_metric_bytes_ == 1) {
132 uint8_t b = readByte(ptr_ + 0);
133 compressed = ((b >> 7) == ((b >> 6) & 1));
134 glyphXMin = (int8_t)(b ^ ((!compressed) << 6));
135 glyphYMin = readByte(ptr_ + 1);
136 glyphXMax = readByte(ptr_ + 2);
137 glyphYMax = readByte(ptr_ + 3);
138 x_advance = readByte(ptr_ + 4);
139 } else {
140 uint16_t b = readWord(ptr_ + 0);
141 compressed = ((b >> 15) == ((b >> 14) & 1));
142 glyphXMin = (int16_t)(b ^ ((!compressed) << 14));
143 glyphXMin = readWord(ptr_ + 0);
144 glyphYMin = readWord(ptr_ + 2);
145 glyphXMax = readWord(ptr_ + 4);
146 glyphYMax = readWord(ptr_ + 6);
147 x_advance = readWord(ptr_ + 8);
148 }
149 return GlyphMetrics(
150 glyphXMin, glyphYMin, glyphXMax, glyphYMax,
152 ? x_advance
153 : glyphYMax - glyphYMin + 1 + font_.metrics().linegap());
154 }
155
156 long data_offset() const {
157 int offset_bytes = font_.offset_bytes_;
158 const roo::byte* PROGMEM ptr = ptr_ + 5 * font_.font_metric_bytes_;
159 return offset_bytes == 2
160 ? (pgm_read_byte(ptr) << 8) | pgm_read_byte(ptr + 1)
161 : offset_bytes == 3
162 ? (pgm_read_byte(ptr) << 16) | (pgm_read_byte(ptr + 1) << 8) |
163 pgm_read_byte(ptr + 2)
165 }
166
167 private:
168 const SmoothFontV2& font_;
169 const roo::byte* PROGMEM ptr_;
170};
171
173 : glyph_count_(0), default_glyph_(0), default_space_width_(0) {
174 roo_io::UnsafeGenericMemoryIterator<const roo::byte PROGMEM*> reader(
175 font_data);
176 uint16_t version = roo_io::ReadBeU16(reader);
177 CHECK(version == 0x0200) << "Unsupported version number: " << version;
178
179 alpha_bits_ = roo_io::ReadU8(reader);
180 CHECK_EQ(alpha_bits_, 4) << "Unsupported alpha encoding: " << alpha_bits_;
181 encoding_bytes_ = roo_io::ReadU8(reader);
182 CHECK_LE(encoding_bytes_, 2)
183 << "Unsupported character encoding: " << encoding_bytes_;
184 font_metric_bytes_ = roo_io::ReadU8(reader);
185 CHECK_LE(font_metric_bytes_, 2)
186 << "Unsupported count of metric bytes: " << encoding_bytes_;
187
188 offset_bytes_ = roo_io::ReadU8(reader);
189 CHECK_LE(offset_bytes_, 3)
190 << "Unsupported count of offset bytes: " << offset_bytes_;
191
192 compression_method_ = roo_io::ReadU8(reader);
193 CHECK_LE(compression_method_, 1)
194 << "Unsupported compression method: " << compression_method_;
195
196 glyph_count_ = roo_io::ReadBeU16(reader);
197 kerning_pairs_count_ = 0;
198 cmap_entries_count_ = roo_io::ReadBeU16(reader);
199
200 FontMetricReader fm_reader(reader, font_metric_bytes_);
201 int xMin = fm_reader.read();
202 int yMin = fm_reader.read();
203 int xMax = fm_reader.read();
204 int yMax = fm_reader.read();
205 int ascent = fm_reader.read();
206 int descent = fm_reader.read();
207 int linegap = fm_reader.read();
208 int min_advance = fm_reader.read();
209 int max_advance = fm_reader.read();
210 int max_right_overhang = fm_reader.read();
211 default_space_width_ = fm_reader.read();
212
213 if (encoding_bytes_ == 1) {
214 default_glyph_ = roo_io::ReadU8(reader);
215 } else {
216 default_glyph_ = roo_io::ReadBeU16(reader);
217 }
218
219 uint8_t kerning_flags = roo_io::ReadU8(reader);
220 kerning_format_ = kerning_flags & 0x03;
221 uint16_t glyph_metrics_offset = roo_io::ReadBeU16(reader);
222 uint32_t glyph_data_offset = ((uint32_t)roo_io::ReadU8(reader) << 16) |
223 ((uint32_t)roo_io::ReadU8(reader) << 8) |
224 (uint32_t)roo_io::ReadU8(reader);
225 roo_io::ReadU8(reader); // Reserved.
226 roo_io::ReadU8(reader); // Reserved.
227 kerning_class_count_ = 0;
228 kerning_source_count_ = 0;
229 kerning_class_entries_count_ = 0;
230
233 const int kCmapEntrySize = 12;
234 if (cmap_entries_count_ > 0) {
235 cmap_entries_ =
236 std::unique_ptr<CmapEntry[]>(new CmapEntry[cmap_entries_count_]);
237 }
238 for (int i = 0; i < cmap_entries_count_; ++i) {
239 const roo::byte* PROGMEM entry = cmap_entries_begin_ + i * kCmapEntrySize;
240 CmapEntry& dst = cmap_entries_[i];
241 dst.range_start = readUWord(entry + 0);
243 dst.range_end = (uint32_t)dst.range_start + range_length;
244 dst.glyph_id_offset = readUWord(entry + 4);
245 dst.data_entries_count = readUWord(entry + 6);
246 dst.data_offset = readTriWord(entry + 8);
247 dst.format = readByte(entry + 11);
248 CHECK_LT(dst.data_offset, (1u << 24)) << "Cmap data offset out of range";
249 }
250
252 glyph_metadata_size_ = (5 * font_metric_bytes_) + offset_bytes_;
254 glyph_metadata_begin_ + glyph_metadata_size_ * glyph_count_;
256 glyph_index_bytes_ = (glyph_count_ < (1 << 8)) ? 1 : 2;
257
261 kerning_source_index_bytes_ = 0;
262 kerning_class_entries_count_ = 0;
263 glyph_kerning_size_ = 0;
264
265 if (kerning_format_ == kKerningFormatPairs) {
266 kerning_pairs_count_ = readUWord(glyph_kerning_begin_ + 0);
268 glyph_kerning_size_ = 2 * glyph_index_bytes_ + 1;
269 // glyph_data_begin_ already set from header.
270 } else if (kerning_format_ == kKerningFormatClasses) {
271 kerning_class_count_ = readUWord(glyph_kerning_begin_ + 0);
272 kerning_source_count_ = readUWord(glyph_kerning_begin_ + 2);
273 kerning_class_entries_count_ = readUWord(glyph_kerning_begin_ + 4);
275 kerning_source_index_bytes_ = glyph_index_bytes_;
277 int source_entry_size = kerning_source_index_bytes_ + 1;
278 int source_table_size = kerning_source_count_ * source_entry_size;
282 kerning_class_table_begin_ + kerning_class_count_ * 4;
283 // kerning_class_entries_count_ read from header.
284 } else {
286 }
287
289 FontMetrics(ascent, descent, linegap, xMin, yMin, xMax, yMax,
292 encoding_bytes_ > 1 ? FontProperties::Charset::kUnicodeBmp
294 min_advance == max_advance && kerning_format_ == kKerningFormatNone
299 kerning_format_ != kKerningFormatNone
302
303 // Serial.println(String() + "Loaded font with " + glyph_count_ +
304 // " glyphs, size " + (ascent - descent));
305}
306
307bool is_space(char32_t code) {
308 // http://en.cppreference.com/w/cpp/string/wide/iswspace; see POSIX
309 return code == 0x0020 || code == 0x00A0 ||
310 (code >= 0x0009 && code <= 0x000D) || code == 0x1680 ||
311 code == 0x180E || (code >= 0x2000 && code <= 0x200B) ||
312 code == 0x2028 || code == 0x2029 || code == 0x205F || code == 0x3000 ||
313 code == 0xFEFF;
314}
315
316void SmoothFontV2::drawGlyphModeVisible(
317 DisplayOutput& output, int16_t x, int16_t y, const GlyphMetrics& metrics,
318 bool compressed, const roo::byte* PROGMEM data, const Box& clip_box,
319 Color color, Color bgcolor, BlendingMode blending_mode) const {
320 Surface s(output, x + metrics.bearingX(), y - metrics.bearingY(), clip_box,
322 if (rle() && compressed) {
323 RleImage4bppxBiased<Alpha4> glyph(metrics.width(), metrics.height(), data,
324 color);
325 streamToSurface(s, std::move(glyph));
326 } else {
327 // Identical as above, but using Raster<> instead of MonoAlpha4RleImage.
328 ProgMemRaster<Alpha4> glyph(metrics.width(), metrics.height(), data, color);
329 streamToSurface(s, std::move(glyph));
330 }
331}
332
333void SmoothFontV2::drawBordered(DisplayOutput& output, int16_t x, int16_t y,
334 int16_t bgwidth, const Drawable& glyph,
335 const Box& clip_box, Color bgColor,
337 Box outer(x, y - metrics().glyphYMax(), x + bgwidth - 1,
338 y - metrics().glyphYMin());
339
340 // NOTE: bgColor is part of the source, not destination.
341 if (bgColor.a() == 0xFF &&
344 // All souce pixels will be fully opaque.
346 }
347
348 if (outer.clip(clip_box) == Box::ClipResult::kEmpty) return;
349 Box inner = glyph.extents().translate(x, y);
350 if (inner.clip(clip_box) == Box::ClipResult::kEmpty) {
351 output.fillRect(blending_mode, outer, bgColor);
352 return;
353 }
354 if (outer.yMin() < inner.yMin()) {
355 output.fillRect(
357 Box(outer.xMin(), outer.yMin(), outer.xMax(), inner.yMin() - 1),
358 bgColor);
359 }
360 if (outer.xMin() < inner.xMin()) {
361 output.fillRect(
363 Box(outer.xMin(), inner.yMin(), inner.xMin() - 1, inner.yMax()),
364 bgColor);
365 }
366 Surface s(output, x, y, clip_box, false, bgColor, FillMode::kExtents,
368 s.drawObject(glyph);
369 if (outer.xMax() > inner.xMax()) {
370 output.fillRect(
372 Box(inner.xMax() + 1, inner.yMin(), outer.xMax(), inner.yMax()),
373 bgColor);
374 }
375 if (outer.yMax() > inner.yMax()) {
376 output.fillRect(
378 Box(outer.xMin(), inner.yMax() + 1, outer.xMax(), outer.yMax()),
379 bgColor);
380 }
381}
382
383void SmoothFontV2::drawGlyphModeFill(
384 DisplayOutput& output, int16_t x, int16_t y, int16_t bgwidth,
385 const GlyphMetrics& glyph_metrics, bool compressed,
386 const roo::byte* PROGMEM data, int16_t offset, const Box& clip_box,
387 Color color, Color bgColor, BlendingMode blending_mode) const {
388 Box box = glyph_metrics.screen_extents().translate(offset, 0);
389 if (rle() && compressed) {
390 auto glyph = MakeDrawableRawStreamable(
391 RleImage4bppxBiased<Alpha4>(box, data, color));
392 drawBordered(output, x, y, bgwidth, glyph, clip_box, bgColor,
394 } else {
395 // Identical as above, but using Raster<>
396 auto glyph =
397 MakeDrawableRawStreamable(ProgMemRaster<Alpha4>(box, data, color));
398 drawBordered(output, x, y, bgwidth, glyph, clip_box, bgColor,
400 }
401}
402
403void SmoothFontV2::drawKernedGlyphsModeFill(
404 DisplayOutput& output, int16_t x, int16_t y, int16_t bgwidth,
405 const GlyphMetrics& left_metrics, bool left_compressed,
406 const roo::byte* PROGMEM left_data, int16_t left_offset,
407 const GlyphMetrics& right_metrics, bool right_compressed,
408 const roo::byte* PROGMEM right_data, int16_t right_offset,
409 const Box& clip_box, Color color, Color bgColor,
411 Box lb = left_metrics.screen_extents().translate(left_offset, 0);
412 Box rb = right_metrics.screen_extents().translate(right_offset, 0);
413 if (rle() && left_compressed && right_compressed) {
414 auto glyph = MakeDrawableRawStreamable(
415 Overlay(RleImage4bppxBiased<Alpha4>(lb, left_data, color), 0, 0,
416 RleImage4bppxBiased<Alpha4>(rb, right_data, color), 0, 0));
417 drawBordered(output, x, y, bgwidth, glyph, clip_box, bgColor,
419 } else if (rle() && left_compressed) {
420 auto glyph = MakeDrawableRawStreamable(
421 Overlay(RleImage4bppxBiased<Alpha4>(lb, left_data, color), 0, 0,
422 ProgMemRaster<Alpha4>(rb, right_data, color), 0, 0));
423 drawBordered(output, x, y, bgwidth, glyph, clip_box, bgColor,
425 } else if (rle() && right_compressed) {
426 auto glyph = MakeDrawableRawStreamable(
427 Overlay(ProgMemRaster<Alpha4>(lb, left_data, color), 0, 0,
428 RleImage4bppxBiased<Alpha4>(rb, right_data, color), 0, 0));
429 drawBordered(output, x, y, bgwidth, glyph, clip_box, bgColor,
431 } else {
432 auto glyph = MakeDrawableRawStreamable(
433 Overlay(ProgMemRaster<Alpha4>(lb, left_data, color), 0, 0,
434 ProgMemRaster<Alpha4>(rb, right_data, color), 0, 0));
435 drawBordered(output, x, y, bgwidth, glyph, clip_box, bgColor,
437 }
438}
439
441 public:
442 GlyphPairIterator(const SmoothFontV2* font) : font_(font), swapped_(false) {}
443
444 const GlyphMetrics& left_metrics() const { return swapped_ ? m2_ : m1_; }
445
446 const GlyphMetrics& right_metrics() const { return swapped_ ? m1_ : m2_; }
447
448 const roo::byte* PROGMEM left_data() const {
449 return font_->glyph_data_begin_ +
450 (swapped_ ? data_offset_2_ : data_offset_1_);
451 }
452
453 const roo::byte* PROGMEM right_data() const {
454 return font_->glyph_data_begin_ +
455 (swapped_ ? data_offset_1_ : data_offset_2_);
456 }
457
458 bool left_compressed() const {
459 return swapped_ ? compressed_2_ : compressed_1_;
460 }
461 bool right_compressed() const {
462 return swapped_ ? compressed_1_ : compressed_2_;
463 }
464
465 int left_glyph_index() const {
466 return swapped_ ? glyph_index_2_ : glyph_index_1_;
467 }
468
469 int right_glyph_index() const {
470 return swapped_ ? glyph_index_1_ : glyph_index_2_;
471 }
472
473 void push(char32_t code) {
474 swapped_ = !swapped_;
475 if (is_space(code)) {
476 *mutable_right_metrics() =
477 GlyphMetrics(0, 0, -1, -1, font_->default_space_width_);
478 *mutable_right_data_offset() = 0;
479 *mutable_right_glyph_index() = -1;
480 return;
481 }
482 int glyph_index = font_->findGlyphIndex(code);
483 if (glyph_index == -1) {
484 glyph_index = font_->findGlyphIndex(font_->default_glyph_);
485 }
486 if (glyph_index == -1) {
487 *mutable_right_metrics() =
488 GlyphMetrics(0, 0, -1, -1, font_->default_space_width_);
489 *mutable_right_data_offset() = 0;
490 *mutable_right_glyph_index() = -1;
491 } else {
493 *mutable_right_metrics() = glyph_meta.readMetrics(
494 FontLayout::kHorizontal, mutable_right_compressed());
495 *mutable_right_data_offset() = glyph_meta.data_offset();
496 *mutable_right_glyph_index() = glyph_index;
497 }
498 }
499
500 void pushNull() {
501 swapped_ = !swapped_;
502 *mutable_right_glyph_index() = -1;
503 }
504
505 private:
506 GlyphMetrics* mutable_right_metrics() { return swapped_ ? &m1_ : &m2_; }
507
508 long* mutable_right_data_offset() {
509 return swapped_ ? &data_offset_1_ : &data_offset_2_;
510 }
511
512 int* mutable_right_glyph_index() {
513 return swapped_ ? &glyph_index_1_ : &glyph_index_2_;
514 }
515
516 bool& mutable_right_compressed() {
517 return swapped_ ? compressed_1_ : compressed_2_;
518 }
519
520 const SmoothFontV2* font_;
521 bool swapped_;
522 GlyphMetrics m1_;
523 GlyphMetrics m2_;
524 long data_offset_1_;
525 long data_offset_2_;
526 int glyph_index_1_ = -1;
527 int glyph_index_2_ = -1;
528 bool compressed_1_;
529 bool compressed_2_;
530};
531
533 uint32_t size) const {
534 roo_io::Utf8Decoder decoder(utf8_data, size);
535 char32_t next_code;
536 if (!decoder.next(next_code)) {
537 // Nothing to draw.
538 return GlyphMetrics(0, 0, -1, -1, 0);
539 }
541 glyphs.push(next_code);
542 bool has_more;
543 int16_t advance = 0;
544 int16_t yMin = 32767;
545 int16_t yMax = -32768;
546 int16_t xMin = glyphs.right_metrics().lsb();
547 int16_t xMax = xMin;
548 do {
549 has_more = decoder.next(next_code);
551 if (has_more) {
552 glyphs.push(next_code);
553 kern = kerning(glyphs.left_glyph_index(), glyphs.right_glyph_index());
554 } else {
555 next_code = 0;
556 glyphs.pushNull();
557 kern = 0;
558 }
559 advance += (glyphs.left_metrics().advance() - kern);
560 const GlyphMetrics& metrics = glyphs.left_metrics();
561 if (yMax < metrics.glyphYMax()) {
562 yMax = metrics.glyphYMax();
563 }
564 if (yMin > metrics.glyphYMin()) {
565 yMin = metrics.glyphYMin();
566 }
567 // NOTE: need to do this at every glyph, because of possibly trailing
568 // spaces.
569 int16_t xm = advance - glyphs.left_metrics().rsb() - 1;
570 if (xm > xMax) {
571 xMax = xm;
572 }
573 } while (has_more);
574 return GlyphMetrics(xMin, yMin, xMax, yMax, advance);
575}
576
578 const char* utf8_data, uint32_t size, GlyphMetrics* result, uint32_t offset,
579 uint32_t max_count) const {
580 roo_io::Utf8Decoder decoder(utf8_data, size);
581 char32_t next_code;
582 if (!decoder.next(next_code)) {
583 // Nothing to measure.
584 return 0;
585 }
589 glyphs.push(next_code);
590 bool has_more;
591 int16_t advance = 0;
592 do {
593 if (glyph_count >= max_count) return glyph_count;
594 has_more = decoder.next(next_code);
596 if (has_more) {
597 glyphs.push(next_code);
598 kern = kerning(glyphs.left_glyph_index(), glyphs.right_glyph_index());
599 } else {
600 next_code = 0;
601 glyphs.pushNull();
602 kern = 0;
603 }
604 if (glyph_idx >= offset) {
606 GlyphMetrics(glyphs.left_metrics().glyphXMin() + advance,
607 glyphs.left_metrics().glyphYMin(),
608 glyphs.left_metrics().glyphXMax() + advance,
609 glyphs.left_metrics().glyphYMax(),
610 glyphs.left_metrics().advance() + advance);
611 }
612 ++glyph_idx;
613 advance += (glyphs.left_metrics().advance() - kern);
614 } while (has_more);
615 return glyph_count;
616}
617
619 uint32_t size, Color color) const {
620 roo_io::Utf8Decoder decoder(utf8_data, size);
621 char32_t next_code;
622 if (!decoder.next(next_code)) {
623 // Nothing to draw.
624 return;
625 }
626 int16_t x = s.dx();
627 int16_t y = s.dy();
628 DisplayOutput& output = s.out();
629
631 glyphs.push(next_code);
633 if (glyphs.right_metrics().lsb() < 0) {
634 preadvanced = glyphs.right_metrics().lsb();
635 x += preadvanced;
636 }
637 bool has_more;
638 do {
639 has_more = decoder.next(next_code);
641 if (has_more) {
642 glyphs.push(next_code);
643 kern = kerning(glyphs.left_glyph_index(), glyphs.right_glyph_index());
644 } else {
645 next_code = 0;
646 glyphs.pushNull();
647 kern = 0;
648 }
649 if (s.fill_mode() == FillMode::kVisible) {
650 // No fill; simply draw and shift.
651 drawGlyphModeVisible(output, x - preadvanced, y, glyphs.left_metrics(),
652 glyphs.left_compressed(), glyphs.left_data(),
653 s.clip_box(), color, s.bgcolor(), s.blending_mode());
654 x += (glyphs.left_metrics().advance() - kern);
655 } else {
656 // General case. We may have two glyphs to worry about, and we may be
657 // pre-advanced. Let's determine our bounding box, taking the
658 // pre-advancement and kerning into account.
659 int16_t advance = glyphs.left_metrics().advance() - kern;
660 int16_t gap = 0;
661 if (has_more) {
662 gap = glyphs.left_metrics().rsb() + glyphs.right_metrics().lsb() - kern;
663 }
664 // Calculate the total width of a rectangle that we will need to fill with
665 // content (glyphs + background).
667 glyphs.left_metrics().glyphXMax() + 1 - preadvanced;
668 if (gap < 0) {
669 drawKernedGlyphsModeFill(
670 output, x, y, total_rect_width, glyphs.left_metrics(),
671 glyphs.left_compressed(), glyphs.left_data(), -preadvanced,
672 glyphs.right_metrics(), glyphs.right_compressed(),
673 glyphs.right_data(), advance - preadvanced,
674 Box::Intersect(s.clip_box(), Box(x, y - metrics().glyphYMax(),
675 x + total_rect_width - 1,
676 y - metrics().glyphYMin())),
677 color, s.bgcolor(), s.blending_mode());
678 } else {
679 // Glyphs do not overlap; can draw them one by one.
680 if (has_more) {
681 // Include the interim whitespace gap in the rectangle that we'll fill
682 // (the gap will be filled with background). This way, we draw longer
683 // horizontal strikes which translates to fewer SPI operations.
685 } else if (glyphs.left_metrics().rsb() > 0) {
686 // After the last character, fill up the right-side bearing.
687 total_rect_width += glyphs.left_metrics().rsb();
688 }
689 drawGlyphModeFill(
690 output, x, y, total_rect_width, glyphs.left_metrics(),
691 glyphs.left_compressed(), glyphs.left_data(), -preadvanced,
692 Box::Intersect(s.clip_box(), Box(x, y - metrics().glyphYMax(),
693 x + total_rect_width - 1,
694 y - metrics().glyphYMin())),
695 color, s.bgcolor(), s.blending_mode());
696 }
697 x += total_rect_width;
699 }
700 } while (has_more);
701}
702
703void SmoothFontV2::drawGlyph(const Surface& s, char32_t code,
704 FontLayout layout, Color color) const {
706 int16_t x = s.dx();
707 int16_t y = s.dy();
708 DisplayOutput& output = s.out();
709
710 int glyph_index = findGlyphIndex(code);
711 if (glyph_index == -1) {
712 if (is_space(code)) {
713 if (s.fill_mode() == FillMode::kExtents && default_space_width_ > 0) {
714 output.fillRect(
715 s.blending_mode(),
716 Box(x, y - metrics().glyphYMax(), x + default_space_width_ - 1,
717 y - metrics().glyphYMin()),
718 s.bgcolor());
719 }
720 return;
721 }
722 glyph_index = findGlyphIndex(default_glyph_);
723 }
724 if (glyph_index == -1) return;
725
727 bool compressed;
730
732 if (glyph_metrics.lsb() < 0) {
734 x += preadvanced;
735 }
736
737 if (s.fill_mode() == FillMode::kVisible) {
738 drawGlyphModeVisible(output, x - preadvanced, y, glyph_metrics, compressed,
739 glyph_data_begin_ + reader.data_offset(), s.clip_box(),
740 color, s.bgcolor(), s.blending_mode());
741 return;
742 }
743
745 if (glyph_metrics.rsb() > 0) {
746 // For a standalone glyph, include trailing side-bearing in the fill area.
748 }
749 drawGlyphModeFill(
751 glyph_data_begin_ + reader.data_offset(), -preadvanced,
752 Box::Intersect(s.clip_box(),
753 Box(x, y - metrics().glyphYMax(),
754 x + total_rect_width - 1, y - metrics().glyphYMin())),
755 color, s.bgcolor(), s.blending_mode());
756}
757
759 GlyphMetrics* result) const {
760 int glyph_index = findGlyphIndex(code);
761 if (glyph_index == -1) {
762 if (is_space(code)) {
763 *result = GlyphMetrics(0, 0, -1, -1,
765 ? default_space_width_
766 : 1 + metrics().linegap());
767 return true;
768 } else {
769 return false;
770 }
771 }
773 bool compressed;
774 *result = reader.readMetrics(layout, compressed);
775 return true;
776}
777
778int16_t SmoothFontV2::getKerning(char32_t left, char32_t right) const {
779 int left_glyph_index = findGlyphIndex(left);
780 if (left_glyph_index == -1) {
781 if (is_space(left)) return 0;
782 left_glyph_index = findGlyphIndex(default_glyph_);
783 }
784
785 int right_glyph_index = findGlyphIndex(right);
786 if (right_glyph_index == -1) {
787 if (is_space(right)) return 0;
788 right_glyph_index = findGlyphIndex(default_glyph_);
789 }
790
791 return kerning(left_glyph_index, right_glyph_index);
792}
793
794int SmoothFontV2::findGlyphIndex(char32_t code) const {
795 if (code > 0xFFFF) return -1;
797 for (int i = 0; i < cmap_entries_count_; ++i) {
798 const CmapEntry& entry = cmap_entries_[i];
799 if (ucode < entry.range_start) return -1;
800 if (ucode >= entry.range_end) continue;
801 uint16_t rel = (uint16_t)(ucode - entry.range_start);
802 if (entry.format == 0) {
803 return entry.glyph_id_offset + rel;
804 }
805 if (entry.format == 1) {
806 const roo::byte* PROGMEM data = font_begin_ + entry.data_offset;
807 int lo = 0;
808 int hi = (int)entry.data_entries_count - 1;
809 while (lo <= hi) {
810 int mid = (lo + hi) / 2;
811 uint16_t val = readUWord(data + mid * 2);
812 if (val == rel) return entry.glyph_id_offset + mid;
813 if (rel < val) {
814 hi = mid - 1;
815 } else {
816 lo = mid + 1;
817 }
818 }
819 return -1;
820 }
821 return -1;
822 }
823 return -1;
824}
825
826static uint16_t read_glyph_index(const roo::byte* PROGMEM address,
827 int index_bytes) {
829}
830
831const roo::byte* PROGMEM SmoothFontV2::findKernPair(char32_t left,
832 char32_t right) const {
833 // Legacy API no longer used; keep for compatibility if needed.
834 return nullptr;
835}
836
837int16_t SmoothFontV2::kerning(int left_glyph_index,
838 int right_glyph_index) const {
839 if (left_glyph_index < 0 || right_glyph_index < 0) return 0;
840 if (kerning_format_ == kKerningFormatPairs) {
841 // Pair format: binary search over sorted (left,right) glyph index pairs.
842 int lo = 0;
843 int hi = kerning_pairs_count_ - 1;
844 while (lo <= hi) {
845 int mid = (lo + hi) / 2;
846 const roo::byte* PROGMEM entry =
847 glyph_kerning_begin_ + mid * glyph_kerning_size_;
848 uint16_t left = read_glyph_index(entry, glyph_index_bytes_);
849 uint16_t right =
850 read_glyph_index(entry + glyph_index_bytes_, glyph_index_bytes_);
851 if (left == left_glyph_index && right == right_glyph_index) {
852 return pgm_read_byte(entry + 2 * glyph_index_bytes_);
853 }
854 if (left < left_glyph_index ||
855 (left == left_glyph_index && right < right_glyph_index)) {
856 lo = mid + 1;
857 } else {
858 hi = mid - 1;
859 }
860 }
861 return 0;
862 }
863 if (kerning_format_ == kKerningFormatClasses) {
864 return kerningWithClassFormat(left_glyph_index, right_glyph_index);
865 }
866 return 0;
867}
868
869int16_t SmoothFontV2::kerningWithClassFormat(int left_glyph_index,
870 int right_glyph_index) const {
871 if (kerning_source_count_ == 0 || kerning_class_count_ == 0) return 0;
872
873 // 1) Find the class assigned to the left glyph using binary search
874 // over the source table (glyph_index -> class_id).
875 int entry_size = kerning_source_index_bytes_ + 1;
876 int lo = 0;
877 int hi = kerning_source_count_ - 1;
878 uint8_t class_id = 0xFF;
879
880 while (lo <= hi) {
881 int mid = (lo + hi) / 2;
882 const roo::byte* PROGMEM entry =
883 kerning_source_table_begin_ + mid * entry_size;
884 uint16_t glyph_index = read_glyph_index(entry, kerning_source_index_bytes_);
885 if (glyph_index == left_glyph_index) {
886 class_id = pgm_read_byte(entry + kerning_source_index_bytes_);
887 break;
888 }
889 if (glyph_index < left_glyph_index) {
890 lo = mid + 1;
891 } else {
892 hi = mid - 1;
893 }
894 }
895
896 if (class_id == 0xFF || class_id >= kerning_class_count_) return 0;
897
898 // 2) Look up the class entry range (offset,count).
899 const roo::byte* PROGMEM cls = kerning_class_table_begin_ + class_id * 4;
900 uint16_t offset = readUWord(cls + 0);
901 uint16_t count = readUWord(cls + 2);
902 if (count == 0) return 0;
903
904 // 3) Binary search within the class entries for the right glyph index.
905 const roo::byte* PROGMEM entries =
906 kerning_class_entries_begin_ + offset * entry_size;
907 lo = 0;
908 hi = count - 1;
909 while (lo <= hi) {
910 int mid = (lo + hi) / 2;
911 const roo::byte* PROGMEM entry = entries + mid * entry_size;
912 uint16_t glyph_index = read_glyph_index(entry, kerning_source_index_bytes_);
913 if (glyph_index == right_glyph_index) {
914 return pgm_read_byte(entry + kerning_source_index_bytes_);
915 }
916 if (glyph_index < right_glyph_index) {
917 lo = mid + 1;
918 } else {
919 hi = mid - 1;
920 }
921 }
922 return 0;
923}
924
925} // namespace roo_display
Axis-aligned integer rectangle.
Definition box.h:12
static Box Intersect(const Box &a, const Box &b)
Return the intersection of two boxes (may be empty).
Definition box.h:25
ARGB8888 color stored as a 32-bit unsigned integer.
Definition color.h:16
The abstraction for drawing to a display.
Definition device.h:15
void fillRect(BlendingMode blending_mode, const Box &rect, Color color)
Fill a single rectangle. Invalidates the address window.
Definition device.h:135
FontMetricReader(roo_io::UnsafeGenericMemoryIterator< const roo::byte PROGMEM * > &reader, int font_metric_bytes)
Basic font metrics (ascent, descent, bounding box, and line spacing).
Definition font.h:24
int16_t linegap() const
Additional gap between lines.
Definition font.h:44
int16_t glyphYMin() const
Definition font.h:50
int16_t glyphYMax() const
Definition font.h:52
Metadata describing a font's encoding and spacing behavior.
Definition font.h:73
void init(FontMetrics metrics, FontProperties properties)
Definition font.h:258
const FontMetrics & metrics() const
Return font metrics.
Definition font.h:188
Per-glyph metrics (bounding box and advance).
Definition font.h:139
GlyphMetadataReader(const SmoothFontV2 &font, int glyph_index)
GlyphMetrics readMetrics(FontLayout layout, bool &compressed)
const roo::byte *PROGMEM left_data() const
const roo::byte *PROGMEM right_data() const
Smooth font v2 with split cmap and glyph metrics format.
uint32_t getHorizontalStringGlyphMetrics(const char *utf8_data, uint32_t size, GlyphMetrics *result, uint32_t offset, uint32_t max_count) const override
Return metrics for consecutive glyphs in the UTF-8 string.
bool getGlyphMetrics(char32_t code, FontLayout layout, GlyphMetrics *result) const override
Retrieve glyph metrics for a code point and layout.
GlyphMetrics getHorizontalStringMetrics(const char *utf8_data, uint32_t size) const override
Return metrics of the specified UTF-8 string as if it were a single glyph.
void drawHorizontalString(const Surface &s, const char *utf8_data, uint32_t size, Color color) const override
Draw a UTF-8 string horizontally.
SmoothFontV2(const U *font_data PROGMEM)
Legacy constructor for fonts using uint8_t byte data.
int16_t getKerning(char32_t left, char32_t right) const override
Return kerning adjustment for a pair of code points.
void drawGlyph(const Surface &s, char32_t code, FontLayout layout, Color color) const override
Draw a single glyph.
Low-level handle used to draw to an underlying device.
Definition drawable.h:60
@ CHECK
Definition inflate.h:47
Defines 140 opaque HTML named colors.
BlendingMode
Porter-Duff style blending modes.
Definition blending.h:17
@ kSourceOverOpaque
Similar to kSourceOver, but assumes that the destination is opaque.
@ kSource
The new ARGB8888 value completely replaces the old one.
@ kSourceOver
Source is placed (alpha-blended) over the destination. This is the default blending mode.
bool is_space(char32_t code)
static uint16_t read_glyph_index(const roo::byte *PROGMEM address, int index_bytes)
DrawableRawStreamable< RawStreamable > MakeDrawableRawStreamable(RawStreamable streamable)
FontLayout
Glyph layout direction.
Definition font.h:16
static uint32_t readTriWord(const roo::byte *PROGMEM ptr)
static int8_t readByte(const roo::byte *PROGMEM ptr)
static int16_t readWord(const roo::byte *PROGMEM ptr)
static uint16_t readUWord(const roo::byte *PROGMEM ptr)
internal::Superposition< Bg, Fg > Overlay(Bg bg, int16_t bg_x, int16_t bg_y, Fg fg, int16_t fg_x, int16_t fg_y)
static const uint8_t font[] PROGMEM
@ kVisible
Fully transparent pixels do not need to be filled.
@ kExtents
Fill the entire extents box (possibly with fully transparent pixels).
void streamToSurface(const Surface &s, RawStreamable streamable)
#define pgm_read_byte(addr)
Definition progmem.h:14
#define PROGMEM
Definition progmem.h:10
Color bgcolor
Definition smooth.cpp:889
BlendingMode blending_mode
Definition smooth.cpp:888