mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
sceFontCharactersRefersTextCodes fills a 64-byte FontTextCodes with the order and code of a text character and keeps the current, start and term characters at +0x28, +0x30 and +0x38. The traversal follows the PS4 11.00 libSceFont.sprx disassembly posted in the review of #4050, which implemented the same rules: Refers never compares the character with the term and returns null for a clustered or synthetic character, sceFontTextCodesStepNext returns null at the term after visiting it, sceFontTextCodesStepBack returns null at the start character, and both steps skip synthetic characters. Null from StepNext at the end of the chain and from StepBack at the head of the chain is assumed and not covered by the report. The signatures follow Orbital's IDA type database and SharpProspero. A null cursor or output returns null as in KytyPS5; a null text character throws. guest_font_tests gets configure_windows_unwind so the expected exception reaches the test on Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
694 lines
29 KiB
C++
694 lines
29 KiB
C++
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
#include <cstring>
|
|
#include <new>
|
|
#include <stdexcept>
|
|
#include <vector>
|
|
|
|
#include "prx/libSceFont/include/FontInternal.hpp"
|
|
|
|
namespace {
|
|
|
|
using namespace Font;
|
|
|
|
constexpr std::uint16_t TEXT_SOURCE_MAGIC = 0x0F04;
|
|
constexpr std::uint16_t FONT_STRING_MAGIC = 0x0F05;
|
|
constexpr std::uint16_t FONT_WRITING_MAGIC = 0x0F06;
|
|
constexpr std::uint16_t CREATE_STRING_DETAIL_ID = 0x0FD4;
|
|
constexpr std::uint32_t WRITING_FORM_HORIZONTAL = 0x10;
|
|
constexpr std::uint32_t WRITING_FORM_HORIZONTAL_LTR = 0x12;
|
|
constexpr std::int32_t PARSER_RESULT_ERROR = -1;
|
|
constexpr std::int32_t PARSER_RESULT_TERMINATE = 0;
|
|
constexpr std::int32_t PARSER_RESULT_FONT_CODE = 1;
|
|
constexpr std::size_t MAX_PARSED_CHARACTERS = 4096;
|
|
constexpr std::int32_t WRITING_MASK_NONE = 0;
|
|
constexpr std::int32_t WRITING_MASK_FORMAT_CHARACTERS = 1;
|
|
constexpr std::uint64_t CHARACTER_FLAG_FORMAT = 1ull << 42;
|
|
|
|
struct CharacterStorage {
|
|
std::atomic<std::uint32_t> refCount{1};
|
|
std::vector<FontTextCharacter> characters;
|
|
};
|
|
|
|
struct FontStringData {
|
|
std::uint16_t magic = FONT_STRING_MAGIC;
|
|
std::uint8_t flags = 0;
|
|
std::uint8_t reserved0 = 0;
|
|
std::uint32_t writingForm = WRITING_FORM_HORIZONTAL;
|
|
const FontMemory* memory = nullptr;
|
|
FontTextSource* source = nullptr;
|
|
FontHandle defaultFont = nullptr;
|
|
std::uint32_t terminateCode = 0;
|
|
void* terminateOrder = nullptr;
|
|
std::uint32_t textCount = 0;
|
|
std::uint32_t renderCount = 0;
|
|
CharacterStorage* storage = nullptr;
|
|
};
|
|
|
|
struct FontWritingData {
|
|
std::uint16_t magic = FONT_WRITING_MAGIC;
|
|
std::uint16_t reserved0 = 0;
|
|
std::uint32_t writingForm = WRITING_FORM_HORIZONTAL;
|
|
FontString string = nullptr;
|
|
CharacterStorage* storage = nullptr;
|
|
FontHandle defaultFont = nullptr;
|
|
std::size_t nextIndex = 0;
|
|
std::size_t currentIndex = 0;
|
|
float penX = 0.0f;
|
|
float penY = 0.0f;
|
|
bool hasAggregateMetrics = false;
|
|
bool hasCurrentStep = false;
|
|
bool maskFormatCharacters = true;
|
|
bool ownsStorage = false;
|
|
bool hasRawGlyphMetrics = false;
|
|
FontWritingStep step{};
|
|
FontWritingMetrics metrics{};
|
|
FontGlyphMetrics rawGlyphMetrics{};
|
|
FontWritingLetterStep letterStep{};
|
|
};
|
|
|
|
static_assert(sizeof(FontWritingData) <= sizeof(FontWriting));
|
|
|
|
FontStringData* GetStringData(FontString fontString) {
|
|
auto* data = reinterpret_cast<FontStringData*>(fontString);
|
|
return data && data->magic == FONT_STRING_MAGIC ? data : nullptr;
|
|
}
|
|
|
|
FontWritingData* GetWritingData(FontWriting* fontWriting) {
|
|
if (!fontWriting) return nullptr;
|
|
auto* data = reinterpret_cast<FontWritingData*>(fontWriting->systemUse);
|
|
return data->magic == FONT_WRITING_MAGIC ? data : nullptr;
|
|
}
|
|
|
|
void ReleaseStorage(CharacterStorage*& storage) {
|
|
if (!storage) return;
|
|
if (storage->refCount.fetch_sub(1, std::memory_order_acq_rel) == 1) delete storage;
|
|
storage = nullptr;
|
|
}
|
|
|
|
void ReleaseWritingStorage(FontWritingData& writing) {
|
|
if (!writing.ownsStorage) return;
|
|
ReleaseStorage(writing.storage);
|
|
writing.ownsStorage = false;
|
|
}
|
|
|
|
std::uint16_t TextSourceMagic(const FontTextSource* source) {
|
|
return static_cast<std::uint16_t>(source->systemUse0 & 0xFFFFu);
|
|
}
|
|
|
|
bool IsWhitespaceCode(std::uint32_t code) {
|
|
switch (code) {
|
|
case 0x0009: case 0x000A: case 0x000B: case 0x000C: case 0x000D: case 0x0020: case 0x0085:
|
|
case 0x00A0: case 0x1680: case 0x2028: case 0x2029: case 0x202F: case 0x205F: case 0x3000:
|
|
return true;
|
|
default:
|
|
return code >= 0x2000 && code <= 0x200A;
|
|
}
|
|
}
|
|
|
|
bool IsFormatCode(std::uint32_t code) {
|
|
switch (code) {
|
|
case 0x00AD: case 0x061C: case 0x200B: case 0x200C: case 0x200D: case 0x200E: case 0x200F:
|
|
case 0x2060: case 0x2061: case 0x2062: case 0x2063: case 0x2064: case 0x2066: case 0x2067:
|
|
case 0x2068: case 0x2069: case 0xFE0E: case 0xFE0F: case 0xFEFF:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
FontTextCharacter MakeCharacter(FontHandle font, std::uint32_t code, void* textOrder) {
|
|
FontTextCharacter character{};
|
|
character.textOrder = textOrder;
|
|
character.font = font;
|
|
character.characterCode = code;
|
|
character.clusterSpan = 1;
|
|
character.flags = static_cast<std::uint64_t>(IsWhitespaceCode(code) ? 0x0E : 0) << 8;
|
|
if (IsFormatCode(code)) character.flags |= CHARACTER_FLAG_FORMAT;
|
|
return character;
|
|
}
|
|
|
|
void LinkCharacters(std::vector<FontTextCharacter>& characters) {
|
|
for (std::size_t i = 0; i < characters.size(); ++i) {
|
|
characters[i].prev = i == 0 ? nullptr : &characters[i - 1];
|
|
characters[i].next = i + 1 < characters.size() ? &characters[i + 1] : nullptr;
|
|
}
|
|
}
|
|
|
|
FontTextCharacter* TextCharacters(FontStringData* data) {
|
|
return data && data->storage && !data->storage->characters.empty() ? data->storage->characters.data() : nullptr;
|
|
}
|
|
|
|
std::uint32_t LevelOf(const FontTextCharacter* character) {
|
|
return static_cast<std::uint32_t>((character->flags >> 24) & 0xFFu);
|
|
}
|
|
|
|
void FillWritingMetricsFromGlyph(FontWritingData& writing, const FontGlyphMetrics& glyphMetrics) {
|
|
writing.rawGlyphMetrics = glyphMetrics;
|
|
writing.hasRawGlyphMetrics = true;
|
|
writing.step.advanceX = glyphMetrics.Horizontal.advance;
|
|
writing.step.advanceY = glyphMetrics.Vertical.advance;
|
|
writing.step.GlyphMetrics = glyphMetrics;
|
|
writing.step.Positioning.x = writing.step.x;
|
|
writing.step.Positioning.y = writing.step.y;
|
|
writing.step.GlyphMetrics.width = writing.step.x;
|
|
writing.step.GlyphMetrics.height = writing.step.y;
|
|
}
|
|
|
|
bool PopulateWritingStep(FontWritingData& writing, std::size_t index) {
|
|
if (!writing.storage || index >= writing.storage->characters.size()) return false;
|
|
const FontTextCharacter& character = writing.storage->characters[index];
|
|
const FontHandle font = character.font ? character.font : writing.defaultFont;
|
|
writing.step.x = writing.penX;
|
|
writing.step.y = writing.penY;
|
|
writing.step.advanceX = 0.0f;
|
|
writing.step.advanceY = 0.0f;
|
|
writing.step.font = font;
|
|
writing.step.Profile = {};
|
|
writing.step.Profile.characterCount = 1;
|
|
writing.step.glyphCode = character.characterCode;
|
|
writing.step.Positioning.x = 0.0f;
|
|
writing.step.Positioning.y = 0.0f;
|
|
writing.step.GlyphMetrics = {};
|
|
writing.rawGlyphMetrics = {};
|
|
writing.hasRawGlyphMetrics = false;
|
|
if ((character.flags & CHARACTER_FLAG_FORMAT) != 0 && writing.maskFormatCharacters) writing.step.Profile.invisibleGlyph = 1;
|
|
if (font && character.characterCode != 0) {
|
|
FontGlyphMetrics glyphMetrics{};
|
|
int rc = GetCharGlyphMetrics(font, character.characterCode, &glyphMetrics, true);
|
|
if (rc != SCE_FONT_OK) rc = GetCharGlyphMetrics(font, character.characterCode, &glyphMetrics, false);
|
|
if (rc == SCE_FONT_OK) FillWritingMetricsFromGlyph(writing, glyphMetrics);
|
|
}
|
|
writing.penX += writing.step.advanceX;
|
|
writing.penY += writing.step.advanceY;
|
|
return true;
|
|
}
|
|
|
|
void AccumulateWritingMetrics(FontWritingData& writing) {
|
|
const FontWritingMetrics previous = writing.metrics;
|
|
const FontGlyphMetrics& glyphMetrics = writing.hasRawGlyphMetrics ? writing.rawGlyphMetrics : writing.step.GlyphMetrics;
|
|
const float left = writing.step.x + glyphMetrics.Horizontal.bearingX;
|
|
const float right = left + glyphMetrics.width;
|
|
const float top = writing.step.y - glyphMetrics.Horizontal.bearingY;
|
|
const float bottom = top + glyphMetrics.height;
|
|
writing.metrics.advanceX = writing.step.x + writing.step.advanceX;
|
|
writing.metrics.advanceY = writing.step.y + writing.step.advanceY;
|
|
if (!writing.hasAggregateMetrics) {
|
|
writing.metrics.Extent = {top, bottom, left, right};
|
|
writing.hasAggregateMetrics = true;
|
|
return;
|
|
}
|
|
writing.metrics.Extent.left = std::min(previous.Extent.left, left);
|
|
writing.metrics.Extent.right = std::max(previous.Extent.right, right);
|
|
writing.metrics.Extent.top = std::min(previous.Extent.top, top);
|
|
writing.metrics.Extent.bottom = std::max(previous.Extent.bottom, bottom);
|
|
}
|
|
|
|
constexpr std::size_t TEXT_CODES_CURRENT = 5;
|
|
constexpr std::size_t TEXT_CODES_START = 6;
|
|
constexpr std::size_t TEXT_CODES_TERM = 7;
|
|
|
|
void StepTextCodes(FontTextCodes* textCodes, const FontTextCharacter* character) {
|
|
textCodes->text.order = character->textOrder;
|
|
textCodes->text.code = character->characterCode;
|
|
textCodes->systemUse[TEXT_CODES_CURRENT] = const_cast<FontTextCharacter*>(character);
|
|
}
|
|
|
|
const FontTextCharacter* TextCodesCharacter(const FontTextCodes* textCodes, std::size_t slot) {
|
|
return static_cast<const FontTextCharacter*>(textCodes->systemUse[slot]);
|
|
}
|
|
|
|
}
|
|
|
|
#pragma GCC visibility push(default)
|
|
|
|
extern "C" {
|
|
|
|
int APS5_VABI sceFontTextSourceInit(FontTextSource* textSource, const void* textAddress, std::uint32_t textSizeByte, FontTextParseFunction textParser, void* textObject) {
|
|
if (!textSource) return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
std::memset(textSource, 0, sizeof(*textSource));
|
|
if (!textParser) return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
const void* end = textSizeByte != 0 ? static_cast<const std::uint8_t*>(textAddress) + textSizeByte : nullptr;
|
|
textSource->systemUse0 = (static_cast<std::uint64_t>(WRITING_FORM_HORIZONTAL) << 32) | TEXT_SOURCE_MAGIC;
|
|
textSource->start = textAddress;
|
|
textSource->end = end;
|
|
textSource->current = textAddress;
|
|
textSource->textParser = textParser;
|
|
textSource->textObject = textObject;
|
|
textSource->defaultFont = nullptr;
|
|
textSource->systemUse[0] = const_cast<void*>(textAddress);
|
|
textSource->systemUse[1] = const_cast<void*>(end);
|
|
textSource->systemUse[2] = reinterpret_cast<void*>(textParser);
|
|
textSource->systemUse[3] = textObject;
|
|
textSource->systemUse[4] = nullptr;
|
|
return SCE_FONT_OK;
|
|
}
|
|
|
|
int APS5_VABI sceFontTextSourceRewind(FontTextSource* textSource) {
|
|
if (!textSource || TextSourceMagic(textSource) != TEXT_SOURCE_MAGIC) return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
textSource->start = textSource->systemUse[0];
|
|
textSource->end = textSource->systemUse[1];
|
|
textSource->current = textSource->systemUse[0];
|
|
textSource->textParser = reinterpret_cast<FontTextParseFunction>(textSource->systemUse[2]);
|
|
textSource->textObject = textSource->systemUse[3];
|
|
textSource->defaultFont = static_cast<FontHandle>(textSource->systemUse[4]);
|
|
return SCE_FONT_OK;
|
|
}
|
|
|
|
int APS5_VABI sceFontTextSourceSetDefaultFont(FontTextSource* textSource, FontHandle defaultFont) {
|
|
if (!textSource || TextSourceMagic(textSource) != TEXT_SOURCE_MAGIC) return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
textSource->defaultFont = defaultFont;
|
|
textSource->systemUse[4] = defaultFont;
|
|
return SCE_FONT_OK;
|
|
}
|
|
|
|
int APS5_VABI sceFontTextSourceSetWritingForm(FontTextSource* textSource, std::int32_t writingForm) {
|
|
if (!textSource || TextSourceMagic(textSource) != TEXT_SOURCE_MAGIC) return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
if (writingForm < static_cast<std::int32_t>(WRITING_FORM_HORIZONTAL) || writingForm > static_cast<std::int32_t>(WRITING_FORM_HORIZONTAL_LTR)) return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
textSource->systemUse0 = (textSource->systemUse0 & 0xFFFFFFFFull) | (static_cast<std::uint64_t>(writingForm) << 32);
|
|
return SCE_FONT_OK;
|
|
}
|
|
|
|
int APS5_VABI sceFontCreateString(const FontMemory* fontMemory, FontTextSource* textSource, const FontCreateStringDetail* detail, FontString* pFontString) {
|
|
if (!fontMemory || !textSource || !pFontString || !textSource->textParser) {
|
|
if (pFontString) *pFontString = nullptr;
|
|
return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
}
|
|
*pFontString = nullptr;
|
|
if (TextSourceMagic(textSource) != TEXT_SOURCE_MAGIC) return SCE_FONT_ERROR_INVALID_TEXT_SOURCE;
|
|
if (fontMemory->mem_kind != MEMORY_MAGIC || !fontMemory->iface || !fontMemory->iface->alloc || !fontMemory->iface->dealloc) return SCE_FONT_ERROR_INVALID_MEMORY;
|
|
if (detail) {
|
|
constexpr std::uint32_t validOrders = (1u << 0) | (1u << 1) | (1u << 2) | (1u << 31);
|
|
if (detail->detailId != CREATE_STRING_DETAIL_ID || detail->detailType > 1 || (detail->detections & ~0x07u) != 0 || (detail->ordersOption & ~validOrders) != 0) return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
}
|
|
void* raw = fontMemory->iface->alloc(fontMemory->mspace_handle, sizeof(FontStringData));
|
|
if (!raw) return SCE_FONT_ERROR_ALLOCATION_FAILED;
|
|
auto* data = new (raw) FontStringData{};
|
|
data->memory = fontMemory;
|
|
data->source = textSource;
|
|
data->defaultFont = textSource->defaultFont;
|
|
data->storage = new CharacterStorage{};
|
|
if (!data->defaultFont && detail) data->defaultFont = detail->defaultFont;
|
|
data->writingForm = static_cast<std::uint32_t>(textSource->systemUse0 >> 32);
|
|
std::int32_t state = SCE_FONT_ERROR_INVALID_TEXT_SOURCE;
|
|
void* textOrder = nullptr;
|
|
for (std::size_t parsed = 0; parsed < MAX_PARSED_CHARACTERS; ++parsed) {
|
|
FontTextParseResult result{};
|
|
state = textSource->textParser(textSource, &textOrder, &result);
|
|
if (state == PARSER_RESULT_FONT_CODE) {
|
|
const FontHandle font = result.FontCode.font ? result.FontCode.font : data->defaultFont;
|
|
if (!font) {
|
|
state = SCE_FONT_ERROR_INVALID_TEXT_SOURCE;
|
|
break;
|
|
}
|
|
data->storage->characters.push_back(MakeCharacter(font, result.FontCode.code, textOrder));
|
|
continue;
|
|
}
|
|
if (state == PARSER_RESULT_TERMINATE) {
|
|
data->terminateOrder = textOrder;
|
|
data->terminateCode = result.Terminate.terminateCode;
|
|
state = SCE_FONT_OK;
|
|
} else if (state == PARSER_RESULT_ERROR) {
|
|
state = result.Error.errorCode < 0 ? result.Error.errorCode : SCE_FONT_ERROR_INVALID_TEXT_SOURCE;
|
|
} else {
|
|
state = SCE_FONT_ERROR_INVALID_TEXT_SOURCE;
|
|
}
|
|
break;
|
|
}
|
|
if (state == PARSER_RESULT_FONT_CODE) state = SCE_FONT_ERROR_INVALID_TEXT_SOURCE;
|
|
if (state != SCE_FONT_OK) {
|
|
ReleaseStorage(data->storage);
|
|
data->~FontStringData();
|
|
fontMemory->iface->dealloc(fontMemory->mspace_handle, raw);
|
|
return state;
|
|
}
|
|
LinkCharacters(data->storage->characters);
|
|
data->textCount = static_cast<std::uint32_t>(data->storage->characters.size());
|
|
data->renderCount = data->textCount;
|
|
*pFontString = reinterpret_cast<FontString>(data);
|
|
return SCE_FONT_OK;
|
|
}
|
|
|
|
int APS5_VABI sceFontDestroyString(FontString* pFontString) {
|
|
if (!pFontString) return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
auto* data = GetStringData(*pFontString);
|
|
if (!data) {
|
|
*pFontString = nullptr;
|
|
return SCE_FONT_ERROR_INVALID_STRING;
|
|
}
|
|
const FontMemory* memory = data->memory;
|
|
ReleaseStorage(data->storage);
|
|
data->~FontStringData();
|
|
memory->iface->dealloc(memory->mspace_handle, data);
|
|
*pFontString = nullptr;
|
|
return SCE_FONT_OK;
|
|
}
|
|
|
|
std::uint32_t APS5_VABI sceFontStringGetTerminateCode(FontString fontString) {
|
|
const auto* data = GetStringData(fontString);
|
|
return data ? data->terminateCode : 0;
|
|
}
|
|
|
|
void* APS5_VABI sceFontStringGetTerminateOrder(FontString fontString) {
|
|
const auto* data = GetStringData(fontString);
|
|
return data ? data->terminateOrder : nullptr;
|
|
}
|
|
|
|
int APS5_VABI sceFontStringGetWritingForm(FontString fontString) {
|
|
const auto* data = GetStringData(fontString);
|
|
return data ? static_cast<int>(data->writingForm) : 0;
|
|
}
|
|
|
|
FontTextCharacter* APS5_VABI sceFontStringRefersTextCharacters(FontString fontString, std::uint32_t* characterCount) {
|
|
auto* data = GetStringData(fontString);
|
|
if (!data || data->textCount == 0) {
|
|
if (characterCount) *characterCount = 0;
|
|
return nullptr;
|
|
}
|
|
if (characterCount) *characterCount = data->textCount;
|
|
return TextCharacters(data);
|
|
}
|
|
|
|
const FontTextCharacter* APS5_VABI sceFontStringRefersRenderCharacters(FontString fontString, FontTextCharacter* startCharacter, FontTextCharacter* lastCharacter, std::uint32_t* characterCount) {
|
|
auto* data = GetStringData(fontString);
|
|
if (!data || !characterCount || data->renderCount == 0) {
|
|
if (characterCount) *characterCount = 0;
|
|
return nullptr;
|
|
}
|
|
FontTextCharacter* characters = TextCharacters(data);
|
|
if (!characters) {
|
|
*characterCount = 0;
|
|
return nullptr;
|
|
}
|
|
const FontTextCharacter* begin = characters;
|
|
const FontTextCharacter* end = characters + data->renderCount;
|
|
if (!startCharacter) startCharacter = characters;
|
|
if (startCharacter < begin || startCharacter >= end || (lastCharacter && (lastCharacter < begin || lastCharacter >= end))) {
|
|
*characterCount = 0;
|
|
return nullptr;
|
|
}
|
|
FontTextCharacter* probe = startCharacter;
|
|
if (startCharacter->clusterKind > 1) {
|
|
do {
|
|
startCharacter = probe;
|
|
probe = startCharacter->prev;
|
|
if (!probe) break;
|
|
} while (probe->synthetic < 0);
|
|
}
|
|
auto startFlags = static_cast<std::uint32_t>(startCharacter->flags);
|
|
const std::uint32_t paragraphLevel = (data->writingForm >> 8) & 1u;
|
|
if ((startFlags & 0xFF00u) == 0x100u && startCharacter->prev) {
|
|
if (startCharacter->prev->clusterKind == 1) startCharacter = startCharacter->prev;
|
|
startFlags = static_cast<std::uint32_t>(startCharacter->flags);
|
|
}
|
|
std::uint32_t startLevel = startFlags >> 24;
|
|
std::uint32_t lastLevel = startLevel;
|
|
std::uint32_t minLevel = startLevel;
|
|
std::uint32_t count = startCharacter->clusterIndex == 0 ? 1u : 0u;
|
|
probe = startCharacter;
|
|
while (probe != lastCharacter) {
|
|
probe = probe->next;
|
|
if (!probe) {
|
|
if (lastCharacter) {
|
|
*characterCount = 0;
|
|
return nullptr;
|
|
}
|
|
break;
|
|
}
|
|
if (probe->clusterIndex == 0) {
|
|
lastLevel = LevelOf(probe);
|
|
if (lastLevel < minLevel && ((lastLevel ^ paragraphLevel) & 1u) == 0) minLevel = lastLevel;
|
|
++count;
|
|
}
|
|
}
|
|
if (startLevel != lastLevel || startLevel != minLevel) {
|
|
probe = startCharacter->prev;
|
|
while (probe) {
|
|
if (probe->clusterIndex == 0) {
|
|
if (LevelOf(probe) <= minLevel) break;
|
|
++count;
|
|
}
|
|
startCharacter = probe;
|
|
probe = probe->prev;
|
|
}
|
|
if (lastCharacter) {
|
|
probe = lastCharacter->next;
|
|
while (probe) {
|
|
if (probe->clusterIndex == 0) {
|
|
if (LevelOf(probe) <= minLevel) break;
|
|
++count;
|
|
lastCharacter = probe;
|
|
}
|
|
probe = probe->next;
|
|
}
|
|
}
|
|
}
|
|
if (lastCharacter) {
|
|
probe = lastCharacter->next;
|
|
std::uint32_t clusterTrim = 0;
|
|
while (probe) {
|
|
std::uint32_t nextTrim = clusterTrim;
|
|
if (probe->clusterIndex == 0) {
|
|
if (probe->synthetic == 0) break;
|
|
nextTrim = 0;
|
|
FontTextCharacter* nextLast = probe;
|
|
if (probe->synthetic < 1) {
|
|
if (probe->clusterKind == 1) break;
|
|
nextTrim = clusterTrim + 1;
|
|
nextLast = lastCharacter;
|
|
}
|
|
lastCharacter = nextLast;
|
|
++count;
|
|
}
|
|
probe = probe->next;
|
|
count -= nextTrim;
|
|
clusterTrim = nextTrim;
|
|
}
|
|
}
|
|
startLevel = LevelOf(startCharacter);
|
|
if (startLevel != paragraphLevel) {
|
|
probe = startCharacter;
|
|
if (!lastCharacter || LevelOf(lastCharacter) != startLevel) {
|
|
do {
|
|
probe = probe->next;
|
|
if (!probe) {
|
|
if (lastCharacter) startCharacter = nullptr;
|
|
break;
|
|
}
|
|
FontTextCharacter* best = startCharacter;
|
|
std::uint32_t bestLevel = startLevel;
|
|
if (probe->clusterIndex == 0) {
|
|
const std::uint32_t probeLevel = LevelOf(probe);
|
|
if (probeLevel <= paragraphLevel) break;
|
|
if ((startLevel & 1u) == paragraphLevel && probeLevel < startLevel && (probeLevel & 1u) != paragraphLevel) {
|
|
best = probe;
|
|
bestLevel = probeLevel;
|
|
}
|
|
}
|
|
startLevel = bestLevel;
|
|
startCharacter = best;
|
|
} while (probe != lastCharacter);
|
|
} else if ((LevelOf(startCharacter) & 1u) != paragraphLevel) {
|
|
startCharacter = lastCharacter;
|
|
}
|
|
}
|
|
if (!startCharacter) count = 0;
|
|
*characterCount = count;
|
|
return startCharacter;
|
|
}
|
|
|
|
int APS5_VABI sceFontCharacterGetBidiLevel(const FontTextCharacter* textCharacter, int* bidiLevel) {
|
|
if (!textCharacter || !bidiLevel) return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
*bidiLevel = static_cast<int>(LevelOf(textCharacter));
|
|
return SCE_FONT_OK;
|
|
}
|
|
|
|
int APS5_VABI sceFontCharacterGetSyllableStringState(const FontTextCharacter* textCharacter, int* syllableStringState) {
|
|
if (!textCharacter || !syllableStringState) {
|
|
if (syllableStringState) *syllableStringState = 0;
|
|
return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
}
|
|
if (textCharacter->characterCode >= 0x0E00 && textCharacter->characterCode <= 0x0E7F) throw std::runtime_error("libSceFont: sceFontCharacterGetSyllableStringState for Thai syllable clusters is not implemented");
|
|
*syllableStringState = 0;
|
|
return SCE_FONT_OK;
|
|
}
|
|
|
|
int APS5_VABI sceFontCharacterGetTextFontCode(const FontTextCharacter* textCharacter, FontHandle* pFontHandle, std::uint32_t* textCode) {
|
|
if (!textCharacter) {
|
|
if (pFontHandle) *pFontHandle = nullptr;
|
|
if (textCode) *textCode = 0;
|
|
return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
}
|
|
if (!pFontHandle || !textCode) return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
*pFontHandle = textCharacter->font;
|
|
*textCode = textCharacter->characterCode;
|
|
return SCE_FONT_OK;
|
|
}
|
|
|
|
int APS5_VABI sceFontCharacterGetTextOrder(const FontTextCharacter* textCharacter, void** pTextOrder) {
|
|
if (!pTextOrder) return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
if (!textCharacter) {
|
|
*pTextOrder = nullptr;
|
|
return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
}
|
|
*pTextOrder = textCharacter->textOrder;
|
|
return SCE_FONT_OK;
|
|
}
|
|
|
|
std::uint32_t APS5_VABI sceFontCharacterLooksFormatCharacters(const FontTextCharacter* textCharacter) {
|
|
if (!textCharacter) return 0;
|
|
return (textCharacter->flags & CHARACTER_FLAG_FORMAT) != 0 ? textCharacter->characterCode : 0;
|
|
}
|
|
|
|
std::uint32_t APS5_VABI sceFontCharacterLooksWhiteSpace(const FontTextCharacter* textCharacter) {
|
|
if (!textCharacter) return 0;
|
|
return ((textCharacter->flags >> 8) & 0xFFu) == 0x0E ? textCharacter->characterCode : 0;
|
|
}
|
|
|
|
FontTextCharacter* APS5_VABI sceFontCharacterRefersTextBack(const FontTextCharacter* textCharacter) {
|
|
if (!textCharacter) return nullptr;
|
|
for (FontTextCharacter* current = textCharacter->prev; current; current = current->prev) {
|
|
if (current->synthetic == 0 && current->clusterIndex == 0) return current;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
FontTextCharacter* APS5_VABI sceFontCharacterRefersTextNext(const FontTextCharacter* textCharacter) {
|
|
if (!textCharacter) return nullptr;
|
|
for (FontTextCharacter* current = textCharacter->next; current; current = current->next) {
|
|
if (current->synthetic == 0 && current->clusterIndex == 0) return current;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
FontTextCodes* APS5_VABI sceFontCharactersRefersTextCodes(const FontTextCharacter* textCharacter, const FontTextCharacter* termCharacter, FontTextCodes* textCodes) {
|
|
if (!textCodes) return nullptr;
|
|
if (!textCharacter) throw std::runtime_error("libSceFont: sceFontCharactersRefersTextCodes without a text character");
|
|
if (textCharacter->clusterIndex != 0 || textCharacter->synthetic != 0) return nullptr;
|
|
std::memset(textCodes, 0, sizeof(FontTextCodes));
|
|
StepTextCodes(textCodes, textCharacter);
|
|
textCodes->systemUse[TEXT_CODES_START] = const_cast<FontTextCharacter*>(textCharacter);
|
|
textCodes->systemUse[TEXT_CODES_TERM] = const_cast<FontTextCharacter*>(termCharacter);
|
|
return textCodes;
|
|
}
|
|
|
|
FontTextCodes* APS5_VABI sceFontTextCodesStepNext(FontTextCodes* textCodesStep) {
|
|
if (!textCodesStep) return nullptr;
|
|
const FontTextCharacter* current = TextCodesCharacter(textCodesStep, TEXT_CODES_CURRENT);
|
|
if (!current || current == TextCodesCharacter(textCodesStep, TEXT_CODES_TERM)) return nullptr;
|
|
const FontTextCharacter* next = current->next;
|
|
while (next && next->synthetic != 0) next = next->next;
|
|
if (!next) return nullptr;
|
|
StepTextCodes(textCodesStep, next);
|
|
return textCodesStep;
|
|
}
|
|
|
|
FontTextCodes* APS5_VABI sceFontTextCodesStepBack(FontTextCodes* textCodesStep) {
|
|
if (!textCodesStep) return nullptr;
|
|
const FontTextCharacter* current = TextCodesCharacter(textCodesStep, TEXT_CODES_CURRENT);
|
|
if (!current || current == TextCodesCharacter(textCodesStep, TEXT_CODES_START)) return nullptr;
|
|
const FontTextCharacter* previous = current->prev;
|
|
while (previous && previous->synthetic != 0) previous = previous->prev;
|
|
if (!previous) return nullptr;
|
|
StepTextCodes(textCodesStep, previous);
|
|
return textCodesStep;
|
|
}
|
|
|
|
int APS5_VABI sceFontWritingInit(FontWriting* fontWriting, FontString fontString, const FontTextCharacter* fontCharacter) {
|
|
if (!fontWriting || !fontString || !fontCharacter) return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
auto* data = GetStringData(fontString);
|
|
if (!data) return SCE_FONT_ERROR_INVALID_STRING;
|
|
if (auto* previous = GetWritingData(fontWriting)) ReleaseWritingStorage(*previous);
|
|
std::memset(fontWriting, 0, sizeof(*fontWriting));
|
|
auto* writing = new (fontWriting->systemUse) FontWritingData{};
|
|
writing->string = fontString;
|
|
writing->storage = data->storage;
|
|
writing->defaultFont = data->defaultFont;
|
|
if (writing->storage) writing->storage->refCount.fetch_add(1, std::memory_order_relaxed);
|
|
writing->ownsStorage = true;
|
|
writing->writingForm = data->writingForm;
|
|
const FontTextCharacter* characters = TextCharacters(data);
|
|
if (!characters || fontCharacter < characters || fontCharacter >= characters + data->storage->characters.size()) {
|
|
ReleaseWritingStorage(*writing);
|
|
return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
}
|
|
writing->nextIndex = static_cast<std::size_t>(fontCharacter - characters);
|
|
writing->currentIndex = writing->nextIndex;
|
|
writing->hasCurrentStep = false;
|
|
if (!PopulateWritingStep(*writing, writing->nextIndex)) {
|
|
ReleaseWritingStorage(*writing);
|
|
return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
}
|
|
writing->penX = 0.0f;
|
|
writing->penY = 0.0f;
|
|
writing->hasAggregateMetrics = false;
|
|
writing->metrics = {};
|
|
return SCE_FONT_OK;
|
|
}
|
|
|
|
int APS5_VABI sceFontWritingGetRenderMetrics(FontWriting* fontWriting, FontWritingMetrics* writingMetrics) {
|
|
if (!fontWriting || !writingMetrics) {
|
|
if (writingMetrics) *writingMetrics = {};
|
|
return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
}
|
|
const auto* writing = GetWritingData(fontWriting);
|
|
if (!writing) {
|
|
*writingMetrics = {};
|
|
return SCE_FONT_ERROR_INVALID_WRITING;
|
|
}
|
|
*writingMetrics = writing->metrics;
|
|
return SCE_FONT_OK;
|
|
}
|
|
|
|
const FontWritingStep* APS5_VABI sceFontWritingRefersRenderStep(FontWriting* fontWriting) {
|
|
auto* writing = GetWritingData(fontWriting);
|
|
if (!writing || !writing->storage || writing->nextIndex >= writing->storage->characters.size()) return nullptr;
|
|
if (!PopulateWritingStep(*writing, writing->nextIndex)) return nullptr;
|
|
writing->currentIndex = writing->nextIndex;
|
|
writing->hasCurrentStep = true;
|
|
AccumulateWritingMetrics(*writing);
|
|
++writing->nextIndex;
|
|
return &writing->step;
|
|
}
|
|
|
|
FontTextCharacter* APS5_VABI sceFontWritingRefersRenderStepCharacter(FontWriting* fontWriting, const FontWritingLetterStep** pLetterStep) {
|
|
if (pLetterStep) *pLetterStep = nullptr;
|
|
auto* writing = GetWritingData(fontWriting);
|
|
if (!writing || !writing->hasCurrentStep || !writing->storage || writing->currentIndex >= writing->storage->characters.size()) return nullptr;
|
|
auto& letterStep = writing->letterStep;
|
|
letterStep = {};
|
|
letterStep.x = writing->step.x;
|
|
letterStep.y = writing->step.y;
|
|
letterStep.advanceX = writing->step.advanceX;
|
|
letterStep.advanceY = writing->step.advanceY;
|
|
letterStep.Components.textsCount = 1;
|
|
letterStep.Components.textsIndex = static_cast<std::uint32_t>(writing->currentIndex);
|
|
letterStep.Components.glyphsCount = 1;
|
|
letterStep.Components.glyphsIndex = static_cast<std::uint32_t>(writing->currentIndex);
|
|
letterStep.Components.characterTextCount = static_cast<std::uint8_t>(std::max<std::uint32_t>(writing->step.Profile.characterCount, 1));
|
|
if (pLetterStep) *pLetterStep = &letterStep;
|
|
return &writing->storage->characters[writing->currentIndex];
|
|
}
|
|
|
|
int APS5_VABI sceFontWritingSetMaskInvisible(FontWriting* fontWriting, std::int32_t mask) {
|
|
auto* writing = GetWritingData(fontWriting);
|
|
if (!writing) return SCE_FONT_ERROR_INVALID_WRITING;
|
|
if (mask == WRITING_MASK_FORMAT_CHARACTERS) {
|
|
writing->maskFormatCharacters = false;
|
|
return SCE_FONT_OK;
|
|
}
|
|
if (mask == WRITING_MASK_NONE) {
|
|
writing->maskFormatCharacters = true;
|
|
return SCE_FONT_OK;
|
|
}
|
|
return SCE_FONT_ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
}
|
|
|
|
#pragma GCC visibility pop
|