71 lines
3 KiB
C++
71 lines
3 KiB
C++
//SPDX-License-Identifier: LGPL-3.0-only
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
module;
|
|
#include "../lib/stb_truetype.h"
|
|
export module Crafter.Graphics:Font;
|
|
import std;
|
|
|
|
namespace Crafter {
|
|
// Decode the UTF-8 codepoint at `text[i]` and advance `i` past it.
|
|
// Returns 0 once `i` reaches the end. Malformed sequences yield U+FFFD
|
|
// and the index is moved past one byte to keep iteration finite.
|
|
export inline std::uint32_t DecodeUtf8(std::string_view text, std::size_t& i) {
|
|
if (i >= text.size()) return 0;
|
|
std::uint8_t b0 = static_cast<std::uint8_t>(text[i]);
|
|
|
|
if (b0 < 0x80) { ++i; return b0; }
|
|
|
|
int extra;
|
|
std::uint32_t cp;
|
|
if ((b0 & 0xE0) == 0xC0) { extra = 1; cp = b0 & 0x1F; }
|
|
else if ((b0 & 0xF0) == 0xE0) { extra = 2; cp = b0 & 0x0F; }
|
|
else if ((b0 & 0xF8) == 0xF0) { extra = 3; cp = b0 & 0x07; }
|
|
else { ++i; return 0xFFFD; }
|
|
|
|
++i;
|
|
for (int k = 0; k < extra; ++k) {
|
|
if (i >= text.size()) return 0xFFFD;
|
|
std::uint8_t b = static_cast<std::uint8_t>(text[i]);
|
|
if ((b & 0xC0) != 0x80) return 0xFFFD;
|
|
cp = (cp << 6) | (b & 0x3Fu);
|
|
++i;
|
|
}
|
|
return cp;
|
|
}
|
|
|
|
export class Font {
|
|
public:
|
|
std::vector<unsigned char> fontBuffer;
|
|
std::int_fast32_t ascent;
|
|
std::int_fast32_t descent;
|
|
std::int_fast32_t lineGap;
|
|
stbtt_fontinfo font;
|
|
Font(const std::filesystem::path& font);
|
|
std::uint32_t GetLineWidth(const std::string_view text, float size);
|
|
// Map a target advance (px from the line start) to the nearest UTF-8
|
|
// codepoint boundary, returning its BYTE offset into `text`. A single
|
|
// left-to-right pass accumulates per-glyph advances (O(n) metric
|
|
// lookups), instead of re-walking the prefix for every boundary as a
|
|
// naive caller would. Cumulative advance is monotonic (advances are
|
|
// non-negative), so the scan stops at the first boundary past the
|
|
// closest one. Returned offsets land on codepoint boundaries, which are
|
|
// exactly the valid positions for a byte-based text cursor.
|
|
std::size_t NearestCursorByte(std::string_view text, float size, float target);
|
|
float LineHeight(float size);
|
|
float AscentPx(float size);
|
|
float ScaleForSize(float size);
|
|
|
|
// Horizontal advance for `cp` in unscaled font units, cached per Font.
|
|
// Stored in font units (not pixels) so it can be rescaled for any
|
|
// `size`; Glyph::advance in the FontAtlas is baked at kBaseSize and is
|
|
// wrong at other sizes, so it cannot be reused here. ASCII lands in a
|
|
// flat array (the common path for caret hit-testing, which rescans the
|
|
// whole field per character), everything else in the map.
|
|
std::int32_t AdvanceUnits(std::uint32_t cp);
|
|
|
|
private:
|
|
std::array<std::int32_t, 128> asciiAdvance_; // -1 = uncached
|
|
std::unordered_map<std::uint32_t, std::int32_t> advanceUnits_;
|
|
};
|
|
}
|