Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
254 changes: 254 additions & 0 deletions Apps/Maps/CHANGELOG.md

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions Apps/Maps/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# tactility-cmakelists-version: 1
cmake_minimum_required(VERSION 3.20)

if (NOT DEFINED ENV{TACTILITY_SDK_PATH})
message(FATAL_ERROR "TACTILITY_SDK_PATH environment variable is not set")
endif()

get_filename_component(TACTILITY_SDK_PATH "$ENV{TACTILITY_SDK_PATH}" ABSOLUTE)
include("${TACTILITY_SDK_PATH}/TactilitySDK.cmake")

tactility_project_pre(maps)
project(maps)
tactility_project_post(maps)
674 changes: 674 additions & 0 deletions Apps/Maps/LICENSE

Large diffs are not rendered by default.

298 changes: 298 additions & 0 deletions Apps/Maps/README.md

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions Apps/Maps/THIRD_PARTY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Third-party code

| Component | Where | Licence | Notes |
|---|---|---|---|
| miniz 3.0.2 (Rich Geldreich, RAD Game Tools, Valve Software) | `host/third_party/miniz/` | MIT, see `host/third_party/miniz/LICENSE` | PC build only: inflate and PNG writing for the host tools and tests. The app uses the `tinfl_decompress` exported by Tactility. |
| Adwaita Sans (GNOME [adwaita-fonts](https://github.com/GNOME/adwaita-fonts), derived from Inter by the Inter Project Authors) | `assets/fonts/LabelSans.ttf` | SIL Open Font License 1.1, https://raw.githubusercontent.com/GNOME/adwaita-fonts/refs/heads/main/LICENSE | A subset (Basic Latin, Latin-1, Latin Extended-A, U+0218-021B, U+0259, Greek U+0386-03CE, Cyrillic U+0400-045F and U+0490-0491, plus the characters listed in `tools/label_codepoints.txt`, which map names use; weight 550, no hinting or layout tables) made by `host/make_label_font.py` from the font in Tactility's repository. The app makes its map label fonts from it on the device. The name table of the file names the licence. |

| [Maki](https://github.com/mapbox/maki) icons (Mapbox) | `main/Source/render/PoiIconData.h` | CC0 1.0 Universal | 89 icons turned into 15 x 15 alpha masks by `host/make_poi_icons.py`. |

`main/Source/app/RootCertificates.h` holds the public root certificates of Google Trust Services (GTS Root R1, R4)
and Let's Encrypt (ISRG Root X1, X2), as published at pki.goog and letsencrypt.org, used to verify the HTTPS
connection to OpenFreeMap.

The rasteriser in `main/Source/render/Raster.cpp` is written for this project. It uses the published signed-area
coverage technique (as described for font-rs and stb_truetype), no code was copied.

# Map data

Test fixtures in `tests/fixtures/` are vector tiles from [OpenFreeMap](https://openfreemap.org/), in the
OpenMapTiles schema. © OpenMapTiles © OpenStreetMap contributors. OpenStreetMap data is available under the
Open Database License (ODbL).
Binary file added Apps/Maps/assets/fonts/LabelSans.ttf
Binary file not shown.
Binary file added Apps/Maps/assets/regions.bin
Binary file not shown.
8 changes: 8 additions & 0 deletions Apps/Maps/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
include("$ENV{TACTILITY_SDK_PATH}/TactilitySDK.cmake")

file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
tactility_component_register(
SRCS ${SOURCE_FILES}
REQUIRES graphics-module binfont-module location-module http-module esp_http_client esp_rom mbedtls
)
target_compile_options(${COMPONENT_LIB} PRIVATE -O2 -Wno-format-truncation)
183 changes: 183 additions & 0 deletions Apps/Maps/main/Source/app/BinFontText.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "BinFontText.h"

#include "../platform/Platform.h"

#include <binfont/binfont.h>
Comment thread
KenVanHoeylandt marked this conversation as resolved.
Resolved
#include <binfont/render.h>
#include <graphics/pixel_buffer.h>
#include <lvgl.h>
#include <lvgl/fonts.h>

#include <cstdio>
#include <cstring>

namespace maps {

namespace {

struct FontSearch {
const char* prefix;
char name[96];
};

bool findFont(const char* name, bool isDirectory, void* context) {
auto* search = static_cast<FontSearch*>(context);
if (!isDirectory && strncmp(name, search->prefix, strlen(search->prefix)) == 0) {
snprintf(search->name, sizeof(search->name), "%s", name);
return false;
}
return true;
}

/** Decodes one UTF-8 character and advances the pointer. */
uint32_t nextCodepoint(const char*& text) {
const auto first = static_cast<uint8_t>(*text++);
if (first < 0x80) {
return first;
}
if ((first & 0xE0) == 0xC0 && *text != '\0') {
return ((first & 0x1Fu) << 6) | (static_cast<uint8_t>(*text++) & 0x3Fu);
}
// Longer sequences are skipped and shown as '?'
while ((static_cast<uint8_t>(*text) & 0xC0) == 0x80) {
text++;
}
return '?';
}

} // namespace

bool BinFontText::open(const char* folder, const char* const* prefixes, int prefixCount) {
for (int i = 0; i < prefixCount; i++) {
FontSearch search { prefixes[i], "" };
listDirectory(folder, findFont, &search);
if (search.name[0] == '\0') {
continue;
}
char path[260];
snprintf(path, sizeof(path), "%s/%s", folder, search.name);
if (binfont_open_file(path, &font) == ERROR_NONE) {
owned = true;
BinFontMetrics metrics;
binfont_get_metrics(font, &metrics);
height = metrics.line_height;
ascentPixels = metrics.ascent;
logInfo("Font %s, line height %d", search.name, height);
return true;
}
font = nullptr;
}
return false;
}

bool BinFontText::openFile(const char* path) {
close();
if (binfont_open_file(path, &font) != ERROR_NONE) {
font = nullptr;
return false;
}
owned = true;
BinFontMetrics metrics;
binfont_get_metrics(font, &metrics);
height = metrics.line_height;
ascentPixels = metrics.ascent;
logInfo("Font file %s, line height %d", path, height);
return true;
}

bool BinFontText::borrow(int lvglFontSize) {
const lv_font_t* lvFont = lvgl_get_text_font(static_cast<LvglFontSize>(lvglFontSize));
// Tactility's fonts come from lvgl_binfont_create(), which keeps the BinFont in dsc. Without a registered font
// LVGL's built-in one comes back, whose dsc is something else.
if (lvFont == nullptr || lvFont->dsc == nullptr || lvFont->get_glyph_dsc == lv_font_get_glyph_dsc_fmt_txt) {
return false;
}
font = static_cast<BinFont*>(const_cast<void*>(lvFont->dsc));
owned = false;
BinFontMetrics metrics;
binfont_get_metrics(font, &metrics);
height = metrics.line_height;
ascentPixels = metrics.ascent;
logInfo("Font from the system, size %d, line height %d", lvglFontSize, height);
return true;
}

void BinFontText::close() {
if (target != nullptr) {
pixel_buffer_free(target);
target = nullptr;
targetPixels = nullptr;
}
if (font != nullptr && owned) {
binfont_close(font);
}
font = nullptr;
owned = false;
}

int BinFontText::textWidth(const char* text) {
if (font == nullptr) {
return 0;
}
// The same advances and kerning binfont_draw_text_rgb565() applies
int32_t widthX16 = 0;
uint32_t previous = 0;
bool hasPrevious = false;
while (*text != '\0') {
BinFontGlyph glyph;
if (!binfont_get_glyph(font, nextCodepoint(text), &glyph)) {
continue;
}
if (hasPrevious) {
widthX16 += binfont_get_kerning_x16(font, previous, glyph.glyph_id);
}
widthX16 += static_cast<int32_t>(glyph.advance_x16);
previous = glyph.glyph_id;
hasPrevious = true;
}
return (widthX16 + 8) >> 4;
}

void BinFontText::drawText(Canvas& canvas, int x, int y, const char* text, uint16_t color) {
if (font == nullptr) {
return;
}
if (target == nullptr || targetPixels != canvas.pixels) {
if (target != nullptr) {
pixel_buffer_free(target);
}
target = pixel_buffer_wrap(canvas.pixels, DISPLAY_COLOR_FORMAT_RGB565, canvas.width, canvas.height, 0);
targetPixels = target != nullptr ? canvas.pixels : nullptr;
if (target == nullptr) {
return;
}
}
binfont_draw_text_rgb565(target, x, y + ascentPixels, font, text, color, PIXEL_BUFFER_CONVERSION_AVERAGE);
}

bool BinFontText::glyph(uint32_t codepoint, LabelGlyph& out, bool withMask) {
BinFontGlyph found;
if (font == nullptr || !binfont_get_glyph(font, codepoint, &found)) {
return false;
}
out.id = found.glyph_id;
out.advanceX16 = static_cast<int>(found.advance_x16);
out.left = found.x;
// binfont gives the box's bottom above the baseline, labels want its top below it
out.top = -(found.y + found.height);
out.width = found.width;
out.height = found.height;
out.alpha = nullptr;
if (withMask && found.width > 0 && found.height > 0 && found.width * found.height <= MASK_BYTES &&
binfont_get_glyph_bitmap(font, &found, mask, found.width) == ERROR_NONE) {
out.alpha = mask;
}
return true;
}

int BinFontText::kerningX16(uint32_t leftId, uint32_t rightId) {
return font != nullptr ? binfont_get_kerning_x16(font, leftId, rightId) : 0;
}

} // namespace maps
58 changes: 58 additions & 0 deletions Apps/Maps/main/Source/app/BinFontText.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once

#include "../render/LabelLayout.h"
#include "../ui/Canvas.h"

#include <cstdint>

struct BinFont;
struct PixelBuffer;

namespace maps {

/**
* Text in one of Tactility's cached fonts (<data>/cache/fonts), drawn with the binfont-module. As a LabelFont it gives
* glyph masks for map labels.
*/
class BinFontText final : public Font, public LabelFont {
public:
~BinFontText() override { close(); }

/**
* Opens the first font whose file name starts with one of the prefixes. Call it on the I/O task: the binfont-module
* loads the whole file into PSRAM, so drawing never reads the card.
*/
bool open(const char* folder, const char* const* prefixes, int prefixCount);
/** Opens one font file (read whole into PSRAM, so on the I/O task). */
bool openFile(const char* path);
/**
* Uses the text font Tactility's UI uses (an LvglFontSize), without a copy. Firmware builds with the fonts inside
* have no font files on the card, so this comes first. Fails when that font is not a BinFont.
*/
bool borrow(int lvglFontSize);
void close();
bool isOpen() const { return font != nullptr; }

int lineHeight() const override { return font != nullptr ? height : 10; }
int ascent() const override { return ascentPixels; }
bool glyph(uint32_t codepoint, LabelGlyph& out, bool withMask) override;
int kerningX16(uint32_t leftId, uint32_t rightId) override;
int textWidth(const char* text) override;
void drawText(Canvas& canvas, int x, int y, const char* text, uint16_t color) override;

private:
BinFont* font = nullptr;
/** Borrowed fonts belong to Tactility and are not closed. */
bool owned = false;
int height = 0;
int ascentPixels = 0;
/** The mask of the last glyph asked for with withMask; labels use fonts of at most about 30 px. */
static constexpr int MASK_BYTES = 48 * 48;
uint8_t mask[MASK_BYTES] {};
/** The canvas the text was last drawn on, wrapped once as a PixelBuffer. */
PixelBuffer* target = nullptr;
uint16_t* targetPixels = nullptr;
};

} // namespace maps
Loading
Loading