fix: update NetSurf port & montauk frontend to use new paths for fonts and icons

This commit is contained in:
daniel committed 2026-09-30 08:47:44 +02:00
1 parent 6f3cf99667
commit 35e3d1a0bb
21 files changed
+3376 -34

No files matched your search

+794
View File
@@ -0,0 +1,794 @@
/* C ABI bridge to MontaukOS' native TrueType and BearSSL stacks. */
#include <gui/truetype.hpp>
#include <gui/dialogs.hpp>
#include <gui/svg.hpp>
#include <gui/canvas.hpp>
#include <gui/mtk/widgets.hpp>
#include <tls/tls.hpp>
extern "C" {
#include <stdlib.h>
#include <string.h>
}
#include "montauk_bridge.h"
namespace {
enum FontFace {
SANS_REGULAR, SANS_BOLD, SANS_ITALIC, SANS_BOLD_ITALIC,
SERIF_REGULAR, SERIF_BOLD, SERIF_ITALIC, SERIF_BOLD_ITALIC,
MONO_REGULAR, MONO_BOLD, MONO_ITALIC, MONO_BOLD_ITALIC,
UI_MEDIUM,
FONT_FACE_COUNT
};
gui::TrueTypeFont fonts[FONT_FACE_COUNT];
bool fonts_attempted;
bool font_valid[FONT_FACE_COUNT];
gui::SvgIcon icons[MTK_NETSURF_ICON_COUNT] = {};
int icons_scale = -1;
int icon_size = 16;
const char *icon_paths[MTK_NETSURF_ICON_COUNT] = {
"0:/os/icons/go-previous-symbolic.svg",
"0:/os/icons/go-next-symbolic.svg",
"0:/os/icons/view-refresh-symbolic.svg",
"0:/os/icons/media-stop.svg",
"0:/os/icons/go-home-symbolic.svg",
"0:/os/icons/window-close-symbolic.svg"
};
struct AddressInputSlot {
uintptr_t key;
gui::mtk::TextInputState state;
int view_start;
bool used;
};
AddressInputSlot address_inputs[MTK_NETSURF_MAX_TABS] = {};
uint32_t *address_scratch;
size_t address_scratch_pixels;
const char *font_paths[FONT_FACE_COUNT] = {
"0:/os/fonts/Roboto-Regular.ttf", "0:/os/fonts/Roboto-Bold.ttf",
"0:/os/fonts/Roboto-Italic.ttf", "0:/os/fonts/Roboto-BoldItalic.ttf",
"0:/os/fonts/NotoSerif-Regular.ttf", "0:/os/fonts/NotoSerif-SemiBold.ttf",
"0:/os/fonts/NotoSerif-Italic.ttf", "0:/os/fonts/NotoSerif-BoldItalic.ttf",
"0:/os/fonts/JetBrainsMono-Regular.ttf", "0:/os/fonts/JetBrainsMono-Bold.ttf",
"0:/os/fonts/JetBrainsMono-Italic.ttf", "0:/os/fonts/JetBrainsMono-BoldItalic.ttf",
"0:/os/fonts/Roboto-Medium.ttf"
};
void init_fonts()
{
if (fonts_attempted) return;
fonts_attempted = true;
for (int i = 0; i < FONT_FACE_COUNT; i++)
font_valid[i] = fonts[i].init(font_paths[i]);
gui::fonts::system_font = font_valid[UI_MEDIUM] ? &fonts[UI_MEDIUM] :
(font_valid[SANS_REGULAR] ? &fonts[SANS_REGULAR] : nullptr);
gui::fonts::system_bold = font_valid[SANS_BOLD] ? &fonts[SANS_BOLD] : gui::fonts::system_font;
gui::fonts::mono = font_valid[MONO_REGULAR] ? &fonts[MONO_REGULAR] : nullptr;
gui::fonts::mono_bold = font_valid[MONO_BOLD] ? &fonts[MONO_BOLD] : gui::fonts::mono;
}
AddressInputSlot *address_input_slot(uintptr_t key, bool create)
{
for (int i = 0; i < MTK_NETSURF_MAX_TABS; i++)
if (address_inputs[i].used && address_inputs[i].key == key)
return &address_inputs[i];
if (!create) return nullptr;
for (int i = 0; i < MTK_NETSURF_MAX_TABS; i++) {
if (!address_inputs[i].used) {
address_inputs[i] = {};
address_inputs[i].used = true;
address_inputs[i].key = key;
gui::mtk::text_input_reset(address_inputs[i].state, 0);
return &address_inputs[i];
}
}
return nullptr;
}
gui::TrueTypeFont *select_font(int family, int weight, unsigned flags)
{
init_fonts();
int base = family == 1 ? SERIF_REGULAR :
(family == 2 ? MONO_REGULAR : SANS_REGULAR);
bool bold = weight >= 600;
bool italic = (flags & 3u) != 0;
if (family == 0 && !bold && !italic && weight >= 500 &&
font_valid[UI_MEDIUM]) return &fonts[UI_MEDIUM];
int face = base + (bold ? 1 : 0) + (italic ? 2 : 0);
if (!font_valid[face]) face = base + (bold ? 1 : 0);
if (!font_valid[face]) face = base;
return font_valid[face] ? &fonts[face] : nullptr;
}
uint32_t next_utf8(const char *text, size_t length, size_t *offset)
{
if (*offset >= length) return 0;
uint8_t c = static_cast<uint8_t>(text[(*offset)++]);
if (c < 0x80) return c;
unsigned extra;
uint32_t value;
uint32_t minimum;
if ((c & 0xe0) == 0xc0) { extra = 1; value = c & 0x1f; minimum = 0x80; }
else if ((c & 0xf0) == 0xe0) { extra = 2; value = c & 0x0f; minimum = 0x800; }
else if ((c & 0xf8) == 0xf0) { extra = 3; value = c & 0x07; minimum = 0x10000; }
else return 0xfffd;
if (extra > length - *offset) { *offset = length; return 0xfffd; }
for (unsigned i = 0; i < extra; i++) {
uint8_t trail = static_cast<uint8_t>(text[(*offset)++]);
if ((trail & 0xc0) != 0x80) return 0xfffd;
value = (value << 6) | (trail & 0x3f);
}
if (value < minimum || value > 0x10ffff ||
(value >= 0xd800 && value <= 0xdfff)) return 0xfffd;
return value;
}
int glyph_advance(gui::TrueTypeFont *font, gui::GlyphCache *cache, uint32_t cp)
{
if (cp < 256) {
gui::CachedGlyph *glyph = font->get_glyph(cache, static_cast<int>(cp));
return glyph != nullptr ? glyph->advance : 0;
}
int advance = 0, lsb = 0;
stbtt_GetCodepointHMetrics(&font->info, static_cast<int>(cp), &advance, &lsb);
return static_cast<int>(advance * cache->scale);
}
void blend_glyph(uint32_t *pixels, int width, int height,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
int x, int baseline, const uint8_t *bitmap, int glyph_width,
int glyph_height, int xoff, int yoff, gui::Color colour)
{
if (bitmap == nullptr) return;
int gx = x + xoff, gy = baseline + yoff;
for (int row = 0; row < glyph_height; row++) {
int dy = gy + row;
if (dy < clip_y0 || dy >= clip_y1 || dy < 0 || dy >= height) continue;
for (int col = 0; col < glyph_width; col++) {
int dx = gx + col;
if (dx < clip_x0 || dx >= clip_x1 || dx < 0 || dx >= width) continue;
uint8_t alpha = bitmap[row * glyph_width + col];
if (alpha == 0) continue;
uint32_t dst = pixels[dy * width + dx];
unsigned inv = 255 - alpha;
unsigned r = (alpha * colour.r + inv * ((dst >> 16) & 255) + 128) / 255;
unsigned g = (alpha * colour.g + inv * ((dst >> 8) & 255) + 128) / 255;
unsigned b = (alpha * colour.b + inv * (dst & 255) + 128) / 255;
pixels[dy * width + dx] = 0xff000000u | (r << 16) | (g << 8) | b;
}
}
}
} // namespace
extern "C" void mtk_netsurf_icons_cleanup(void)
{
for (int i = 0; i < MTK_NETSURF_ICON_COUNT; i++) {
if (icons[i].pixels != nullptr) gui::svg_free(icons[i]);
icons[i] = {};
}
icons_scale = -1;
}
extern "C" void mtk_netsurf_icons_set_scale(int scale)
{
if (scale < 0) scale = 0;
if (scale > 2) scale = 2;
if (icons_scale == scale) return;
mtk_netsurf_icons_cleanup();
icons_scale = scale;
icon_size = scale == 0 ? 14 : (scale == 2 ? 20 : 16);
gui::Color colour = gui::Color::from_rgb(55, 55, 55);
for (int i = 0; i < MTK_NETSURF_ICON_COUNT; i++)
icons[i] = gui::svg_load(icon_paths[i], icon_size, icon_size, colour);
}
extern "C" int mtk_netsurf_icon_size(void)
{
return icon_size;
}
extern "C" void mtk_netsurf_icon_draw(int icon, uint32_t *pixels,
int width, int height, int x, int y, unsigned opacity)
{
if (icon < 0 || icon >= MTK_NETSURF_ICON_COUNT || pixels == nullptr ||
icons[icon].pixels == nullptr || opacity == 0) return;
if (opacity > 255) opacity = 255;
const gui::SvgIcon &source = icons[icon];
for (int row = 0; row < source.height; row++) {
int dy = y + row;
if (dy < 0 || dy >= height) continue;
for (int col = 0; col < source.width; col++) {
int dx = x + col;
if (dx < 0 || dx >= width) continue;
uint32_t src = source.pixels[row * source.width + col];
unsigned alpha = ((src >> 24) & 255u) * opacity / 255u;
if (alpha == 0) continue;
uint32_t dst = pixels[dy * width + dx];
unsigned inv = 255 - alpha;
unsigned r = (alpha * ((src >> 16) & 255u) + inv * ((dst >> 16) & 255u) + 128) / 255;
unsigned g = (alpha * ((src >> 8) & 255u) + inv * ((dst >> 8) & 255u) + 128) / 255;
unsigned b = (alpha * (src & 255u) + inv * (dst & 255u) + 128) / 255;
pixels[dy * width + dx] = 0xff000000u | (r << 16) | (g << 8) | b;
}
}
}
extern "C" void mtk_netsurf_address_input_focus(uintptr_t key,
const char *text, int select_all)
{
AddressInputSlot *slot = address_input_slot(key, true);
if (slot == nullptr) return;
int length = text != nullptr ? static_cast<int>(strlen(text)) : 0;
if (select_all) {
slot->state.selection_anchor = 0;
slot->state.cursor = length;
} else {
gui::mtk::text_input_set_cursor(slot->state, length,
slot->state.cursor, false);
}
slot->state.dragging = false;
gui::mtk::context_menu_close(slot->state.context);
}
extern "C" int mtk_netsurf_address_input_key(uintptr_t key, char *text,
int capacity, int scancode, int ascii, int pressed, int shift,
int ctrl, int alt)
{
AddressInputSlot *slot = address_input_slot(key, true);
if (slot == nullptr) return 0;
montauk::abi::KeyEvent event = {};
event.scancode = static_cast<uint8_t>(scancode);
event.ascii = static_cast<char>(ascii);
event.pressed = pressed != 0;
event.shift = shift != 0;
event.ctrl = ctrl != 0;
event.alt = alt != 0;
return gui::mtk::text_input_key(slot->state, text, capacity, event);
}
extern "C" int mtk_netsurf_address_input_mouse(uintptr_t key, char *text,
int capacity, int x, int y, int buttons, int previous_buttons,
int field_x, int field_y, int field_w, int field_h,
int view_w, int view_h, int focused)
{
AddressInputSlot *slot = address_input_slot(key, true);
if (slot == nullptr) return 0;
gui::Rect bounds = {field_x, field_y, field_w, field_h};
if (slot->state.context.open) {
return gui::mtk::text_input_handle_mouse(slot->state, bounds,
text, capacity, x, y, static_cast<uint8_t>(buttons),
static_cast<uint8_t>(previous_buttons), view_w, view_h,
focused != 0);
}
bool right_pressed = (buttons & 2) != 0 && (previous_buttons & 2) == 0;
if (right_pressed && bounds.contains(x, y)) {
int visible = gui::mtk::text_input_index_from_x(bounds,
text + slot->view_start, x, false);
int cursor = slot->view_start + visible;
int length = gui::mtk::text_input_len(text, capacity);
int start = gui::mtk::text_input_selection_start(slot->state, length);
int end = gui::mtk::text_input_selection_end(slot->state, length);
if (start == end || cursor < start || cursor > end)
gui::mtk::text_input_set_cursor(slot->state, length, cursor, false);
slot->state.dragging = false;
gui::mtk::context_menu_open(slot->state.context, x, y);
return gui::mtk::TEXT_INPUT_CONSUMED;
}
gui::mtk::TextInputState visible = slot->state;
visible.cursor = visible.cursor > slot->view_start ?
visible.cursor - slot->view_start : 0;
visible.selection_anchor = visible.selection_anchor > slot->view_start ?
visible.selection_anchor - slot->view_start : 0;
int result = gui::mtk::text_input_mouse(visible, bounds,
text + slot->view_start, x, y, static_cast<uint8_t>(buttons),
static_cast<uint8_t>(previous_buttons), focused != 0, false);
slot->state.cursor = visible.cursor + slot->view_start;
slot->state.selection_anchor = visible.selection_anchor + slot->view_start;
slot->state.dragging = visible.dragging;
return result;
}
extern "C" void mtk_netsurf_address_input_destroy(uintptr_t key)
{
AddressInputSlot *slot = address_input_slot(key, false);
if (slot != nullptr) *slot = {};
}
extern "C" int mtk_netsurf_page_scrollbar_mouse(int x, int y, int buttons,
int previous_buttons, int width, int height, int chrome_height,
int content_extent, int *scroll_y, int *hovered, int *dragging,
int *drag_offset)
{
if (scroll_y == nullptr || hovered == nullptr || dragging == nullptr ||
drag_offset == nullptr) return 0;
gui::Rect viewport = {0, chrome_height, width, height - chrome_height};
gui::Rect track = gui::mtk::scrollbar_track_rect(viewport);
gui::Rect thumb = gui::mtk::scrollbar_thumb_rect(track,
content_extent, viewport.h, *scroll_y);
bool left_down = (buttons & 1) != 0;
bool just_pressed = left_down && (previous_buttons & 1) == 0;
bool just_released = !left_down && (previous_buttons & 1) != 0;
bool scrollable = !track.empty() && !thumb.empty();
int result = 0;
int hover_now = scrollable && track.contains(x, y) ? 1 : 0;
if (*hovered != hover_now) {
*hovered = hover_now;
result |= 2;
}
if (!scrollable) {
if (*dragging) {
*dragging = 0;
result |= 2;
}
return result;
}
if (*dragging) {
if (left_down) {
int next = gui::mtk::scrollbar_offset_from_thumb_top(track,
content_extent, viewport.h, thumb.h, y - *drag_offset);
if (next != *scroll_y) {
*scroll_y = next;
result |= 2;
}
}
if (just_released) {
*dragging = 0;
result |= 2;
}
return result | 1;
}
if (just_pressed && track.contains(x, y)) {
*dragging = 1;
if (thumb.contains(x, y)) {
*drag_offset = y - thumb.y;
} else {
*drag_offset = thumb.h / 2;
*scroll_y = gui::mtk::scrollbar_offset_from_thumb_top(track,
content_extent, viewport.h, thumb.h, y - *drag_offset);
}
return result | 3;
}
return result;
}
extern "C" int mtk_netsurf_page_view_width(int width)
{
int view_width = width - gui::mtk::SCROLLBAR_W;
return view_width > 1 ? view_width : 1;
}
extern "C" void mtk_netsurf_chrome_metrics(int scale, int width, int tab_count,
struct mtk_netsurf_chrome_metrics *m)
{
if (m == nullptr) return;
if (scale < 0) scale = 0;
if (scale > 2) scale = 2;
memset(m, 0, sizeof(*m));
const int margin = scale == 2 ? 12 : 8;
const int nav_h = scale == 0 ? 36 : (scale == 2 ? 54 : 44);
m->tab_h = scale == 0 ? 30 : (scale == 2 ? 44 : 36);
m->chrome_h = m->tab_h + nav_h;
m->status_h = 0;
m->button_size = scale == 0 ? 26 : (scale == 2 ? 36 : 30);
m->button_gap = scale == 2 ? 7 : 5;
m->button_y = m->tab_h + (nav_h - m->button_size) / 2;
m->back_x = margin;
m->forward_x = m->back_x + m->button_size + m->button_gap;
m->reload_x = m->forward_x + m->button_size + m->button_gap;
m->address_x = m->reload_x + m->button_size + margin + 2;
m->address_y = m->button_y;
m->address_h = m->button_size;
m->menu_x = width - margin - m->button_size;
m->home_x = m->menu_x - m->button_gap - m->button_size;
m->address_w = m->home_x - m->button_gap - m->address_x;
if (m->address_w < 100) m->address_w = 100;
m->tab_x = margin;
m->tab_y = scale == 2 ? 5 : 4;
m->add_w = m->tab_h - 8;
m->add_h = m->tab_h - 8;
m->add_y = 4;
int tabs_available = width - margin * 2 - m->add_w - m->button_gap;
if (tab_count < 1) tab_count = 1;
m->tab_w = tabs_available / tab_count;
int tab_max = scale == 0 ? 190 : (scale == 2 ? 260 : 220);
int tab_min = scale == 0 ? 50 : (scale == 2 ? 70 : 58);
if (m->tab_w > tab_max) m->tab_w = tab_max;
if (m->tab_w < tab_min) m->tab_w = tab_min;
m->tab_content_w = m->tab_w - m->button_gap;
m->tab_close_w = scale == 2 ? 30 : 26;
m->add_x = m->tab_x + tab_count * m->tab_w;
/* MTK menus deliberately keep the same compact physical metrics as the
* desktop context menu. Browser chrome scales, but scaling this popup as
* well made it unusually sparse beside every other system menu. It needs
* a little more width than the desktop menu for keyboard shortcuts. */
m->popup_w = 220;
m->popup_item_h = 28;
m->popup_x = width - margin - m->popup_w;
m->popup_y = m->chrome_h + 4;
}
namespace {
gui::Color chrome_colour(unsigned value)
{
return gui::Color::from_rgb(value, value, value);
}
bool chrome_contains(int px, int py, int x, int y, int w, int h)
{
return px >= x && py >= y && px < x + w && py < y + h;
}
int chrome_text_baseline(int y, int height, int weight, int size)
{
gui::TrueTypeFont *font = select_font(0, weight, 0);
if (font == nullptr) return y + (height + size) / 2;
gui::GlyphCache *cache = font->get_cache(size > 0 ? size : 1);
return y + (height - cache->line_height) / 2 + cache->ascent;
}
void chrome_text(const mtk_netsurf_chrome_model *model, int x, int baseline,
const char *value, gui::Color colour, int weight, int size)
{
if (value == nullptr) return;
mtk_netsurf_font_draw(model->pixels, model->width, model->height,
0, 0, model->width, model->height, x, baseline, value, strlen(value),
colour.to_pixel(), 0, weight, 0, size);
}
void chrome_text_clipped(const mtk_netsurf_chrome_model *model, int x,
int baseline, int available, const char *value, gui::Color colour,
int weight, int size)
{
if (value == nullptr || available <= 0) return;
if (mtk_netsurf_font_width(0, weight, 0, size, value, strlen(value)) <= available) {
chrome_text(model, x, baseline, value, colour, weight, size);
return;
}
char clipped[128];
size_t length = strlen(value);
if (length > sizeof(clipped) - 4) length = sizeof(clipped) - 4;
memcpy(clipped, value, length);
while (length > 0) {
clipped[length] = 0;
strcpy(clipped + length, "...");
if (mtk_netsurf_font_width(0, weight, 0, size, clipped,
length + 3) <= available) break;
do { length--; }
while (length > 0 && (static_cast<unsigned char>(clipped[length]) & 0xc0) == 0x80);
}
chrome_text(model, x, baseline, clipped, colour, weight, size);
}
void chrome_icon_button(gui::Canvas &canvas,
const mtk_netsurf_chrome_model *model, int x, int y, int size,
int icon, bool enabled, bool active = false)
{
bool hover = chrome_contains(model->mouse_x, model->mouse_y, x, y, size, size);
gui::Color bg = active ? gui::Color::from_rgb(0xc0, 0xd0, 0xe8) :
(hover && enabled ? chrome_colour(0xdc) : chrome_colour(0xe8));
canvas.fill_rounded_rect(x, y, size, size, 4, bg);
int is = mtk_netsurf_icon_size();
mtk_netsurf_icon_draw(icon, model->pixels, model->width, model->height,
x + (size - is) / 2, y + (size - is) / 2, enabled ? 255 : 72);
}
void chrome_address(gui::Canvas &canvas, const mtk_netsurf_chrome_model *model,
const struct mtk_netsurf_chrome_metrics &m, int font)
{
AddressInputSlot *slot = address_input_slot(model->address_input_key, true);
if (slot == nullptr) return;
const char *text = model->address != nullptr ? model->address : "";
int length = static_cast<int>(strlen(text));
slot->state.cursor = gui::mtk::text_input_clamp_cursor(slot->state.cursor, length);
slot->state.selection_anchor = gui::mtk::text_input_clamp_cursor(
slot->state.selection_anchor, length);
int start = 0;
if (model->address_focus) {
int cursor = slot->state.cursor;
int available = m.address_w - 18;
int used = 0;
start = cursor;
while (start > 0) {
int previous = start - 1;
while (previous > 0 &&
(static_cast<unsigned char>(text[previous]) & 0xc0) == 0x80)
previous--;
int glyph = mtk_netsurf_font_width(0, 500, 0, font,
text + previous, static_cast<size_t>(start - previous));
if (used + glyph > available) break;
used += glyph;
start = previous;
}
}
slot->view_start = start;
size_t needed = static_cast<size_t>(m.address_w) * m.address_h;
if (needed > address_scratch_pixels) {
uint32_t *replacement = static_cast<uint32_t *>(malloc(needed * sizeof(uint32_t)));
if (replacement != nullptr) {
free(address_scratch);
address_scratch = replacement;
address_scratch_pixels = needed;
}
}
if (address_scratch == nullptr) return;
gui::Canvas field(address_scratch, m.address_w, m.address_h);
gui::mtk::Theme theme = gui::mtk::make_theme();
field.fill(chrome_colour(0xf5));
gui::Rect bounds = {0, 0, m.address_w, m.address_h};
int relative_cursor = slot->state.cursor - start;
int relative_anchor = slot->state.selection_anchor > start ?
slot->state.selection_anchor - start : 0;
gui::fonts::UI_SIZE = font;
gui::mtk::draw_text_field(field, bounds, text + start,
relative_cursor, model->address_focus != 0, false, theme,
relative_anchor);
for (int row = 0; row < m.address_h; row++)
for (int col = 0; col < m.address_w; col++)
model->pixels[(m.address_y + row) * model->width + m.address_x + col] =
address_scratch[row * m.address_w + col];
}
void chrome_address_context(gui::Canvas &canvas,
const mtk_netsurf_chrome_model *model)
{
AddressInputSlot *slot = address_input_slot(model->address_input_key, false);
if (slot == nullptr || !slot->state.context.open) return;
gui::mtk::Theme theme = gui::mtk::make_theme();
gui::mtk::draw_text_input_context_menu(canvas, slot->state, theme,
gui::mtk::text_input_has_selection(slot->state,
model->address != nullptr ? model->address : ""));
}
void chrome_popup(gui::Canvas &canvas, const mtk_netsurf_chrome_model *model,
const struct mtk_netsurf_chrome_metrics &m)
{
if (model->menu_mode == 0) return;
static const gui::mtk::ContextMenuItem menu_items[] = {
{"New Tab", 0, true, "Ctrl + T", false},
{"Find in Page", 1, true, "Ctrl + F", false},
{"Bookmark Page", 2, true, "Ctrl + D", false},
{"Bookmarks", 3, true, nullptr, false},
{"Copy Address", 4, true, nullptr, true},
{"Zoom In", 5, true, "Ctrl + +", false},
{"Zoom Out", 6, true, "Ctrl + -", false},
{"Reset Zoom", 7, true, "Ctrl + 0", true},
{"Home", 8, true, nullptr, false},
{"Quit", 9, true, nullptr, false},
};
gui::mtk::ContextMenuItem bookmark_items[MTK_NETSURF_MAX_BOOKMARK_ROWS + 1] = {};
const gui::mtk::ContextMenuItem *items = menu_items;
int rows = 10;
if (model->menu_mode != 1) {
rows = model->bookmark_count + 1;
bookmark_items[0] = {"Bookmarks", -1, true, nullptr, true};
for (int row = 1; row < rows; row++)
bookmark_items[row] = {
model->bookmark_titles[row - 1], row - 1, true, nullptr, false};
items = bookmark_items;
}
gui::mtk::ContextMenuState state = {true, m.popup_x, m.popup_y, -1};
state.hover = gui::mtk::context_menu_hit_item(state, rows,
model->mouse_x, model->mouse_y, model->width, model->height,
m.popup_w, m.popup_item_h);
gui::mtk::Theme theme = gui::mtk::make_theme();
/* Use the context-menu widget's native system font. Passing the browser's
* TrueType chrome font here gave this otherwise-native widget a visibly
* different weight and baseline. */
gui::mtk::draw_context_menu(canvas, state, items, rows, theme,
m.popup_w, m.popup_item_h);
}
} // namespace
extern "C" void mtk_netsurf_chrome_draw(const struct mtk_netsurf_chrome_model *model)
{
if (model == nullptr || model->pixels == nullptr || model->width <= 0 || model->height <= 0)
return;
mtk_netsurf_icons_set_scale(model->scale);
struct mtk_netsurf_chrome_metrics m;
mtk_netsurf_chrome_metrics(model->scale, model->width, model->tab_count, &m);
gui::Canvas canvas(model->pixels, model->width, model->height);
int font = model->scale == 0 ? 14 : (model->scale == 2 ? 22 : 18);
gui::Color toolbar = gui::Color::from_rgb(0xf5, 0xf5, 0xf5);
canvas.fill_rect(0, 0, model->width, m.chrome_h, toolbar);
canvas.hline(0, m.chrome_h - 1, model->width, chrome_colour(0xca));
for (int i = 0; i < model->tab_count; i++) {
int x = m.tab_x + i * m.tab_w;
int h = m.tab_h - m.tab_y;
bool active = i == model->active_tab;
bool hover = chrome_contains(model->mouse_x, model->mouse_y,
x, m.tab_y, m.tab_content_w, h);
gui::Color bg = active ? gui::Color::from_rgb(0xff, 0xff, 0xff) :
(hover ? chrome_colour(0xe2) : chrome_colour(0xeb));
canvas.fill_rounded_rect(x, m.tab_y, m.tab_content_w, h, 5, bg);
int close_x = x + m.tab_content_w - m.tab_close_w;
int is = mtk_netsurf_icon_size();
if (chrome_contains(model->mouse_x, model->mouse_y,
close_x, m.tab_y, m.tab_close_w, h))
canvas.fill_rounded_rect(close_x + 2, m.tab_y + (h - 24) / 2,
m.tab_close_w - 4, 24, 4, chrome_colour(0xdc));
int tab_weight = active ? 600 : 500;
int tab_baseline = chrome_text_baseline(m.tab_y, h, tab_weight, font);
chrome_text_clipped(model, x + 11, tab_baseline,
m.tab_content_w - m.tab_close_w - 18,
model->tab_titles[i] != nullptr ? model->tab_titles[i] : "New Tab",
chrome_colour(0x32), tab_weight, font);
mtk_netsurf_icon_draw(MTK_NETSURF_ICON_CLOSE, model->pixels, model->width,
model->height, close_x + (m.tab_close_w - is) / 2,
m.tab_y + (h - is) / 2, hover || active ? 155 : 100);
}
bool add_hover = chrome_contains(model->mouse_x, model->mouse_y,
m.add_x, m.add_y, m.add_w, m.add_h);
canvas.fill_rounded_rect(m.add_x, m.add_y, m.add_w, m.add_h, 4,
add_hover ? chrome_colour(0xdc) : chrome_colour(0xe8));
int plus_size = font + 2;
int plus_w = mtk_netsurf_font_width(0, 500, 0, plus_size, "+", 1);
int plus_baseline = chrome_text_baseline(m.add_y, m.add_h, 500, plus_size);
chrome_text(model, m.add_x + (m.add_w - plus_w) / 2,
plus_baseline, "+", chrome_colour(0x42), 500, plus_size);
chrome_icon_button(canvas, model, m.back_x, m.button_y, m.button_size,
MTK_NETSURF_ICON_BACK, model->can_back != 0);
chrome_icon_button(canvas, model, m.forward_x, m.button_y, m.button_size,
MTK_NETSURF_ICON_FORWARD, model->can_forward != 0);
chrome_icon_button(canvas, model, m.reload_x, m.button_y, m.button_size,
model->loading ? MTK_NETSURF_ICON_STOP : MTK_NETSURF_ICON_RELOAD, true);
chrome_address(canvas, model, m, font);
chrome_icon_button(canvas, model, m.home_x, m.button_y, m.button_size,
MTK_NETSURF_ICON_HOME, true);
bool menu_hover = chrome_contains(model->mouse_x, model->mouse_y,
m.menu_x, m.button_y, m.button_size, m.button_size);
canvas.fill_rounded_rect(m.menu_x, m.button_y, m.button_size, m.button_size, 4,
model->menu_mode || menu_hover ? chrome_colour(0xdc) : chrome_colour(0xe8));
for (int dot = -1; dot <= 1; dot++)
canvas.fill_rounded_rect(m.menu_x + m.button_size / 2 + dot * 5 - 1,
m.button_y + m.button_size / 2 - 1, 3, 3, 1, chrome_colour(0x37));
gui::Rect viewport = {0, m.chrome_h, model->width,
model->height - m.chrome_h};
gui::Rect scrollbar_track = gui::mtk::scrollbar_track_rect(viewport);
gui::Rect scrollbar_thumb = gui::mtk::scrollbar_thumb_rect(scrollbar_track,
model->extent_height, viewport.h, model->scroll_y);
gui::mtk::Theme scrollbar_theme = gui::mtk::make_theme();
gui::mtk::draw_scrollbar(canvas, scrollbar_track, scrollbar_thumb,
model->scrollbar_hover != 0, model->scrollbar_dragging != 0,
scrollbar_theme);
if (model->find_focus) {
int fw = model->scale == 2 ? 520 : 410;
int fh = model->scale == 2 ? 46 : 36;
int fx = model->width - fw - 18, fy = m.chrome_h + 10;
canvas.fill_rounded_rect(fx + 3, fy + 4, fw, fh, 5,
chrome_colour(0xd2));
canvas.fill_rounded_rect(fx, fy, fw, fh, 5,
gui::Color::from_rgb(0xff, 0xff, 0xff));
chrome_text(model, fx + 12, fy + (fh + font - 2) / 2,
"Find", chrome_colour(0x46), 600, font - 2);
canvas.fill_rounded_rect(fx + 58, fy + 6, fw - 178, fh - 12, 3,
chrome_colour(0xf4));
chrome_text_clipped(model, fx + 66, fy + (fh + font - 2) / 2,
fw - 196, model->find_text != nullptr ? model->find_text : "",
chrome_colour(0x28), 500, font - 2);
chrome_text(model, fx + fw - 108, fy + (fh + font - 3) / 2,
"Enter next", chrome_colour(0x78), 500, font - 3);
}
if (model->status != nullptr && model->status[0] != 0 &&
strncmp(model->status, "Done", 4) != 0 &&
strcmp(model->status, "Ready") != 0 &&
strncmp(model->status, "Loading", 7) != 0) {
int status_font = font > 14 ? font - 3 : 12;
int sw = mtk_netsurf_font_width(0, 500, 0, status_font,
model->status, strlen(model->status));
int max_w = model->width * 2 / 3;
if (sw + 20 > max_w) sw = max_w - 20;
int box_w = sw + 20, box_h = status_font + 12;
int sy = model->height - box_h - 8;
canvas.fill_rounded_rect(8, sy, box_w, box_h, 4, chrome_colour(0xc8));
canvas.fill_rounded_rect(9, sy + 1, box_w - 2, box_h - 2, 3,
gui::Color::from_rgb(0xff, 0xff, 0xff));
chrome_text_clipped(model, 18, sy + (box_h + status_font) / 2,
box_w - 20, model->status, chrome_colour(0x4a), 500, status_font);
}
chrome_popup(canvas, model, m);
chrome_address_context(canvas, model);
}
extern "C" int mtk_netsurf_font_width(int family, int weight, unsigned flags,
int size, const char *text, size_t length)
{
gui::TrueTypeFont *font = select_font(family, weight, flags);
if (font == nullptr || text == nullptr) return size * static_cast<int>(length);
gui::GlyphCache *cache = font->get_cache(size > 0 ? size : 1);
int result = 0;
for (size_t offset = 0; offset < length; )
result += glyph_advance(font, cache, next_utf8(text, length, &offset));
return result;
}
extern "C" void mtk_netsurf_font_draw(uint32_t *pixels, int width, int height,
int clip_x0, int clip_y0, int clip_x1, int clip_y1,
int x, int baseline, const char *text, size_t length,
uint32_t colour, int family, int weight, unsigned flags, int size)
{
gui::TrueTypeFont *font = select_font(family, weight, flags);
if (font == nullptr || text == nullptr || pixels == nullptr) return;
int pixel_size = size > 0 ? size : 1;
gui::GlyphCache *cache = font->get_cache(pixel_size);
gui::Color c = gui::Color::from_rgb((colour >> 16) & 255,
(colour >> 8) & 255, colour & 255);
int cursor = x;
for (size_t offset = 0; offset < length; ) {
uint32_t cp = next_utf8(text, length, &offset);
if (cp < 256) {
gui::CachedGlyph *glyph = font->get_glyph(cache, static_cast<int>(cp));
if (glyph != nullptr) {
blend_glyph(pixels, width, height, clip_x0, clip_y0, clip_x1, clip_y1,
cursor, baseline, glyph->bitmap, glyph->width, glyph->height,
glyph->xoff, glyph->yoff, c);
cursor += glyph->advance;
}
continue;
}
int x0, y0, x1, y1;
stbtt_GetCodepointBitmapBox(&font->info, static_cast<int>(cp),
cache->scale, cache->scale, &x0, &y0, &x1, &y1);
int glyph_width = x1 - x0, glyph_height = y1 - y0;
uint8_t *bitmap = nullptr;
if (glyph_width > 0 && glyph_height > 0 &&
(size_t)glyph_width * glyph_height <= 1024u * 1024u) {
bitmap = static_cast<uint8_t *>(malloc((size_t)glyph_width * glyph_height));
if (bitmap != nullptr)
stbtt_MakeCodepointBitmap(&font->info, bitmap, glyph_width, glyph_height,
glyph_width, cache->scale, cache->scale, static_cast<int>(cp));
}
blend_glyph(pixels, width, height, clip_x0, clip_y0, clip_x1, clip_y1,
cursor, baseline, bitmap, glyph_width, glyph_height, x0, y0, c);
free(bitmap);
cursor += glyph_advance(font, cache, cp);
}
}
extern "C" int mtk_netsurf_https_fetch(const char *host, uint32_t ip,
uint16_t port, const char *request, size_t request_len,
char *response, size_t response_size)
{
static tls::TrustAnchors anchors = {};
static bool anchors_attempted;
if (!anchors_attempted) {
anchors = tls::load_trust_anchors();
anchors_attempted = true;
}
if (anchors.count == 0 || request_len > 0x7fffffff ||
response_size > 0x7fffffff) return -1;
return tls::https_fetch(host, ip, port, request,
static_cast<int>(request_len), anchors, response,
static_cast<int>(response_size));
}
extern "C" int mtk_netsurf_open_file(char *path, size_t path_size)
{
if (path == nullptr || path_size == 0 || path_size > 0x7fffffff) return 0;
char message[128] = {};
path[0] = 0;
return gui::dialogs::open_file("Select File", "", path,
static_cast<int>(path_size), message, sizeof(message)) ? 1 : 0;
}