Skip to content

Commit

Permalink
Add text support
Browse files Browse the repository at this point in the history
  • Loading branch information
Mat931 committed Feb 26, 2024
1 parent c289329 commit a322a01
Show file tree
Hide file tree
Showing 3 changed files with 394 additions and 10 deletions.
84 changes: 82 additions & 2 deletions display-simulator/esphome-display.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,14 +255,55 @@ class Display {
} else if (y1 == y2) { // Check for special case of a top-flat triangle
this.filled_flat_side_triangle_(x3, y3, x1, y1, x2, y2, color);
} else { // General case: split the no-flat-side triangle in a top-flat triangle and bottom-flat triangle
let x_temp = Math.floor(x1 + ((y2 - y1) / (y3 - y1)) * (x3 - x1)),
let x_temp = Math.floor(x1 + Math.floor((y2 - y1) / (y3 - y1)) * (x3 - x1)),
y_temp = y2;
this.filled_flat_side_triangle_(x1, y1, x2, y2, x_temp, y_temp, color);
this.filled_flat_side_triangle_(x3, y3, x2, y2, x_temp, y_temp, color);
}
}

image(x, y, image, align = Image.TOP_LEFT, color_on = this.COLOR_ON, color_off = this.COLOR_OFF) {
print(x, y, font, arg4, arg5 = null, arg6 = null) {
let color = this.COLOR_ON;
let align = Font.TOP_LEFT;
let text;
if (arg5 === null && arg6 === null) {
text = arg4;
} else if (arg6 === null) {
if (typeof(arg4) == "number") {
align = arg4;
} else {
color = arg4;
}
text = arg5;
} else {
color = arg4;
align = arg5;
text = arg6;
}
x = Math.floor(x);
y = Math.floor(y);
let [x_start, y_start, width, height] = this.get_text_bounds(x, y, text, font, align);
font.print(x_start, y_start, this, color, text);
}

image(x, y, image, arg4 = null, arg5 = null, arg6 = null) {
let align = Image.TOP_LEFT;
let color_on = this.COLOR_ON;
let color_off = this.COLOR_OFF
if (typeof(arg4) == "number") {
align = arg4;
if (arg5 !== null) {
color_on = arg5;
if (arg6 !== null) {
color_off = arg6;
}
}
} else if (arg4 !== null) {
color_on = arg4;
if (arg5 !== null) {
color_off = arg5;
}
}
x = Math.floor(x);
y = Math.floor(y);
let x_align = Math.floor(align) & Image.HORIZONTAL_ALIGNMENT;
Expand Down Expand Up @@ -294,4 +335,43 @@ class Display {

image.draw(x, y, this, color_on, color_off);
}

get_text_bounds(x, y, text, font, align) {
let [width, x_offset, baseline, height] = font.measure(text);

let x_align = Math.floor(align) & 0x18;
let y_align = Math.floor(align) & 0x07;
let x1, y1;

switch (x_align) {
case Font.RIGHT:
x1 = x - width;
break;
case Font.CENTER_HORIZONTAL:
x1 = x - Math.floor(width / 2);
break;
case Font.LEFT:
default:
// LEFT
x1 = x;
break;
}

switch (y_align) {
case Font.BOTTOM:
y1 = y - height;
break;
case Font.BASELINE:
y1 = y - baseline;
break;
case Font.CENTER_VERTICAL:
y1 = y - Math.floor(height / 2);
break;
case Font.TOP:
default:
y1 = y;
break;
}
return [x1, y1, width, height];
}
}
187 changes: 187 additions & 0 deletions display-simulator/esphome-font.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
class Glyph {
// Code in this class was taken from esphome/components/font/font.cpp and translated to JavaScript

constructor(data) {
this.glyph_data_ = data;
}

draw(x, y, display, color) {
let x_at = Math.floor(x)
let y_start = Math.floor(y);
let scan_x1 = this.glyph_data_.offset_x;
let scan_y1 = this.glyph_data_.offset_y;
let scan_width = this.glyph_data_.width;
let scan_height = this.glyph_data_.height;

let data = 0;
let max_x = x_at + scan_x1 + scan_width;
let max_y = y_start + scan_y1 + scan_height;

for (let glyph_y = y_start + scan_y1; glyph_y < max_y; glyph_y++) {
for (let glyph_x = x_at + scan_x1; glyph_x < max_x; data++, glyph_x += 8) {
let pixel_data = this.glyph_data_.data[data];
let pixel_max_x = Math.min(max_x, glyph_x + 8);

for (let pixel_x = glyph_x; pixel_x < pixel_max_x && pixel_data; pixel_x++, pixel_data <<= 1) {
if (pixel_data & 0x80) {
display.draw_pixel_at(pixel_x, glyph_y, color);
}
}
}
}
}

get_char() {
return this.glyph_data_.a_char;
}

compare_to(str) {
// 1 -> this.char_
// 2 -> str
for (let i = 0;; i++) {
if (this.glyph_data_.a_char.charCodeAt(i) == 0)
return true;
if (str.charCodeAt(i) == 0)
return false;
if (this.glyph_data_.a_char.charCodeAt(i) > str.charCodeAt(i))
return false;
if (this.glyph_data_.a_char.charCodeAt(i) < str.charCodeAt(i))
return true;
}
// this should not happen
return false;
}

match_length(str) {
for (let i = 0;; i++) {
if (this.glyph_data_.a_char.charCodeAt(i) == 0)
return i;
if (str.charCodeAt(i) != this.glyph_data_.a_char.charCodeAt(i))
return 0;
}
// this should not happen
return 0;
}
}

class Font {
// Code in this class was taken from esphome/components/font/font.cpp and translated to JavaScript
static TOP = 0x00;
static CENTER_VERTICAL = 0x01;
static BASELINE = 0x02;
static BOTTOM = 0x04;
static LEFT = 0x00;
static CENTER_HORIZONTAL = 0x08;
static RIGHT = 0x10;
static TOP_LEFT = Font.TOP | Font.LEFT;
static TOP_CENTER = Font.TOP | Font.CENTER_HORIZONTAL;
static TOP_RIGHT = Font.TOP | Font.RIGHT;
static CENTER_LEFT = Font.CENTER_VERTICAL | Font.LEFT;
static CENTER = Font.CENTER_VERTICAL | Font.CENTER_HORIZONTAL;
static CENTER_RIGHT = Font.CENTER_VERTICAL | Font.RIGHT;
static BASELINE_LEFT = Font.BASELINE | Font.LEFT;
static BASELINE_CENTER = Font.BASELINE | Font.CENTER_HORIZONTAL;
static BASELINE_RIGHT = Font.BASELINE | Font.RIGHT;
static BOTTOM_LEFT = Font.BOTTOM | Font.LEFT;
static BOTTOM_CENTER = Font.BOTTOM | Font.CENTER_HORIZONTAL;
static BOTTOM_RIGHT = Font.BOTTOM | Font.RIGHT;

constructor(data, data_nr, baseline, height) {
this.glyphs_ = [];
for (let i = 0; i < data.length; i++) {
this.glyphs_.push(new Glyph(data[i]));
}

this.baseline_ = Math.floor(baseline);
this.height_ = Math.floor(height);
}

match_next_glyph(str) {
let lo = 0;
let hi = this.glyphs_.length - 1;
while (lo != hi) {
let mid = Math.floor((lo + hi + 1) / 2);
if (this.glyphs_[mid].compare_to(str)) {
lo = mid;
} else {
hi = mid - 1;
}
}
let match_length = this.glyphs_[lo].match_length(str);
if (match_length <= 0)
return [-1, match_length];
return [lo, match_length];
}

print(x_start, y_start, display, color, text) {
let i = 0;
let x_at = x_start;
if (text[text.length - 1] != "\u0000") {
text += "\u0000"
}
while (text[i] != "\u0000") {
let [glyph_n, match_length] = this.match_next_glyph(text.substring(i));
if (glyph_n < 0) {
// Unknown char, skip
console.log("Encountered character without representation in font: '" + text[i] + "'");
if (this.get_glyphs().length > 0) {
let glyph_width = this.get_glyphs()[0].glyph_data_.width;
display.filled_rectangle(x_at, y_start, glyph_width, this.height_, color);
x_at += glyph_width;
}

i++;
continue;
}

let glyph = this.get_glyphs()[glyph_n];
glyph.draw(x_at, y_start, display, color);
x_at += glyph.glyph_data_.width + glyph.glyph_data_.offset_x;
i += match_length;
}
}

measure(str) {
let i = 0;
let min_x = 0;
let has_char = false;
let x = 0;
if (str[str.length - 1] != "\u0000") {
str += "\u0000"
}
while (str[i] != "\u0000") {
let [glyph_n, match_length] = this.match_next_glyph(str.substring(i));
if (glyph_n < 0) {
// Unknown char, skip
if (this.get_glyphs().length > 0)
x += this.get_glyphs()[0].glyph_data_.width;
i++;
continue;
}

let glyph = this.glyphs_[glyph_n];
if (!has_char) {
min_x = glyph.glyph_data_.offset_x;
} else {
min_x = Math.min(min_x, x + glyph.glyph_data_.offset_x);
}
x += glyph.glyph_data_.width + glyph.glyph_data_.offset_x;

i += match_length;
has_char = true;
}
return [(x - min_x), min_x, this.baseline_, this.height_];
}

get_baseline() {
return this.baseline_;
}

get_height() {
return this.height_;
}

get_glyphs() {
return this.glyphs_;
}
}
Loading

0 comments on commit a322a01

Please sign in to comment.