15 std::filesystem::path
path, std::string detail)
17 return {code, std::move(
path), std::move(detail)};
23 return {
reinterpret_cast<const char*
>(
value.data()),
value.size()};
37 std::u16string_view
text,
const std::filesystem::path& fontPath)
40 result.reserve(
text.size() * 3U);
44 result.push_back(
static_cast<char>(
codePoint));
47 result.push_back(
static_cast<char>(0xC0U | (
codePoint >> 6U)));
48 result.push_back(
static_cast<char>(0x80U | (
codePoint & 0x3FU)));
52 result.push_back(
static_cast<char>(0xE0U | (
codePoint >> 12U)));
53 result.push_back(
static_cast<char>(0x80U | ((
codePoint >> 6U) & 0x3FU)));
54 result.push_back(
static_cast<char>(0x80U | (
codePoint & 0x3FU)));
58 result.push_back(
static_cast<char>(0xF0U | (
codePoint >> 18U)));
59 result.push_back(
static_cast<char>(0x80U | ((
codePoint >> 12U) & 0x3FU)));
60 result.push_back(
static_cast<char>(0x80U | ((
codePoint >> 6U) & 0x3FU)));
61 result.push_back(
static_cast<char>(0x80U | (
codePoint & 0x3FU)));
72 fontPath,
"truncated UTF-16 surrogate pair"));
73 const auto low =
static_cast<std::uint32_t
>(
text[++
index]);
76 fontPath,
"invalid UTF-16 surrogate pair"));
82 fontPath,
"isolated UTF-16 low surrogate"));
89 const data::LegacyBitmapRGBA8&
source, ClipRect
clip)
91 data::LegacyBitmapRGBA8 result;
98 result.pixels.resize(
static_cast<std::size_t
>(result.width) *
103 for (std::uint32_t
y = 0;
y < result.height; ++
y)
107 (
static_cast<std::size_t
>(
clip.x) * 4U);
108 std::memcpy(result.pixels.data() +
116 std::u8string_view
right)
noexcept
118 if (
left.size() !=
right.size())
return false;
119 for (std::size_t
i = 0;
i <
left.size(); ++
i)
125 if (
a !=
b)
return false;
138 if constexpr (
sizeof(
wchar_t) == 2)
140 std::u16string
utf16;
143 utf16.push_back(
static_cast<char16_t>(
value));
149 result.reserve(
text.size() * 4U);
153 result.push_back(
static_cast<char>(
codePoint));
156 result.push_back(
static_cast<char>(0xC0U | (
codePoint >> 6U)));
157 result.push_back(
static_cast<char>(0x80U | (
codePoint & 0x3FU)));
161 result.push_back(
static_cast<char>(0xE0U | (
codePoint >> 12U)));
162 result.push_back(
static_cast<char>(0x80U | ((
codePoint >> 6U) & 0x3FU)));
163 result.push_back(
static_cast<char>(0x80U | (
codePoint & 0x3FU)));
167 result.push_back(
static_cast<char>(0xF0U | (
codePoint >> 18U)));
168 result.push_back(
static_cast<char>(0x80U | ((
codePoint >> 12U) & 0x3FU)));
169 result.push_back(
static_cast<char>(0x80U | ((
codePoint >> 6U) & 0x3FU)));
170 result.push_back(
static_cast<char>(0x80U | (
codePoint & 0x3FU)));
179 "invalid wide-character Unicode code point"));
193 clearPresentationFaces();
200 return ttfInitialized_ && font_ !=
nullptr;
208 std::expected<void, Error> Runtime::reopen()
210 if (!ttfInitialized_)
214 "no font path has been selected"));
221 const bool configured =
224 static_cast<float>(settings_.
size)) &&
235 clearPresentationFaces();
242 const std::filesystem::path&
path, std::string familyName)
247 if (
auto result = reopen(); !result)
259 "font size must be between 1 and 512 points"));
262 settings_.
size = points;
280 settings_.
weight = std::clamp(weight, 0, 1000);
304 clearPresentationFaces();
316 "font setting slot is outside the retail 0..9 range"));
317 slots_[
slot] = settings_;
323 return slot < SlotCount && slots_[
slot] ? &*slots_[
slot] :
nullptr;
330 "font setting slot is unavailable"));
333 settings_ = *slots_[
slot];
336 if (
auto result = reopen(); !result)
362 settings_.
fontPath,
"font runtime is not ready"));
376 if (!
utf8)
return std::unexpected(
utf8.error());
385 settings_.
fontPath,
"word-wrap width must be positive"));
386 if (
utf8.empty())
return std::vector<std::string>{{}};
393 (
static_cast<unsigned char>(
text[
offset]) & 0xC0U) == 0x80U)
399 std::replace(
value.begin(),
value.end(),
'_',
' ');
403 std::vector<std::string>
lines;
404 std::string remaining(
utf8);
408 if (!
whole)
return std::unexpected(
whole.error());
413 lines.push_back(std::move(remaining));
417 std::size_t
cut = std::string::npos;
418 std::size_t
resume = std::string::npos;
419 std::size_t
search = remaining.size();
422 const auto space = remaining.rfind(
' ',
search - 1);
423 if (
space == std::string::npos)
break;
452 end <= remaining.size() && end != 0;)
455 std::string_view(remaining).
substr(0, end));
461 if (end == remaining.size())
break;
469 remaining.erase(0,
resume);
470 if (remaining.empty())
472 lines.emplace_back();
479 void Runtime::clearPresentationFaces()
noexcept
481 for (
auto&
face : presentationFaces_)
face.
font.reset();
482 presentationClock_ = 0;
487 return std::count_if(presentationFaces_.begin(), presentationFaces_.end(),
488 [](
const auto&
face) { return bool(face.font); });
492 std::string_view
utf8, std::uint32_t
colorRef,
int size,
int weight,
493 bool italic,
bool underline,
bool strikeOut,
bool antialiased)
497 "font size must be between 1 and 512 points"));
500 settings_.
fontPath,
"font runtime is not ready"));
502 weight = std::clamp(weight, 0, 1000);
503 for (
auto&
face : presentationFaces_)
504 if (
face.font &&
face.size == size &&
face.weight == weight &&
505 face.italic == italic &&
face.underline == underline &&
face.strikeOut == strikeOut)
507 face.lastUsed = ++presentationClock_;
519 auto slot = std::find_if(presentationFaces_.begin(), presentationFaces_.end(),
520 [](
const auto&
face) { return !face.font; });
521 if (
slot == presentationFaces_.end())
522 slot = std::min_element(presentationFaces_.begin(), presentationFaces_.end(),
523 [](
const auto&
a,
const auto&
b) { return a.lastUsed < b.lastUsed; });
524 *
slot = PresentationFace{std::move(
clone), size, weight, italic, underline, strikeOut, ++presentationClock_};
534 std::expected<data::LegacyBitmapRGBA8, Error> Runtime::renderFace(
540 settings_.
fontPath,
"font runtime is not ready"));
542 const std::uint8_t
red =
static_cast<std::uint8_t
>(
colorRef & 0xFFU);
543 const std::uint8_t
green =
static_cast<std::uint8_t
>((
colorRef >> 8U) & 0xFFU);
544 const std::uint8_t
blue =
static_cast<std::uint8_t
>((
colorRef >> 16U) & 0xFFU);
566 settings_.
fontPath,
"SDL returned an empty text surface"));
569 data::LegacyBitmapRGBA8 result;
570 result.width =
static_cast<std::uint32_t
>(
converted->w + 1);
571 result.height =
static_cast<std::uint32_t
>(
converted->h);
572 result.pixels.assign(
static_cast<std::size_t
>(result.width) *
573 result.height * 4U, 0
U);
576 const auto* pixels =
static_cast<const std::uint8_t*
>(
converted->pixels);
579 const auto*
source = pixels +
static_cast<std::ptrdiff_t
>(
y) *
converted->pitch;
581 static_cast<std::size_t
>(
y) * result.width * 4U;
592 if (!
utf8)
return std::unexpected(
utf8.error());
600 if (!
image)
return std::unexpected(
image.error());
608 if (!
image)
return std::unexpected(
image.error());
616 if (
utf8.empty())
return {};
623 const std::int64_t
sourceX64 =
x < 0 ? -
static_cast<std::int64_t
>(
x) : 0;
624 const std::int64_t
sourceY64 =
y < 0 ? -
static_cast<std::int64_t
>(
y) : 0;
628 const auto destinationX =
static_cast<std::uint32_t
>(std::max(
x, 0));
629 const auto destinationY =
static_cast<std::uint32_t
>(std::max(
y, 0));
646 if (
pixelCount > std::numeric_limits<std::size_t>::max() / 4U ||
649 settings_.
fontPath,
"invalid destination RGBA8 surface"));
663 return static_cast<std::uint8_t
>(std::min(
702 if (!
utf8)
return std::unexpected(
utf8.error());
706 std::expected<std::filesystem::path, Error>
709 constexpr std::u8string_view
wanted =
u8"Arial.ttf";
712 std::error_code
error;
720 if (!
error && std::filesystem::is_regular_file(status))
726 "Arial.ttf was not found in the explicit retail font roots"));
std::vector< std::uint8_t > data
std::atomic< float > left
std::atomic< float > right
std::shared_ptr< const data::HmdTextureImage > source
std::expected< void, Error > setFont(const std::filesystem::path &path, std::string familyName={})
void resetCharacteristics()
std::size_t presentationFaceCount() const noexcept
const Settings * savedSettings(std::size_t slot) const noexcept
std::expected< Metrics, Error > measure(std::string_view utf8) const
std::expected< data::LegacyBitmapRGBA8, Error > renderPresentation(std::string_view utf8, std::uint32_t colorRef, int size, int weight, bool italic, bool underline=false, bool strikeOut=false, bool antialiased=true)
const Settings & settings() const noexcept
std::expected< data::LegacyBitmapRGBA8, Error > render(std::string_view utf8, std::uint32_t colorRef, bool antialiased=false) const
static constexpr std::size_t SlotCount
void setWeight(int weight)
void setUnderline(bool enabled)
std::expected< void, Error > setSize(int points)
std::expected< void, Error > saveSettings(std::size_t slot)
std::expected< data::LegacyBitmapRGBA8, Error > renderClipped(std::string_view utf8, std::uint32_t colorRef, ClipRect clip, bool antialiased=false) const
bool ready() const noexcept
std::expected< std::vector< std::string >, Error > wrap(std::string_view utf8, int width) const
std::expected< void, Error > restoreSettings(std::size_t slot)
void setStrikeOut(bool enabled)
void setItalic(bool enabled)
std::expected< void, Error > blitText(data::LegacyBitmapRGBA8 &destination, std::string_view utf8, int x, int y, std::uint32_t colorRef) const
std::expected< std::string, Error > transcodeUtf8(std::u16string_view text)
@ SurfaceConversionFailed
std::expected< std::filesystem::path, Error > resolveRetailArial(std::span< const std::filesystem::path > roots)
std::filesystem::path fontPath