75#include "roo_io/core/input_iterator.h"
76#include "roo_io/text/unicode.h"
77#include "roo_logging.h"
82constexpr uint8_t kKerningFormatNone = 0;
83constexpr uint8_t kKerningFormatPairs = 1;
84constexpr uint8_t kKerningFormatClasses = 2;
87class FontMetricReader {
90 roo_io::UnsafeGenericMemoryIterator<const roo::byte PROGMEM*>&
reader,
95 return font_metric_bytes_ == 1 ? roo_io::ReadS8(reader_)
96 : roo_io::ReadBeS16(reader_);
100 roo_io::UnsafeGenericMemoryIterator<const roo::byte PROGMEM*>& reader_;
101 int font_metric_bytes_;
131 if (font_.font_metric_bytes_ == 1) {
150 glyphXMin, glyphYMin, glyphXMax, glyphYMax,
158 const roo::byte*
PROGMEM ptr = ptr_ + 5 * font_.font_metric_bytes_;
173 : glyph_count_(0), default_glyph_(0), default_space_width_(0) {
174 roo_io::UnsafeGenericMemoryIterator<const roo::byte PROGMEM*>
reader(
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);
183 <<
"Unsupported character encoding: " << encoding_bytes_;
184 font_metric_bytes_ = roo_io::ReadU8(
reader);
186 <<
"Unsupported count of metric bytes: " << encoding_bytes_;
188 offset_bytes_ = roo_io::ReadU8(
reader);
190 <<
"Unsupported count of offset bytes: " << offset_bytes_;
192 compression_method_ = roo_io::ReadU8(
reader);
194 <<
"Unsupported compression method: " << compression_method_;
196 glyph_count_ = roo_io::ReadBeU16(
reader);
197 kerning_pairs_count_ = 0;
198 cmap_entries_count_ = roo_io::ReadBeU16(
reader);
213 if (encoding_bytes_ == 1) {
214 default_glyph_ = roo_io::ReadU8(
reader);
216 default_glyph_ = roo_io::ReadBeU16(
reader);
227 kerning_class_count_ = 0;
228 kerning_source_count_ = 0;
229 kerning_class_entries_count_ = 0;
234 if (cmap_entries_count_ > 0) {
236 std::unique_ptr<CmapEntry[]>(
new CmapEntry[cmap_entries_count_]);
238 for (
int i = 0;
i < cmap_entries_count_; ++
i) {
240 CmapEntry& dst = cmap_entries_[
i];
248 CHECK_LT(dst.data_offset, (1u << 24)) <<
"Cmap data offset out of range";
252 glyph_metadata_size_ = (5 * font_metric_bytes_) + offset_bytes_;
256 glyph_index_bytes_ = (glyph_count_ < (1 << 8)) ? 1 : 2;
261 kerning_source_index_bytes_ = 0;
262 kerning_class_entries_count_ = 0;
263 glyph_kerning_size_ = 0;
268 glyph_kerning_size_ = 2 * glyph_index_bytes_ + 1;
275 kerning_source_index_bytes_ = glyph_index_bytes_;
309 return code == 0x0020 ||
code == 0x00A0 ||
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,
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,
328 ProgMemRaster<Alpha4> glyph(
metrics.width(),
metrics.height(), data, color);
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,
341 if (bgColor.a() == 0xFF &&
349 Box inner = glyph.extents().translate(x, y);
354 if (outer.yMin() < inner.yMin()) {
357 Box(outer.xMin(), outer.yMin(), outer.xMax(), inner.yMin() - 1),
360 if (outer.xMin() < inner.xMin()) {
363 Box(outer.xMin(), inner.yMin(), inner.xMin() - 1, inner.yMax()),
369 if (outer.xMax() > inner.xMax()) {
372 Box(inner.xMax() + 1, inner.yMin(), outer.xMax(), inner.yMax()),
375 if (outer.yMax() > inner.yMax()) {
378 Box(outer.xMin(), inner.yMax() + 1, outer.xMax(), outer.yMax()),
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,
388 Box box = glyph_metrics.screen_extents().translate(offset, 0);
389 if (rle() && compressed) {
391 RleImage4bppxBiased<Alpha4>(box, data, color));
392 drawBordered(output, x, y, bgwidth, glyph, clip_box, bgColor,
398 drawBordered(output, x, y, bgwidth, glyph, clip_box, bgColor,
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) {
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) {
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) {
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,
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,
449 return font_->glyph_data_begin_ +
450 (swapped_ ? data_offset_2_ : data_offset_1_);
454 return font_->glyph_data_begin_ +
455 (swapped_ ? data_offset_1_ : data_offset_2_);
459 return swapped_ ? compressed_2_ : compressed_1_;
462 return swapped_ ? compressed_1_ : compressed_2_;
466 return swapped_ ? glyph_index_2_ : glyph_index_1_;
470 return swapped_ ? glyph_index_1_ : glyph_index_2_;
474 swapped_ = !swapped_;
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;
484 glyph_index = font_->findGlyphIndex(font_->default_glyph_);
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;
493 *mutable_right_metrics() =
glyph_meta.readMetrics(
495 *mutable_right_data_offset() =
glyph_meta.data_offset();
501 swapped_ = !swapped_;
502 *mutable_right_glyph_index() = -1;
506 GlyphMetrics* mutable_right_metrics() {
return swapped_ ? &m1_ : &m2_; }
508 long* mutable_right_data_offset() {
509 return swapped_ ? &data_offset_1_ : &data_offset_2_;
512 int* mutable_right_glyph_index() {
513 return swapped_ ? &glyph_index_1_ : &glyph_index_2_;
516 bool& mutable_right_compressed() {
517 return swapped_ ? compressed_1_ : compressed_2_;
520 const SmoothFontV2* font_;
526 int glyph_index_1_ = -1;
527 int glyph_index_2_ = -1;
607 glyphs.left_metrics().glyphYMin(),
609 glyphs.left_metrics().glyphYMax(),
633 if (
glyphs.right_metrics().lsb() < 0) {
653 s.clip_box(),
color,
s.bgcolor(),
s.blending_mode());
654 x += (
glyphs.left_metrics().advance() -
kern);
669 drawKernedGlyphsModeFill(
677 color,
s.bgcolor(),
s.blending_mode());
685 }
else if (
glyphs.left_metrics().rsb() > 0) {
695 color,
s.bgcolor(),
s.blending_mode());
716 Box(x, y -
metrics().glyphYMax(), x + default_space_width_ - 1,
740 color,
s.bgcolor(),
s.blending_mode());
755 color,
s.bgcolor(),
s.blending_mode());
765 ? default_space_width_
779 int left_glyph_index = findGlyphIndex(left);
780 if (left_glyph_index == -1) {
782 left_glyph_index = findGlyphIndex(default_glyph_);
785 int right_glyph_index = findGlyphIndex(right);
786 if (right_glyph_index == -1) {
788 right_glyph_index = findGlyphIndex(default_glyph_);
791 return kerning(left_glyph_index, right_glyph_index);
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];
802 if (
entry.format == 0) {
805 if (entry.format == 1) {
806 const roo::byte*
PROGMEM data = font_begin_ + entry.data_offset;
808 int hi = (int)entry.data_entries_count - 1;
810 int mid = (lo + hi) / 2;
811 uint16_t val =
readUWord(data + mid * 2);
812 if (val == rel)
return entry.glyph_id_offset + mid;
831const roo::byte*
PROGMEM SmoothFontV2::findKernPair(
char32_t left,
832 char32_t right)
const {
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) {
843 int hi = kerning_pairs_count_ - 1;
845 int mid = (lo + hi) / 2;
846 const roo::byte*
PROGMEM entry =
847 glyph_kerning_begin_ + mid * glyph_kerning_size_;
851 if (left == left_glyph_index && right == right_glyph_index) {
854 if (left < left_glyph_index ||
855 (left == left_glyph_index && right < right_glyph_index)) {
863 if (kerning_format_ == kKerningFormatClasses) {
864 return kerningWithClassFormat(left_glyph_index, right_glyph_index);
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;
875 int entry_size = kerning_source_index_bytes_ + 1;
877 int hi = kerning_source_count_ - 1;
878 uint8_t class_id = 0xFF;
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_);
889 if (glyph_index < left_glyph_index) {
896 if (class_id == 0xFF || class_id >= kerning_class_count_)
return 0;
899 const roo::byte*
PROGMEM cls = kerning_class_table_begin_ + class_id * 4;
902 if (count == 0)
return 0;
905 const roo::byte*
PROGMEM entries =
906 kerning_class_entries_begin_ + offset * entry_size;
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) {
916 if (glyph_index < right_glyph_index) {
Axis-aligned integer rectangle.
static Box Intersect(const Box &a, const Box &b)
Return the intersection of two boxes (may be empty).
ARGB8888 color stored as a 32-bit unsigned integer.
The abstraction for drawing to a display.
void fillRect(BlendingMode blending_mode, const Box &rect, Color color)
Fill a single rectangle. Invalidates the address window.
FontMetricReader(roo_io::UnsafeGenericMemoryIterator< const roo::byte PROGMEM * > &reader, int font_metric_bytes)
Basic font metrics (ascent, descent, bounding box, and line spacing).
int16_t linegap() const
Additional gap between lines.
int16_t glyphYMin() const
int16_t glyphYMax() const
Metadata describing a font's encoding and spacing behavior.
void init(FontMetrics metrics, FontProperties properties)
const FontMetrics & metrics() const
Return font metrics.
Per-glyph metrics (bounding box and advance).
bool left_compressed() const
const roo::byte *PROGMEM left_data() const
GlyphPairIterator(const SmoothFontV2 *font)
const GlyphMetrics & left_metrics() const
int left_glyph_index() const
const GlyphMetrics & right_metrics() const
bool right_compressed() const
const roo::byte *PROGMEM right_data() const
int right_glyph_index() 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.
Defines 140 opaque HTML named colors.
BlendingMode
Porter-Duff style blending modes.
@ 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.
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)
BlendingMode blending_mode