feat: wi-fi - expand support and fix issues, add GUI components
This commit is contained in:
@@ -161,24 +161,58 @@ struct Canvas {
|
||||
|
||||
// ---- Text ----
|
||||
|
||||
void text_bitmap(int x, int y, const char* str, Color c, int scale = 1) {
|
||||
if (!str || !font_data || scale <= 0) return;
|
||||
uint32_t pixel = c.to_pixel();
|
||||
int cx = x;
|
||||
for (int i = 0; str[i]; i++, cx += FONT_WIDTH * scale) {
|
||||
const uint8_t* glyph =
|
||||
&font_data[(unsigned char)str[i] * FONT_HEIGHT];
|
||||
for (int row = 0; row < FONT_HEIGHT; row++) {
|
||||
uint8_t bits = glyph[row];
|
||||
if (!bits) continue;
|
||||
for (int col = 0; col < FONT_WIDTH; col++) {
|
||||
if (!(bits & (0x80 >> col))) continue;
|
||||
int px = cx + col * scale;
|
||||
int py = y + row * scale;
|
||||
for (int sy = 0; sy < scale; sy++) {
|
||||
int dy = py + sy;
|
||||
if (dy < 0 || dy >= h) continue;
|
||||
for (int sx = 0; sx < scale; sx++) {
|
||||
int dx = px + sx;
|
||||
if (dx >= 0 && dx < w) pixels[dy * w + dx] = pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void text(int x, int y, const char* str, Color c) {
|
||||
if (fonts::system_font && fonts::system_font->valid) {
|
||||
fonts::system_font->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::UI_SIZE);
|
||||
if (fonts::system_font->draw_to_buffer(
|
||||
pixels, w, h, x, y, str, c, fonts::UI_SIZE))
|
||||
return;
|
||||
}
|
||||
text_bitmap(x, y, str, c);
|
||||
}
|
||||
|
||||
void text_2x(int x, int y, const char* str, Color c) {
|
||||
if (fonts::system_font && fonts::system_font->valid) {
|
||||
fonts::system_font->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::LARGE_SIZE);
|
||||
if (fonts::system_font->draw_to_buffer(
|
||||
pixels, w, h, x, y, str, c, fonts::LARGE_SIZE))
|
||||
return;
|
||||
}
|
||||
text_bitmap(x, y, str, c, 2);
|
||||
}
|
||||
|
||||
void text_mono(int x, int y, const char* str, Color c) {
|
||||
if (fonts::mono && fonts::mono->valid) {
|
||||
fonts::mono->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::TERM_SIZE);
|
||||
return;
|
||||
if (fonts::mono->draw_to_buffer(
|
||||
pixels, w, h, x, y, str, c, fonts::TERM_SIZE))
|
||||
return;
|
||||
}
|
||||
text(x, y, str, c);
|
||||
text_bitmap(x, y, str, c);
|
||||
}
|
||||
|
||||
// ---- Icons ----
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
|
||||
namespace gui {
|
||||
|
||||
static constexpr int MAX_WINDOWS = 8;
|
||||
static constexpr int MAX_WINDOWS = 32;
|
||||
static constexpr int PANEL_HEIGHT = 32;
|
||||
static constexpr int MAX_LAUNCHER_ITEMS = 64;
|
||||
|
||||
enum DesktopItemSection : uint8_t {
|
||||
DESKTOP_ITEM_SECTION_HIDDEN = 0,
|
||||
@@ -111,9 +110,10 @@ struct DesktopState {
|
||||
bool launcher_open;
|
||||
char launcher_query[64];
|
||||
int launcher_query_len;
|
||||
LauncherItem launcher_items[MAX_LAUNCHER_ITEMS];
|
||||
LauncherItem* launcher_items; // dynamically grown
|
||||
int launcher_item_count;
|
||||
int launcher_results[MAX_LAUNCHER_ITEMS];
|
||||
int launcher_item_capacity;
|
||||
int* launcher_results; // indices into launcher_items, same capacity
|
||||
int launcher_result_count;
|
||||
int launcher_selected;
|
||||
int launcher_scroll;
|
||||
@@ -122,7 +122,6 @@ struct DesktopState {
|
||||
|
||||
SvgIcon icon_terminal;
|
||||
SvgIcon icon_filemanager;
|
||||
SvgIcon icon_sysinfo;
|
||||
SvgIcon icon_appmenu;
|
||||
SvgIcon icon_folder;
|
||||
SvgIcon icon_file;
|
||||
@@ -181,6 +180,48 @@ struct DesktopState {
|
||||
uint64_t net_cfg_last_poll;
|
||||
Rect net_icon_rect;
|
||||
|
||||
// Registered link-layer interfaces. The IP configuration is global to the
|
||||
// stack, so the wired and wireless popups use `active` to decide which of
|
||||
// them owns the address currently on screen.
|
||||
static constexpr int MAX_NETIFS = 4;
|
||||
montauk::abi::NetIfInfo netifs[MAX_NETIFS];
|
||||
int netif_count;
|
||||
bool eth_present;
|
||||
|
||||
// ---- Wi-Fi -------------------------------------------------------------
|
||||
// The panel entry exists only when a supported adapter is present.
|
||||
static constexpr int MAX_WIFI_NETWORKS = 32;
|
||||
static constexpr int WIFI_SSID_CAP = 36;
|
||||
static constexpr int WIFI_PSK_CAP = 72;
|
||||
|
||||
SvgIcon icon_wifi;
|
||||
bool wifi_present;
|
||||
bool wifi_popup_open;
|
||||
Rect wifi_icon_rect;
|
||||
montauk::abi::WifiInfo wifi_info;
|
||||
montauk::abi::WifiNetwork wifi_networks[MAX_WIFI_NETWORKS];
|
||||
int wifi_network_count;
|
||||
uint64_t wifi_last_poll;
|
||||
uint32_t wifi_scan_generation;
|
||||
bool wifi_scanning;
|
||||
int wifi_scroll; // first visible row of the list
|
||||
bool wifi_boot_scan_started; // the automatic scan at startup
|
||||
bool wifi_autoconnect_done; // saved-network join already tried
|
||||
bool wifi_joining; // a join we started is in flight
|
||||
bool wifi_dhcp_pending; // ask for a lease once the link is up
|
||||
char wifi_joining_ssid[WIFI_SSID_CAP];
|
||||
char wifi_status[96]; // last result line in the popup
|
||||
uint64_t wifi_status_time;
|
||||
|
||||
// Passphrase prompt, shown when a selected network needs a key.
|
||||
bool wifi_prompt_open;
|
||||
char wifi_prompt_ssid[WIFI_SSID_CAP];
|
||||
char wifi_prompt_password[WIFI_PSK_CAP];
|
||||
int wifi_prompt_len;
|
||||
bool wifi_prompt_reveal;
|
||||
bool wifi_prompt_remember;
|
||||
uint8_t wifi_prompt_security;
|
||||
|
||||
bool vol_popup_open;
|
||||
Rect vol_icon_rect;
|
||||
int vol_level; // 0-100
|
||||
@@ -207,7 +248,7 @@ struct DesktopState {
|
||||
// IDs of external windows we've sent a close event to but that haven't
|
||||
// been destroyed yet by their owning process. Prevents the poll loop
|
||||
// from re-creating them at the default position (visible flicker).
|
||||
static constexpr int MAX_CLOSING = 8;
|
||||
static constexpr int MAX_CLOSING = MAX_WINDOWS;
|
||||
int closing_ext_ids[MAX_CLOSING];
|
||||
int closing_ext_count;
|
||||
|
||||
|
||||
@@ -14,8 +14,10 @@ namespace gui {
|
||||
static constexpr int FONT_WIDTH = 8;
|
||||
static constexpr int FONT_HEIGHT = 16;
|
||||
|
||||
// Defined in font_data.cpp
|
||||
extern const uint8_t font_data[256 * 16];
|
||||
// Defined by programs that ship the built-in VGA fallback. Keep the symbol
|
||||
// weak so Canvas remains usable by small apps that intentionally rely only on
|
||||
// TrueType fonts, while login/desktop can recover when TrueType setup fails.
|
||||
extern const uint8_t font_data[256 * 16] __attribute__((weak));
|
||||
|
||||
// Dynamic font height: TTF line height or 16 (bitmap fallback)
|
||||
inline int system_font_height() {
|
||||
|
||||
@@ -678,6 +678,78 @@ inline bool same_color(Color a, Color b) {
|
||||
return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Anti-aliased rounded-rect fill
|
||||
// ============================================================================
|
||||
|
||||
// Coverage-based pixel blend; `cov` is 0..255 and the destination is treated
|
||||
// as opaque.
|
||||
inline void aa_blend_px(Canvas& c, int x, int y, Color color, int cov) {
|
||||
if (x < 0 || x >= c.w || y < 0 || y >= c.h || cov <= 0) return;
|
||||
if (cov > 255) cov = 255;
|
||||
uint32_t dst = c.pixels[y * c.w + x];
|
||||
uint32_t dr = (dst >> 16) & 0xFF, dg = (dst >> 8) & 0xFF, db = dst & 0xFF;
|
||||
uint32_t a = (uint32_t)cov, ia = 255 - a;
|
||||
uint32_t nr = (color.r * a + dr * ia + 127) / 255;
|
||||
uint32_t ng = (color.g * a + dg * ia + 127) / 255;
|
||||
uint32_t nb = (color.b * a + db * ia + 127) / 255;
|
||||
c.pixels[y * c.w + x] = 0xFF000000u | (nr << 16) | (ng << 8) | nb;
|
||||
}
|
||||
|
||||
// Rounded-rect fill with 4x4-supersampled corners. The straight interior is
|
||||
// filled solid (fast); only the four corner arcs pay for anti-aliasing, so
|
||||
// this stays cheap even for large frames.
|
||||
inline void fill_round_rect_aa(Canvas& c, const Rect& box, int radius, Color color) {
|
||||
if (box.w <= 0 || box.h <= 0) return;
|
||||
int r = radius;
|
||||
if (r < 0) r = 0;
|
||||
if (r > box.w / 2) r = box.w / 2;
|
||||
if (r > box.h / 2) r = box.h / 2;
|
||||
|
||||
if (r == 0) {
|
||||
c.fill_rect(box.x, box.y, box.w, box.h, color);
|
||||
return;
|
||||
}
|
||||
|
||||
// Solid interior: center band + top/bottom edge bands between the corners.
|
||||
c.fill_rect(box.x, box.y + r, box.w, box.h - 2 * r, color);
|
||||
c.fill_rect(box.x + r, box.y, box.w - 2 * r, r, color);
|
||||
c.fill_rect(box.x + r, box.y + box.h - r, box.w - 2 * r, r, color);
|
||||
|
||||
const int S = 4;
|
||||
double r2 = (double)r * r;
|
||||
// (cell origin x, cell origin y, arc center x, arc center y)
|
||||
int corners[4][4] = {
|
||||
{box.x, box.y, box.x + r, box.y + r},
|
||||
{box.x + box.w - r, box.y, box.x + box.w - r, box.y + r},
|
||||
{box.x, box.y + box.h - r, box.x + r, box.y + box.h - r},
|
||||
{box.x + box.w - r, box.y + box.h - r, box.x + box.w - r, box.y + box.h - r},
|
||||
};
|
||||
for (auto& cn : corners) {
|
||||
// Push the arc center a half pixel away from this corner (toward the
|
||||
// box interior). Pixel-center sampling otherwise sits half a pixel
|
||||
// closer to the center than the legacy pixel-corner test did, which
|
||||
// filled the arcs fuller and made small radii read as square. Left
|
||||
// corners share x == box.x, top corners share y == box.y.
|
||||
double cx = cn[2] + (cn[0] == box.x ? 0.5 : -0.5);
|
||||
double cy = cn[3] + (cn[1] == box.y ? 0.5 : -0.5);
|
||||
for (int yy = 0; yy < r; yy++) {
|
||||
int py = cn[1] + yy;
|
||||
for (int xx = 0; xx < r; xx++) {
|
||||
int px = cn[0] + xx;
|
||||
int hits = 0;
|
||||
for (int sy = 0; sy < S; sy++)
|
||||
for (int sx = 0; sx < S; sx++) {
|
||||
double dx = (px + (sx + 0.5) / S) - cx;
|
||||
double dy = (py + (sy + 0.5) / S) - cy;
|
||||
if (dx * dx + dy * dy <= r2) hits++;
|
||||
}
|
||||
if (hits) aa_blend_px(c, px, py, color, hits * 255 / (S * S));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void draw_rounded_frame(Canvas& c,
|
||||
const Rect& bounds,
|
||||
int radius,
|
||||
@@ -686,16 +758,16 @@ inline void draw_rounded_frame(Canvas& c,
|
||||
int border_w = 1) {
|
||||
if (bounds.empty()) return;
|
||||
if (border_w <= 0 || same_color(fill, border)) {
|
||||
c.fill_rounded_rect(bounds.x, bounds.y, bounds.w, bounds.h, gui_max(radius, 0), fill);
|
||||
fill_round_rect_aa(c, bounds, gui_max(radius, 0), fill);
|
||||
return;
|
||||
}
|
||||
|
||||
c.fill_rounded_rect(bounds.x, bounds.y, bounds.w, bounds.h, gui_max(radius, 0), border);
|
||||
fill_round_rect_aa(c, bounds, gui_max(radius, 0), border);
|
||||
int inner_w = bounds.w - border_w * 2;
|
||||
int inner_h = bounds.h - border_w * 2;
|
||||
if (inner_w <= 0 || inner_h <= 0) return;
|
||||
c.fill_rounded_rect(bounds.x + border_w, bounds.y + border_w,
|
||||
inner_w, inner_h, gui_max(radius - border_w, 0), fill);
|
||||
Rect inner = {bounds.x + border_w, bounds.y + border_w, inner_w, inner_h};
|
||||
fill_round_rect_aa(c, inner, gui_max(radius - border_w, 0), fill);
|
||||
}
|
||||
|
||||
inline ButtonColors resolve_button_colors(ButtonVariant variant,
|
||||
@@ -942,6 +1014,165 @@ inline void draw_radio(Canvas& c,
|
||||
label, theme.text);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Checkboxes and disclosure arrows
|
||||
// ============================================================================
|
||||
|
||||
enum CheckState : uint8_t {
|
||||
CHECK_OFF = 0,
|
||||
CHECK_ON,
|
||||
CHECK_MIXED,
|
||||
};
|
||||
|
||||
inline CheckState check_state(bool checked) {
|
||||
return checked ? CHECK_ON : CHECK_OFF;
|
||||
}
|
||||
|
||||
inline Rect checkbox_indicator_rect(const Rect& option, int size = 16) {
|
||||
return {option.x, option.y + (option.h - size) / 2, size, size};
|
||||
}
|
||||
|
||||
// Squared distance from a point to a line segment (no sqrt; used for the
|
||||
// thickness test of the checkmark strokes).
|
||||
inline double checkbox_seg_dist2(double px, double py,
|
||||
double ax, double ay, double bx, double by) {
|
||||
double dx = bx - ax, dy = by - ay;
|
||||
double len2 = dx * dx + dy * dy;
|
||||
double t = len2 > 0 ? ((px - ax) * dx + (py - ay) * dy) / len2 : 0.0;
|
||||
if (t < 0) t = 0; else if (t > 1) t = 1;
|
||||
double ex = px - (ax + t * dx), ey = py - (ay + t * dy);
|
||||
return ex * ex + ey * ey;
|
||||
}
|
||||
|
||||
// Anti-aliased two-stroke checkmark via 4x4 supersampling of a fixed-width
|
||||
// polyline.
|
||||
inline void draw_check_mark(Canvas& c, const Rect& box, Color color) {
|
||||
double ax = box.x + box.w * 0.24, ay = box.y + box.h * 0.50;
|
||||
double bx = box.x + box.w * 0.42, by = box.y + box.h * 0.68;
|
||||
double cx = box.x + box.w * 0.74, cy = box.y + box.h * 0.26;
|
||||
double hw = box.w * 0.085 + 0.35; // half stroke width
|
||||
double hw2 = hw * hw;
|
||||
const int S = 4;
|
||||
for (int py = box.y - 1; py <= box.y + box.h + 1; py++) {
|
||||
if (py < 0 || py >= c.h) continue;
|
||||
for (int px = box.x - 1; px <= box.x + box.w + 1; px++) {
|
||||
if (px < 0 || px >= c.w) continue;
|
||||
int hits = 0;
|
||||
for (int sy = 0; sy < S; sy++)
|
||||
for (int sx = 0; sx < S; sx++) {
|
||||
double fx = px + (sx + 0.5) / S;
|
||||
double fy = py + (sy + 0.5) / S;
|
||||
double d1 = checkbox_seg_dist2(fx, fy, ax, ay, bx, by);
|
||||
double d2 = checkbox_seg_dist2(fx, fy, bx, by, cx, cy);
|
||||
if ((d1 < d2 ? d1 : d2) <= hw2) hits++;
|
||||
}
|
||||
if (hits) aa_blend_px(c, px, py, color, hits * 255 / (S * S));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void draw_checkbox(Canvas& c,
|
||||
const Rect& option,
|
||||
const char* label,
|
||||
CheckState state,
|
||||
const Theme& theme,
|
||||
bool enabled = true,
|
||||
bool hovered = false) {
|
||||
Rect box = checkbox_indicator_rect(option);
|
||||
int radius = gui_max(box.w / 5, 2);
|
||||
|
||||
Color fill, border, mark;
|
||||
if (!enabled) {
|
||||
fill = theme.disabled_bg;
|
||||
border = theme.disabled_bg;
|
||||
mark = theme.disabled_fg;
|
||||
} else if (state == CHECK_OFF) {
|
||||
fill = colors::WHITE;
|
||||
border = hovered ? theme.accent : theme.border;
|
||||
mark = theme.accent_fg;
|
||||
} else {
|
||||
fill = hovered ? theme.accent_hover : theme.accent;
|
||||
border = fill;
|
||||
mark = theme.accent_fg;
|
||||
}
|
||||
|
||||
if (state == CHECK_OFF) {
|
||||
// Colored ring with a white interior.
|
||||
fill_round_rect_aa(c, box, radius, border);
|
||||
Rect inner = {box.x + 1, box.y + 1, box.w - 2, box.h - 2};
|
||||
fill_round_rect_aa(c, inner, gui_max(radius - 1, 1), fill);
|
||||
} else {
|
||||
fill_round_rect_aa(c, box, radius, fill);
|
||||
}
|
||||
|
||||
if (state == CHECK_ON) {
|
||||
draw_check_mark(c, box, mark);
|
||||
} else if (state == CHECK_MIXED) {
|
||||
Rect dash = {box.x + 4, box.y + box.h / 2 - 1, box.w - 8, 2};
|
||||
fill_round_rect_aa(c, dash, 1, mark);
|
||||
}
|
||||
|
||||
if (label && label[0]) {
|
||||
int fh = system_font_height();
|
||||
c.text(box.x + box.w + theme.gap_sm,
|
||||
option.y + (option.h - fh) / 2,
|
||||
label, enabled ? theme.text : theme.text_muted);
|
||||
}
|
||||
}
|
||||
|
||||
inline Rect disclosure_rect(const Rect& option, int size = 16) {
|
||||
return {option.x, option.y + (option.h - size) / 2, size, size};
|
||||
}
|
||||
|
||||
inline void draw_disclosure(Canvas& c,
|
||||
const Rect& box,
|
||||
bool expanded,
|
||||
const Theme& theme,
|
||||
bool hovered = false) {
|
||||
Color color = hovered ? theme.text : theme.text_subtle;
|
||||
double cx = box.x + box.w / 2.0;
|
||||
double cy = box.y + box.h / 2.0;
|
||||
double s = box.w * 0.26;
|
||||
if (s < 3) s = 3;
|
||||
|
||||
// Triangle vertices (equilateral-ish), rotated per state.
|
||||
double vx[3], vy[3];
|
||||
if (expanded) { // pointing down
|
||||
vx[0] = cx - s; vy[0] = cy - s * 0.6;
|
||||
vx[1] = cx + s; vy[1] = cy - s * 0.6;
|
||||
vx[2] = cx; vy[2] = cy + s * 0.75;
|
||||
} else { // pointing right
|
||||
vx[0] = cx - s * 0.6; vy[0] = cy - s;
|
||||
vx[1] = cx - s * 0.6; vy[1] = cy + s;
|
||||
vx[2] = cx + s * 0.75; vy[2] = cy;
|
||||
}
|
||||
|
||||
// Anti-aliased fill via 4x4 supersampling of the triangle's half-plane test.
|
||||
auto edge = [](double ax, double ay, double bx, double by, double px, double py) {
|
||||
return (px - ax) * (by - ay) - (py - ay) * (bx - ax);
|
||||
};
|
||||
const int S = 4;
|
||||
for (int py = box.y; py < box.y + box.h; py++) {
|
||||
if (py < 0 || py >= c.h) continue;
|
||||
for (int px = box.x; px < box.x + box.w; px++) {
|
||||
if (px < 0 || px >= c.w) continue;
|
||||
int hits = 0;
|
||||
for (int sy = 0; sy < S; sy++)
|
||||
for (int sx = 0; sx < S; sx++) {
|
||||
double fx = px + (sx + 0.5) / S;
|
||||
double fy = py + (sy + 0.5) / S;
|
||||
double e0 = edge(vx[0], vy[0], vx[1], vy[1], fx, fy);
|
||||
double e1 = edge(vx[1], vy[1], vx[2], vy[2], fx, fy);
|
||||
double e2 = edge(vx[2], vy[2], vx[0], vy[0], fx, fy);
|
||||
bool inside = (e0 >= 0 && e1 >= 0 && e2 >= 0) ||
|
||||
(e0 <= 0 && e1 <= 0 && e2 <= 0);
|
||||
if (inside) hits++;
|
||||
}
|
||||
if (hits) aa_blend_px(c, px, py, color, hits * 255 / (S * S));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline Rect swatch_rect(int row_x, int row_y, int index, int size = 24, int gap = 6) {
|
||||
return {row_x + index * (size + gap), row_y, size, size};
|
||||
}
|
||||
|
||||
@@ -795,11 +795,21 @@ static inline void terminal_resize(TerminalState* t, int new_cols, int new_rows)
|
||||
}
|
||||
}
|
||||
|
||||
int new_scrollback = keep - new_rows;
|
||||
// Anchor the new visible region to the cursor's line. Treating the bottom
|
||||
// new_rows of kept content as the screen (the naive keep - new_rows) only
|
||||
// works when the screen is full: with a partly filled screen -- e.g. a
|
||||
// shell prompt a few lines down with blank rows below it -- it would push
|
||||
// the real text up into scrollback and drop the view onto the blank region,
|
||||
// so a zoom appears to scroll down and strands the cursor in empty space.
|
||||
// Instead pin the cursor to the bottom row when there is enough history
|
||||
// above it, otherwise keep the content anchored at the top.
|
||||
int abs_cursor_y = t->scrollback_lines + t->cursor_y - discard;
|
||||
if (abs_cursor_y < 0) abs_cursor_y = 0;
|
||||
int new_scrollback = abs_cursor_y - (new_rows - 1);
|
||||
if (new_scrollback < 0) new_scrollback = 0;
|
||||
if (new_scrollback > t->max_scrollback) new_scrollback = t->max_scrollback;
|
||||
|
||||
// Adjust cursor
|
||||
int abs_cursor_y = t->scrollback_lines + t->cursor_y - discard;
|
||||
int new_cursor_y = abs_cursor_y - new_scrollback;
|
||||
if (new_cursor_y < 0) new_cursor_y = 0;
|
||||
if (new_cursor_y >= new_rows) new_cursor_y = new_rows - 1;
|
||||
|
||||
@@ -121,10 +121,18 @@ struct TrueTypeFont {
|
||||
return false;
|
||||
}
|
||||
|
||||
montauk::read(fd, data, 0, size);
|
||||
int read_result = montauk::read(fd, data, 0, size);
|
||||
montauk::close(fd);
|
||||
if (read_result < 0 || (uint64_t)read_result != size) {
|
||||
montauk::free(data);
|
||||
data = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stbtt_InitFont(&info, data, stbtt_GetFontOffsetForIndex(data, 0))) {
|
||||
// stbtt_GetFontOffsetForIndex returns -1 for non-font data; passing
|
||||
// that into stbtt_InitFont makes it dereference data + (uint32)-1.
|
||||
int off = stbtt_GetFontOffsetForIndex(data, 0);
|
||||
if (off < 0 || !stbtt_InitFont(&info, data, off)) {
|
||||
montauk::free(data);
|
||||
data = nullptr;
|
||||
return false;
|
||||
@@ -308,13 +316,14 @@ struct TrueTypeFont {
|
||||
draw(fb, x, y, text, fg, pixel_size);
|
||||
}
|
||||
|
||||
void draw_to_buffer(uint32_t* pixels, int buf_w, int buf_h,
|
||||
bool draw_to_buffer(uint32_t* pixels, int buf_w, int buf_h,
|
||||
int x, int y, const char* text,
|
||||
Color color, int pixel_size) {
|
||||
if (!valid) return;
|
||||
if (!valid) return false;
|
||||
GlyphCache* gc = get_cache(pixel_size);
|
||||
int cx = x;
|
||||
int baseline = y + gc->ascent;
|
||||
bool drew_pixel = false;
|
||||
|
||||
for (int i = 0; text[i]; i++) {
|
||||
CachedGlyph* g = get_glyph(gc, (unsigned char)text[i]);
|
||||
@@ -331,6 +340,7 @@ struct TrueTypeFont {
|
||||
if (dx < 0 || dx >= buf_w) continue;
|
||||
uint8_t alpha = g->bitmap[row * g->width + col];
|
||||
if (alpha == 0) continue;
|
||||
drew_pixel = true;
|
||||
|
||||
if (alpha == 255) {
|
||||
pixels[dy * buf_w + dx] =
|
||||
@@ -353,6 +363,7 @@ struct TrueTypeFont {
|
||||
}
|
||||
cx += g->advance;
|
||||
}
|
||||
return drew_pixel;
|
||||
}
|
||||
|
||||
// Draw text to buffer with clip rectangle (pixels outside clip_x..clip_x+clip_w are not drawn)
|
||||
|
||||
Reference in New Issue
Block a user