AnyPS5/core/libs/prx/libSceFont/src/WritingLine.cpp

350 lines
13 KiB
C++

#include <array>
#include <cstddef>
#include <cstdint>
#include <deque>
#include <mutex>
#include <stdexcept>
#include <string>
#include <unordered_set>
#include "prx/libSceFont/include/FontTypes.hpp"
namespace {
constexpr std::int32_t WRITING_FORM_HORIZONTAL = 0x10;
constexpr std::int32_t WRITING_FORM_HORIZONTAL_RTL = 0x11;
constexpr std::int32_t WRITING_FORM_HORIZONTAL_LTR = 0x12;
constexpr std::uint16_t CREATE_WRITING_LINE_DETAIL_ID = 0x0FD5;
constexpr std::uint64_t WRITING_ATTRIBUTE_ID_MASK = 0xFF;
constexpr std::uint64_t WRITING_ATTRIBUTE_ID = 0xFA;
constexpr std::uint64_t WRITING_ATTRIBUTE_SPACE_BEFORE = std::uint64_t{1} << 30;
constexpr std::uint64_t WRITING_ATTRIBUTE_SPACE_AFTER = std::uint64_t{1} << 31;
constexpr std::size_t WRITING_LEVEL_COUNT = 0x7F;
struct CreateWritingLineDetail {
std::uint16_t detailId;
};
struct WritingOrder {
void* orderer = nullptr;
WritingOrder* prev = nullptr;
WritingOrder* next = nullptr;
std::uint64_t attribute = 0;
FontWritingMetrics metrics{};
};
struct WritingLevel {
WritingOrder* first = nullptr;
WritingOrder* last = nullptr;
};
struct WritingLine {
std::int32_t form = 0;
FontWritingMetrics renderMetrics{};
FontWritingLineStep step{};
float advanceX = 0.0f;
float advanceY = 0.0f;
float orderingSpace = 0.0f;
float steppedSpace = 0.0f;
std::size_t lastLevel = 0;
WritingOrder* last = nullptr;
WritingOrder* head = nullptr;
WritingOrder* tail = nullptr;
WritingOrder* cursor = nullptr;
std::array<WritingLevel, WRITING_LEVEL_COUNT> levels{};
std::deque<WritingOrder> orders;
};
std::mutex linesMutex;
std::unordered_set<WritingLine*> lines;
[[noreturn]] void Unsupported(const char* function, const std::string& what) {
throw std::runtime_error(std::string(function) + ": " + what);
}
WritingLine* GetLine(const char* function, void* writingLine) {
auto* line = static_cast<WritingLine*>(writingLine);
std::lock_guard lock(linesMutex);
if (!lines.contains(line)) Unsupported(function, "unknown writing line handle (error code not verified)");
return line;
}
bool IsRightToLeft(const WritingLine& line) {
return line.form == WRITING_FORM_HORIZONTAL_RTL;
}
bool SpaceAfter(const WritingOrder& order) {
return (order.attribute & WRITING_ATTRIBUTE_SPACE_AFTER) != 0;
}
bool SpaceBefore(const WritingOrder& order) {
return (order.attribute & WRITING_ATTRIBUTE_SPACE_BEFORE) != 0;
}
bool IsSpaced(const WritingOrder& before, const WritingOrder& after) {
return SpaceAfter(before) || SpaceBefore(after);
}
float HeadSpace(const WritingLine& line, const WritingOrder& order) {
return IsRightToLeft(line) ? order.metrics.advanceX - order.metrics.Extent.left : order.metrics.Extent.left;
}
float TailSpace(const WritingLine& line, const WritingOrder& order) {
return IsRightToLeft(line) ? order.metrics.Extent.right : order.metrics.advanceX - order.metrics.Extent.right;
}
void ResetOrders(WritingLine& line) {
line.orders.clear();
line.levels.fill({});
line.advanceX = 0.0f;
line.advanceY = 0.0f;
line.orderingSpace = 0.0f;
line.last = nullptr;
line.head = nullptr;
line.tail = nullptr;
line.cursor = nullptr;
}
void InsertAfter(WritingOrder* anchor, WritingOrder* order) {
order->prev = anchor;
order->next = anchor->next;
if (anchor->next) anchor->next->prev = order;
anchor->next = order;
}
void InsertBefore(WritingOrder* anchor, WritingOrder* order) {
order->next = anchor;
order->prev = anchor->prev;
if (anchor->prev) anchor->prev->next = order;
anchor->prev = order;
}
void Link(WritingLine& line, WritingOrder* order, std::size_t level) {
WritingLevel& slot = line.levels[level];
if (!line.last) {
slot = {order, order};
return;
}
if (level >= line.lastLevel) {
if (line.lastLevel % 2 == 0) {
InsertAfter(line.last, order);
} else {
InsertBefore(line.last, order);
}
if (!slot.last || order->prev == slot.last) slot.last = order;
if (!slot.first || order->next == slot.first) slot.first = order;
return;
}
WritingOrder* first = line.levels[line.lastLevel].first;
WritingOrder* last = line.levels[line.lastLevel].last;
line.levels[line.lastLevel] = {};
for (std::size_t closing = line.lastLevel - 1; closing > level; --closing) {
WritingLevel& closed = line.levels[closing];
if (closing % 2 == 0) {
if (closed.first) first = closed.first;
} else {
if (closed.last) last = closed.last;
}
closed = {};
}
if (level % 2 == 0) {
if (!slot.first) slot.first = first;
InsertAfter(last, order);
slot.last = order;
} else {
if (!slot.last) slot.last = last;
InsertBefore(first, order);
slot.first = order;
}
}
float OrderingSpaceDelta(const WritingLine& line, const WritingOrder& order) {
const WritingOrder* before = order.prev;
const WritingOrder* after = order.next;
const bool split = before && after && IsSpaced(*before, *after);
const float sign = split ? -1.0f : 1.0f;
float delta = 0.0f;
if (before) {
const bool spaced = IsSpaced(*before, order);
if (spaced) delta = HeadSpace(line, order);
if (spaced != split) delta += sign * TailSpace(line, *before);
}
if (after) {
const bool spaced = IsSpaced(order, *after);
if (spaced) delta += TailSpace(line, order);
if (spaced != split) delta += sign * HeadSpace(line, *after);
}
return delta;
}
void ExtendRenderMetrics(WritingLine& line) {
const FontWritingMetrics& run = line.step.Metrics;
const float x = line.step.x + line.step.Adjusting.x;
const float y = line.step.y + line.step.Adjusting.y;
line.step.x += run.advanceX;
line.step.y += run.advanceY;
FontWritingMetrics& total = line.renderMetrics;
total.advanceX = x + run.advanceX;
total.advanceY = y + run.advanceY;
if (!(run.Extent.left == run.Extent.right)) {
const float left = x + run.Extent.left;
const float right = x + run.Extent.right;
if (total.Extent.left == 0.0f && total.Extent.right == 0.0f) {
total.Extent.left = left;
total.Extent.right = right;
} else {
if (total.Extent.left > left) total.Extent.left = left;
if (right > total.Extent.right) total.Extent.right = right;
}
}
if (!(run.Extent.top == run.Extent.bottom)) {
const float top = y + run.Extent.top;
const float bottom = y + run.Extent.bottom;
if (total.Extent.top == 0.0f && total.Extent.bottom == 0.0f) {
total.Extent.top = top;
total.Extent.bottom = bottom;
} else {
if (total.Extent.top > top) total.Extent.top = top;
if (bottom > total.Extent.bottom) total.Extent.bottom = bottom;
}
}
}
}
#pragma GCC visibility push(default)
extern "C" {
int APS5_VABI sceFontCreateWritingLine(const FontMemory* memory, std::int32_t writingForm, const void* writingLineDetail, void** pWritingLine) {
(void)memory;
if (!pWritingLine) Unsupported(__func__, "null writing line pointer (error code not verified)");
if (writingForm != WRITING_FORM_HORIZONTAL && writingForm != WRITING_FORM_HORIZONTAL_RTL && writingForm != WRITING_FORM_HORIZONTAL_LTR)
Unsupported(__func__, "writing form " + std::to_string(writingForm) + " is not modelled");
if (writingLineDetail && static_cast<const CreateWritingLineDetail*>(writingLineDetail)->detailId != CREATE_WRITING_LINE_DETAIL_ID)
Unsupported(__func__, "unknown writing line detail id");
auto* line = new WritingLine{};
line->form = writingForm;
{
std::lock_guard lock(linesMutex);
lines.insert(line);
}
*pWritingLine = line;
return SCE_FONT_OK;
}
int APS5_VABI sceFontDestroyWritingLine(void** pWritingLine) {
if (!pWritingLine || !*pWritingLine) return SCE_FONT_OK;
auto* line = GetLine(__func__, *pWritingLine);
{
std::lock_guard lock(linesMutex);
lines.erase(line);
}
delete line;
*pWritingLine = nullptr;
return SCE_FONT_OK;
}
int APS5_VABI sceFontWritingLineClear(void* writingLine) {
auto* line = GetLine(__func__, writingLine);
ResetOrders(*line);
return SCE_FONT_OK;
}
int APS5_VABI sceFontWritingLineWritesOrder(void* writingLine, std::uint64_t writingAttribute, const FontWritingMetrics* writingMetrics, void* writingOrderer) {
auto* line = GetLine(__func__, writingLine);
if (writingAttribute == 0) writingAttribute = WRITING_ATTRIBUTE_ID;
if ((writingAttribute & WRITING_ATTRIBUTE_ID_MASK) != WRITING_ATTRIBUTE_ID)
Unsupported(__func__, "writing attribute " + std::to_string(writingAttribute) + " is not modelled");
const std::size_t level = (writingAttribute >> 8) & 0xFF;
if (level >= WRITING_LEVEL_COUNT) Unsupported(__func__, "embedding level " + std::to_string(level) + " (error code not verified)");
if (!writingOrderer) Unsupported(__func__, "null writing orderer (error code not verified)");
if (line->cursor) ResetOrders(*line);
WritingOrder* order = &line->orders.emplace_back();
order->orderer = writingOrderer;
order->attribute = writingAttribute;
if (writingMetrics) order->metrics = *writingMetrics;
Link(*line, order, level);
if (!order->prev) line->head = order;
if (!order->next) line->tail = order;
line->orderingSpace += OrderingSpaceDelta(*line, *order);
line->advanceX += order->metrics.advanceX;
line->advanceY += order->metrics.advanceY;
line->last = order;
line->lastLevel = level;
return SCE_FONT_OK;
}
const FontWritingLineStep* APS5_VABI sceFontWritingLineRefersRenderStep(void* writingLine) {
if (!writingLine) return nullptr;
auto* line = GetLine(__func__, writingLine);
if (line->cursor) {
ExtendRenderMetrics(*line);
} else {
line->steppedSpace = 0.0f;
line->step.x = 0.0f;
line->step.y = 0.0f;
line->step.spacingProgress = 0.0f;
line->step.Metrics = {};
line->renderMetrics = {};
}
const bool rightToLeft = IsRightToLeft(*line);
WritingOrder* next = nullptr;
if (rightToLeft) {
next = line->cursor ? line->cursor->prev : line->tail;
} else {
next = line->cursor ? line->cursor->next : line->head;
}
if (!next) {
line->cursor = nullptr;
line->step.Metrics = {};
return nullptr;
}
if (rightToLeft) {
const WritingOrder* stepped = next->next;
if (stepped && IsSpaced(*next, *stepped))
line->steppedSpace += stepped->metrics.advanceX - stepped->metrics.Extent.left + next->metrics.Extent.right;
} else {
const WritingOrder* stepped = next->prev;
if (stepped && IsSpaced(*stepped, *next))
line->steppedSpace += stepped->metrics.advanceX - stepped->metrics.Extent.right + next->metrics.Extent.left;
}
line->cursor = next;
line->step.Metrics = next->metrics;
line->step.writingOrderer = next->orderer;
line->step.Adjusting = {};
line->step.spacingProgress = 0.0f;
if (!(line->steppedSpace == 0.0f) && !(line->orderingSpace == 0.0f)) line->step.spacingProgress = line->steppedSpace / line->orderingSpace;
return &line->step;
}
int APS5_VABI sceFontWritingLineGetRenderMetrics(void* writingLine, FontWritingMetrics* writingMetrics) {
auto* line = GetLine(__func__, writingLine);
if (!writingMetrics) Unsupported(__func__, "null metrics pointer (error code not verified)");
*writingMetrics = line->renderMetrics;
return SCE_FONT_OK;
}
int APS5_VABI sceFontWritingLineGetOrderingSpace(void* writingLine, float* advanceSpace, float* headSpace, float* inlineSpace, float* tailSpace) {
auto* line = GetLine(__func__, writingLine);
if (headSpace && !tailSpace) Unsupported(__func__, "a head space pointer without a tail space pointer is not modelled");
if (advanceSpace) *advanceSpace = line->advanceX;
if (headSpace) {
const WritingOrder* leading = IsRightToLeft(*line) ? line->tail : line->head;
const WritingOrder* trailing = IsRightToLeft(*line) ? line->head : line->tail;
if (IsRightToLeft(*line)) {
*headSpace = leading ? leading->metrics.Extent.right : 0.0f;
*tailSpace = trailing ? trailing->metrics.advanceX - trailing->metrics.Extent.left : 0.0f;
} else {
*headSpace = leading ? leading->metrics.Extent.left : 0.0f;
*tailSpace = trailing ? trailing->metrics.advanceX - trailing->metrics.Extent.right : 0.0f;
}
}
if (inlineSpace) *inlineSpace = line->orderingSpace;
return SCE_FONT_OK;
}
}
#pragma GCC visibility pop