diff --git a/.gitmodules b/.gitmodules index dd956d0799..0ee36caa07 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,6 +34,9 @@ [submodule "3rdparty/freetype"] path = 3rdparty/freetype url = https://github.com/freetype/freetype.git +[submodule "3rdparty/harfbuzz"] + path = 3rdparty/harfbuzz + url = https://github.com/harfbuzz/harfbuzz.git [submodule "3rdparty/stb"] path = 3rdparty/stb url = https://github.com/nothings/stb.git diff --git a/3rdparty/harfbuzz b/3rdparty/harfbuzz new file mode 160000 index 0000000000..a1d9bfe628 --- /dev/null +++ b/3rdparty/harfbuzz @@ -0,0 +1 @@ +Subproject commit a1d9bfe62818ef0fa9cf63b6e6d51436b1c93cbc diff --git a/core/libs/prx/libharfbuzz/Buffer.cpp b/core/libs/prx/libharfbuzz/Buffer.cpp new file mode 100644 index 0000000000..12aa85438a --- /dev/null +++ b/core/libs/prx/libharfbuzz/Buffer.cpp @@ -0,0 +1,88 @@ +#include "prx/libharfbuzz/libharfbuzz.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/General.hpp" + +#include + +#include +#include +#include + +static_assert(sizeof(hb_direction_t) == 4); +static_assert(sizeof(hb_script_t) == 4); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(sizeof(unsigned) == 4); +static_assert(sizeof(hb_glyph_info_t) == 20); +static_assert(alignof(hb_glyph_info_t) == 4); +static_assert(offsetof(hb_glyph_info_t, codepoint) == 0); +static_assert(offsetof(hb_glyph_info_t, mask) == 4); +static_assert(offsetof(hb_glyph_info_t, cluster) == 8); +static_assert(offsetof(hb_glyph_info_t, var1) == 12); +static_assert(offsetof(hb_glyph_info_t, var2) == 16); +static_assert(sizeof(hb_glyph_position_t) == 20); +static_assert(alignof(hb_glyph_position_t) == 4); +static_assert(offsetof(hb_glyph_position_t, x_advance) == 0); +static_assert(offsetof(hb_glyph_position_t, y_advance) == 4); +static_assert(offsetof(hb_glyph_position_t, x_offset) == 8); +static_assert(offsetof(hb_glyph_position_t, y_offset) == 12); +static_assert(offsetof(hb_glyph_position_t, var) == 16); + +extern "C" { + +hb_buffer_t* APS5_VABI hb_buffer_create_nid_postfix() { + return hb_buffer_create(); +} + +void APS5_VABI hb_buffer_destroy_nid_postfix(hb_buffer_t* buffer) { + hb_buffer_destroy(buffer); +} + +void APS5_VABI hb_buffer_add_utf16_nid_postfix(hb_buffer_t* buffer, const std::uint16_t* text, int textLength, unsigned itemOffset, int itemLength) { + if (!buffer) NotImplemented_nid_no_patch(__func__); + hb_buffer_add_utf16(buffer, text, textLength, itemOffset, itemLength); +} + +unsigned APS5_VABI hb_buffer_get_length_nid_postfix(const hb_buffer_t* buffer) { + if (!buffer) NotImplemented_nid_no_patch(__func__); + return hb_buffer_get_length(buffer); +} + +std::uint32_t APS5_VABI hb_buffer_get_direction_nid_postfix(const hb_buffer_t* buffer) { + if (!buffer) NotImplemented_nid_no_patch(__func__); + return hb_buffer_get_direction(buffer); +} + +void APS5_VABI hb_buffer_set_direction_nid_postfix(hb_buffer_t* buffer, std::uint32_t direction) { + if (!buffer) NotImplemented_nid_no_patch(__func__); + hb_buffer_set_direction(buffer, static_cast(direction)); +} + +void APS5_VABI hb_buffer_set_script_nid_postfix(hb_buffer_t* buffer, std::uint32_t script) { + if (!buffer) NotImplemented_nid_no_patch(__func__); + hb_buffer_set_script(buffer, static_cast(script)); +} + +void APS5_VABI hb_buffer_guess_segment_properties_nid_postfix(hb_buffer_t* buffer) { + if (!buffer) NotImplemented_nid_no_patch(__func__); + hb_buffer_guess_segment_properties(buffer); +} + +hb_glyph_info_t* APS5_VABI hb_buffer_get_glyph_infos_nid_postfix(hb_buffer_t* buffer, unsigned* length) { + if (!buffer) NotImplemented_nid_no_patch(__func__); + GuestArena::HostWrite write(length, length ? sizeof(*length) : 0); + return hb_buffer_get_glyph_infos(buffer, length); +} + +hb_glyph_position_t* APS5_VABI hb_buffer_get_glyph_positions_nid_postfix(hb_buffer_t* buffer, unsigned* length) { + if (!buffer) NotImplemented_nid_no_patch(__func__); + GuestArena::HostWrite write(length, length ? sizeof(*length) : 0); + return hb_buffer_get_glyph_positions(buffer, length); +} + +void APS5_VABI hb_buffer_reset_nid_postfix(hb_buffer_t* buffer) { + if (!buffer) NotImplemented_nid_no_patch(__func__); + hb_buffer_reset(buffer); +} + +} diff --git a/core/libs/prx/libharfbuzz/CMakeLists.txt b/core/libs/prx/libharfbuzz/CMakeLists.txt new file mode 100644 index 0000000000..4ac265d308 --- /dev/null +++ b/core/libs/prx/libharfbuzz/CMakeLists.txt @@ -0,0 +1,30 @@ +set(harfbuzzSourceDir ${CMAKE_SOURCE_DIR}/3rdparty/harfbuzz) +add_library(harfbuzz_backend STATIC EXCLUDE_FROM_ALL ${harfbuzzSourceDir}/src/harfbuzz.cc) +target_include_directories(harfbuzz_backend PUBLIC ${harfbuzzSourceDir}/src) +set_target_properties(harfbuzz_backend PROPERTIES + POSITION_INDEPENDENT_CODE ON + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON) +install(FILES ${harfbuzzSourceDir}/COPYING DESTINATION licenses/harfbuzz) + +add_library(libharfbuzz SHARED EXCLUDE_FROM_ALL Export.cpp Buffer.cpp) +target_include_directories(libharfbuzz PUBLIC ${LIBS_INCLUDE_DIR}) +target_link_libraries(libharfbuzz PRIVATE harfbuzz_backend) +configure_windows_unwind(libharfbuzz) +if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_options(libharfbuzz PRIVATE LINKER:--exclude-libs,ALL) +endif() + +if(BUILD_TESTING) + add_test_executable(guest_harfbuzz_buffer_tests ${LIBS_INCLUDE_DIR}/tests/GuestHarfBuzzBuffer.cpp) + target_link_libraries(guest_harfbuzz_buffer_tests PRIVATE libharfbuzz harfbuzz_backend libc) + configure_windows_unwind(guest_harfbuzz_buffer_tests) + foreach(mode IN ITEMS utf16 properties lifetime) + add_test(NAME guest_harfbuzz_buffer_${mode} COMMAND guest_harfbuzz_buffer_tests ${mode}) + set_tests_properties(guest_harfbuzz_buffer_${mode} PROPERTIES TIMEOUT 20 ENVIRONMENT "LC_ALL=C;LANG=C") + if(WIN32) + set_tests_properties(guest_harfbuzz_buffer_${mode} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() +endif() diff --git a/core/libs/prx/libharfbuzz/Export.cpp b/core/libs/prx/libharfbuzz/Export.cpp index a9c523b58e..3cc08a5f86 100644 --- a/core/libs/prx/libharfbuzz/Export.cpp +++ b/core/libs/prx/libharfbuzz/Export.cpp @@ -3,143 +3,99 @@ extern "C" { -int APS5_VABI hb_blob_create() { +int APS5_VABI hb_blob_create_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_buffer_add_utf16() { +int APS5_VABI hb_face_create_for_tables_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_buffer_create() { +int APS5_VABI hb_face_destroy_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_buffer_destroy() { +int APS5_VABI hb_font_create_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_buffer_get_direction() { +int APS5_VABI hb_font_destroy_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_buffer_get_glyph_infos() { +int APS5_VABI hb_font_funcs_create_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_buffer_get_glyph_positions() { +int APS5_VABI hb_font_funcs_make_immutable_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_buffer_get_length() { +int APS5_VABI hb_font_funcs_set_glyph_extents_func_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_buffer_guess_segment_properties() { +int APS5_VABI hb_font_funcs_set_glyph_func_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_buffer_reset() { +int APS5_VABI hb_font_funcs_set_glyph_h_advance_func_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_buffer_set_direction() { +int APS5_VABI hb_font_funcs_set_glyph_h_origin_func_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_buffer_set_script() { +int APS5_VABI hb_font_funcs_set_nominal_glyph_func_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_face_create_for_tables() { +int APS5_VABI hb_font_funcs_set_variation_glyph_func_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_face_destroy() { +int APS5_VABI hb_font_make_immutable_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_font_create() { +int APS5_VABI hb_font_set_funcs_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_font_destroy() { +int APS5_VABI hb_font_set_ppem_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_font_funcs_create() { +int APS5_VABI hb_font_set_scale_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_font_funcs_make_immutable() { +int APS5_VABI hb_ft_face_create_cached_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_font_funcs_set_glyph_extents_func() { +int APS5_VABI hb_icu_script_to_script_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_font_funcs_set_glyph_func() { +int APS5_VABI hb_ot_layout_language_find_feature_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_font_funcs_set_glyph_h_advance_func() { +int APS5_VABI hb_ot_layout_script_get_language_tags_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_font_funcs_set_glyph_h_origin_func() { +int APS5_VABI hb_ot_layout_table_get_script_tags_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_font_funcs_set_nominal_glyph_func() { +int APS5_VABI hb_ot_tag_to_script_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI hb_font_funcs_set_variation_glyph_func() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI hb_font_make_immutable() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI hb_font_set_funcs() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI hb_font_set_ppem() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI hb_font_set_scale() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI hb_ft_face_create_cached() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI hb_icu_script_to_script() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI hb_ot_layout_language_find_feature() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI hb_ot_layout_script_get_language_tags() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI hb_ot_layout_table_get_script_tags() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI hb_ot_tag_to_script() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI hb_shape() { +int APS5_VABI hb_shape_nid_postfix() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libharfbuzz/include/InventoryExports.hpp b/core/libs/prx/libharfbuzz/include/InventoryExports.hpp index 9300f810e5..46736bcebb 100644 --- a/core/libs/prx/libharfbuzz/include/InventoryExports.hpp +++ b/core/libs/prx/libharfbuzz/include/InventoryExports.hpp @@ -2,42 +2,46 @@ #include "SceTypes.hpp" +struct hb_buffer_t; +struct hb_glyph_info_t; +struct hb_glyph_position_t; + extern "C" { -int APS5_VABI hb_blob_create(); -int APS5_VABI hb_buffer_add_utf16(); -int APS5_VABI hb_buffer_create(); -int APS5_VABI hb_buffer_destroy(); -int APS5_VABI hb_buffer_get_direction(); -int APS5_VABI hb_buffer_get_glyph_infos(); -int APS5_VABI hb_buffer_get_glyph_positions(); -int APS5_VABI hb_buffer_get_length(); -int APS5_VABI hb_buffer_guess_segment_properties(); -int APS5_VABI hb_buffer_reset(); -int APS5_VABI hb_buffer_set_direction(); -int APS5_VABI hb_buffer_set_script(); -int APS5_VABI hb_face_create_for_tables(); -int APS5_VABI hb_face_destroy(); -int APS5_VABI hb_font_create(); -int APS5_VABI hb_font_destroy(); -int APS5_VABI hb_font_funcs_create(); -int APS5_VABI hb_font_funcs_make_immutable(); -int APS5_VABI hb_font_funcs_set_glyph_extents_func(); -int APS5_VABI hb_font_funcs_set_glyph_func(); -int APS5_VABI hb_font_funcs_set_glyph_h_advance_func(); -int APS5_VABI hb_font_funcs_set_glyph_h_origin_func(); -int APS5_VABI hb_font_funcs_set_nominal_glyph_func(); -int APS5_VABI hb_font_funcs_set_variation_glyph_func(); -int APS5_VABI hb_font_make_immutable(); -int APS5_VABI hb_font_set_funcs(); -int APS5_VABI hb_font_set_ppem(); -int APS5_VABI hb_font_set_scale(); -int APS5_VABI hb_ft_face_create_cached(); -int APS5_VABI hb_icu_script_to_script(); -int APS5_VABI hb_ot_layout_language_find_feature(); -int APS5_VABI hb_ot_layout_script_get_language_tags(); -int APS5_VABI hb_ot_layout_table_get_script_tags(); -int APS5_VABI hb_ot_tag_to_script(); -int APS5_VABI hb_shape(); +int APS5_VABI hb_blob_create_nid_postfix(); +void APS5_VABI hb_buffer_add_utf16_nid_postfix(hb_buffer_t* buffer, const std::uint16_t* text, int textLength, unsigned itemOffset, int itemLength); +hb_buffer_t* APS5_VABI hb_buffer_create_nid_postfix(); +void APS5_VABI hb_buffer_destroy_nid_postfix(hb_buffer_t* buffer); +std::uint32_t APS5_VABI hb_buffer_get_direction_nid_postfix(const hb_buffer_t* buffer); +hb_glyph_info_t* APS5_VABI hb_buffer_get_glyph_infos_nid_postfix(hb_buffer_t* buffer, unsigned* length); +hb_glyph_position_t* APS5_VABI hb_buffer_get_glyph_positions_nid_postfix(hb_buffer_t* buffer, unsigned* length); +unsigned APS5_VABI hb_buffer_get_length_nid_postfix(const hb_buffer_t* buffer); +void APS5_VABI hb_buffer_guess_segment_properties_nid_postfix(hb_buffer_t* buffer); +void APS5_VABI hb_buffer_reset_nid_postfix(hb_buffer_t* buffer); +void APS5_VABI hb_buffer_set_direction_nid_postfix(hb_buffer_t* buffer, std::uint32_t direction); +void APS5_VABI hb_buffer_set_script_nid_postfix(hb_buffer_t* buffer, std::uint32_t script); +int APS5_VABI hb_face_create_for_tables_nid_postfix(); +int APS5_VABI hb_face_destroy_nid_postfix(); +int APS5_VABI hb_font_create_nid_postfix(); +int APS5_VABI hb_font_destroy_nid_postfix(); +int APS5_VABI hb_font_funcs_create_nid_postfix(); +int APS5_VABI hb_font_funcs_make_immutable_nid_postfix(); +int APS5_VABI hb_font_funcs_set_glyph_extents_func_nid_postfix(); +int APS5_VABI hb_font_funcs_set_glyph_func_nid_postfix(); +int APS5_VABI hb_font_funcs_set_glyph_h_advance_func_nid_postfix(); +int APS5_VABI hb_font_funcs_set_glyph_h_origin_func_nid_postfix(); +int APS5_VABI hb_font_funcs_set_nominal_glyph_func_nid_postfix(); +int APS5_VABI hb_font_funcs_set_variation_glyph_func_nid_postfix(); +int APS5_VABI hb_font_make_immutable_nid_postfix(); +int APS5_VABI hb_font_set_funcs_nid_postfix(); +int APS5_VABI hb_font_set_ppem_nid_postfix(); +int APS5_VABI hb_font_set_scale_nid_postfix(); +int APS5_VABI hb_ft_face_create_cached_nid_postfix(); +int APS5_VABI hb_icu_script_to_script_nid_postfix(); +int APS5_VABI hb_ot_layout_language_find_feature_nid_postfix(); +int APS5_VABI hb_ot_layout_script_get_language_tags_nid_postfix(); +int APS5_VABI hb_ot_layout_table_get_script_tags_nid_postfix(); +int APS5_VABI hb_ot_tag_to_script_nid_postfix(); +int APS5_VABI hb_shape_nid_postfix(); } diff --git a/core/libs/tests/GuestHarfBuzzBuffer.cpp b/core/libs/tests/GuestHarfBuzzBuffer.cpp new file mode 100644 index 0000000000..f8cc463ec0 --- /dev/null +++ b/core/libs/tests/GuestHarfBuzzBuffer.cpp @@ -0,0 +1,255 @@ +#include "prx/libharfbuzz/libharfbuzz.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "HarfBuzz buffer check failed at line %d\n", line); + std::abort(); + } +} + +#define Require(value) Check((value), __LINE__) + +struct BufferPair { + hb_buffer_t* guest = hb_buffer_create_nid_postfix(); + hb_buffer_t* native = hb_buffer_create(); + + BufferPair() { + Require(guest && native && guest != native); + } + + ~BufferPair() { + hb_buffer_destroy_nid_postfix(guest); + hb_buffer_destroy(native); + } + + void Reset() { + hb_buffer_reset_nid_postfix(guest); + hb_buffer_reset(native); + } + + void Add(const std::uint16_t* text, int textLength, unsigned itemOffset, int itemLength) { + hb_buffer_add_utf16_nid_postfix(guest, text, textLength, itemOffset, itemLength); + hb_buffer_add_utf16(native, text, textLength, itemOffset, itemLength); + } + + void Guess() { + hb_buffer_guess_segment_properties_nid_postfix(guest); + hb_buffer_guess_segment_properties(native); + } + + void Direction(hb_direction_t direction) { + hb_buffer_set_direction_nid_postfix(guest, direction); + hb_buffer_set_direction(native, direction); + } + + void Script(hb_script_t script) { + hb_buffer_set_script_nid_postfix(guest, script); + hb_buffer_set_script(native, script); + } + + void Compare(bool comparePositions = false) { + Require(hb_buffer_get_length_nid_postfix(guest) == hb_buffer_get_length(native)); + Require(hb_buffer_get_direction_nid_postfix(guest) == hb_buffer_get_direction(native)); + Require(hb_buffer_get_script(guest) == hb_buffer_get_script(native)); + const auto guestLanguage = hb_buffer_get_language(guest); + const auto nativeLanguage = hb_buffer_get_language(native); + Require(bool(guestLanguage) == bool(nativeLanguage)); + if (guestLanguage) Require(std::strcmp(hb_language_to_string(guestLanguage), hb_language_to_string(nativeLanguage)) == 0); + + std::array lengths{0x11223344, 0x55667788, 0x99AABBCC}; + unsigned nativeLength = 0xFFFFFFFF; + auto* infos = hb_buffer_get_glyph_infos_nid_postfix(guest, &lengths[1]); + const auto* nativeInfos = hb_buffer_get_glyph_infos(native, &nativeLength); + Require(lengths[0] == 0x11223344 && lengths[2] == 0x99AABBCC); + Require(lengths[1] == nativeLength && nativeLength == hb_buffer_get_length(native)); + Require(bool(infos) == bool(nativeInfos)); + Require(hb_buffer_get_glyph_infos_nid_postfix(guest, nullptr) == infos); + for (unsigned index = 0; index < nativeLength; ++index) { + Require(infos[index].codepoint == nativeInfos[index].codepoint); + Require(infos[index].mask == nativeInfos[index].mask); + Require(infos[index].cluster == nativeInfos[index].cluster); + Require(infos[index].var1.u32 == nativeInfos[index].var1.u32); + Require(infos[index].var2.u32 == nativeInfos[index].var2.u32); + } + + if (!comparePositions) return; + lengths[1] = 0x55667788; + auto* positions = hb_buffer_get_glyph_positions_nid_postfix(guest, &lengths[1]); + const auto* nativePositions = hb_buffer_get_glyph_positions(native, &nativeLength); + Require(lengths[0] == 0x11223344 && lengths[2] == 0x99AABBCC); + Require(lengths[1] == nativeLength && nativeLength == hb_buffer_get_length(native)); + Require(bool(positions) == bool(nativePositions)); + Require(hb_buffer_get_glyph_positions_nid_postfix(guest, nullptr) == positions); + for (unsigned index = 0; index < nativeLength; ++index) { + Require(positions[index].x_advance == nativePositions[index].x_advance); + Require(positions[index].y_advance == nativePositions[index].y_advance); + Require(positions[index].x_offset == nativePositions[index].x_offset); + Require(positions[index].y_offset == nativePositions[index].y_offset); + Require(positions[index].var.u32 == nativePositions[index].var.u32); + } + } +}; + +static void Utf16() { + BufferPair pair; + pair.Compare(); + const std::array text{'A', 0xD83D, 0xDE00, 0xD800, 'B', 0xDC00, 0, 'C', 0}; + pair.Add(text.data(), 8, 0, -1); + const auto* infos = hb_buffer_get_glyph_infos_nid_postfix(pair.guest, nullptr); + const std::array codepoints{'A', 0x1F600, 0xFFFD, 'B', 0xFFFD, 0, 'C'}; + const std::array clusters{0, 1, 3, 4, 5, 6, 7}; + Require(hb_buffer_get_length_nid_postfix(pair.guest) == codepoints.size()); + for (unsigned index = 0; index < codepoints.size(); ++index) { + Require(infos[index].codepoint == codepoints[index]); + Require(infos[index].cluster == clusters[index]); + } + pair.Compare(true); + for (int length : {0, 1, 2, 3, 6, 8, -1}) { + const unsigned size = length == -1 ? 6 : static_cast(length); + for (unsigned offset = 0; offset <= size; ++offset) { + for (unsigned count = 0; count <= size - offset; ++count) { + pair.Reset(); + pair.Add(text.data(), length, offset, static_cast(count)); + pair.Compare(); + } + pair.Reset(); + pair.Add(text.data(), length, offset, -1); + pair.Compare(); + } + } + + std::vector units(65537); + for (unsigned index = 0; index < 65536; ++index) units[index] = static_cast(index); + pair.Reset(); + pair.Add(units.data(), 65536, 0, 65536); + pair.Compare(true); + + std::mt19937 random(0x48424246); + for (unsigned iteration = 0; iteration < 1000; ++iteration) { + const unsigned size = random() % 257; + units.resize(size + 1); + for (unsigned index = 0; index < size; ++index) units[index] = static_cast(random()); + units[size] = 0; + const unsigned offset = random() % (size + 1); + const unsigned count = random() % (size - offset + 1); + pair.Reset(); + pair.Add(units.data(), static_cast(size), offset, static_cast(count)); + pair.Compare(); + pair.Add(text.data(), 8, 1, 2); + pair.Compare(); + } +} + +static void Properties() { + BufferPair pair; + pair.Guess(); + Require(hb_buffer_get_direction_nid_postfix(pair.guest) == HB_DIRECTION_LTR); + pair.Compare(); + struct ScriptCase { + std::uint16_t character; + hb_script_t script; + hb_direction_t direction; + }; + const std::array cases{{ + {'A', HB_SCRIPT_LATIN, HB_DIRECTION_LTR}, + {0x0627, HB_SCRIPT_ARABIC, HB_DIRECTION_RTL}, + {0x05D0, HB_SCRIPT_HEBREW, HB_DIRECTION_RTL}, + {0x03B1, HB_SCRIPT_GREEK, HB_DIRECTION_LTR}, + {0x0410, HB_SCRIPT_CYRILLIC, HB_DIRECTION_LTR}, + {0x0915, HB_SCRIPT_DEVANAGARI, HB_DIRECTION_LTR}, + {0x4E00, HB_SCRIPT_HAN, HB_DIRECTION_LTR}, + {0x3042, HB_SCRIPT_HIRAGANA, HB_DIRECTION_LTR} + }}; + for (const auto& test : cases) { + pair.Reset(); + const std::array text{' ', 0x0301, test.character, 'A', 0}; + pair.Add(text.data(), -1, 0, -1); + pair.Guess(); + Require(hb_buffer_get_direction_nid_postfix(pair.guest) == test.direction); + Require(hb_buffer_get_script(pair.guest) == test.script); + pair.Compare(); + pair.Guess(); + pair.Compare(); + for (auto direction : {HB_DIRECTION_LTR, HB_DIRECTION_RTL, HB_DIRECTION_TTB, HB_DIRECTION_BTT}) { + pair.Direction(direction); + pair.Guess(); + Require(hb_buffer_get_direction_nid_postfix(pair.guest) == direction); + pair.Compare(); + } + pair.Script(HB_SCRIPT_ARABIC); + pair.Direction(HB_DIRECTION_INVALID); + pair.Guess(); + Require(hb_buffer_get_direction_nid_postfix(pair.guest) == HB_DIRECTION_RTL); + pair.Compare(); + pair.Script(HB_SCRIPT_LATIN); + pair.Guess(); + Require(hb_buffer_get_direction_nid_postfix(pair.guest) == HB_DIRECTION_RTL); + pair.Compare(); + } + pair.Reset(); + Require(hb_buffer_get_direction_nid_postfix(pair.guest) == HB_DIRECTION_INVALID); + Require(hb_buffer_get_script(pair.guest) == HB_SCRIPT_INVALID); + Require(hb_buffer_get_language(pair.guest) == HB_LANGUAGE_INVALID); + pair.Compare(); +} + +static void Lifetime() { + hb_buffer_destroy_nid_postfix(nullptr); + BufferPair first; + BufferPair second; + const std::array text{'A', 'B', 'C', 0}; + for (unsigned iteration = 0; iteration < 128; ++iteration) { + first.Add(text.data(), -1, 0, -1); + first.Direction(HB_DIRECTION_RTL); + second.Reset(); + second.Add(text.data(), 3, 1, 1); + second.Script(HB_SCRIPT_LATIN); + second.Guess(); + Require(hb_buffer_get_length_nid_postfix(first.guest) == 3 * (iteration + 1)); + Require(hb_buffer_get_length_nid_postfix(second.guest) == 1); + first.Compare(); + second.Compare(); + } + first.Compare(true); + auto* infos = hb_buffer_get_glyph_infos_nid_postfix(first.guest, nullptr); + auto* nativeInfos = hb_buffer_get_glyph_infos(first.native, nullptr); + auto* positions = hb_buffer_get_glyph_positions_nid_postfix(first.guest, nullptr); + auto* nativePositions = hb_buffer_get_glyph_positions(first.native, nullptr); + infos[0].mask = nativeInfos[0].mask = 0xF00D; + infos[0].cluster = nativeInfos[0].cluster = 0xABCDEF; + positions[0].x_advance = nativePositions[0].x_advance = -1234; + positions[0].y_offset = nativePositions[0].y_offset = 5678; + first.Guess(); + first.Compare(true); + Require(hb_buffer_get_glyph_infos_nid_postfix(first.guest, nullptr) == infos); + Require(hb_buffer_get_glyph_positions_nid_postfix(first.guest, nullptr) == positions); + second.Reset(); + first.Compare(true); + first.Reset(); + first.Compare(); + first.Add(text.data(), -1, 0, -1); + first.Compare(true); + const auto* resetPositions = hb_buffer_get_glyph_positions_nid_postfix(first.guest, nullptr); + Require(resetPositions[0].x_advance == 0 && resetPositions[0].y_offset == 0); +} + +int main(int argc, char** argv) { + Require(argc == 2); + const std::string_view mode = argv[1]; + if (mode == "utf16") Utf16(); + else if (mode == "properties") Properties(); + else if (mode == "lifetime") Lifetime(); + else Require(false); + std::printf("HarfBuzz buffer %s checks passed\n", argv[1]); +} diff --git a/docs/dev/debt/libharfbuzz.md b/docs/dev/debt/libharfbuzz.md index ac79a11307..0f70a725c7 100644 --- a/docs/dev/debt/libharfbuzz.md +++ b/docs/dev/debt/libharfbuzz.md @@ -2,22 +2,11 @@ ### Callable exports -- [Callable exports](../../../core/libs/prx/libharfbuzz/Export.cpp) - 35 callable NIDs match the [public 3.20 catalogue](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libharfbuzz.c) and the [selected export metadata](https://gist.github.com/kyon-eth/510aa3fcaf09cbe8b52048b44e4e67c0/b909f17c99ecd6323c085f70064548789062d57e) from an older `libharfbuzz` image and a later `libSceVshHarfBuzz` image (SHA-256 `25af97e02c1fa8d318ff3697eb8a79057ee9b8c5e5e4ec321f25114aceb05628`). The later image has a different module and library identity; matching NIDs do not establish forwarding or equivalent behavior. The version labels are not authenticated. Prototypes and behavior are unverified; these declarations throw through `NotImplemented_nid_no_patch` and implement no runtime functionality. +- [Callable exports](../../../core/libs/prx/libharfbuzz/Export.cpp) - The remaining 24 throwing callable NIDs match the [public 3.20 catalogue](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libharfbuzz.c) and the [selected export metadata](https://gist.github.com/kyon-eth/510aa3fcaf09cbe8b52048b44e4e67c0/b909f17c99ecd6323c085f70064548789062d57e) from an older `libharfbuzz` image and a later `libSceVshHarfBuzz` image (SHA-256 `25af97e02c1fa8d318ff3697eb8a79057ee9b8c5e5e4ec321f25114aceb05628`). The later image has a different module and library identity; matching NIDs do not establish forwarding or equivalent behavior. The version labels are not authenticated. Their prototypes and behavior are unverified; these declarations throw through `NotImplemented_nid_no_patch`. | Name | NID | | --- | --- | | `hb_blob_create` | `hj1KIIN5D54` | -| `hb_buffer_add_utf16` | `P+hgu937lFs` | -| `hb_buffer_create` | `7f13XmbjTZo` | -| `hb_buffer_destroy` | `K-y1p7Z9THQ` | -| `hb_buffer_get_direction` | `0UafewjjWpM` | -| `hb_buffer_get_glyph_infos` | `ENsHmJCNG4Q` | -| `hb_buffer_get_glyph_positions` | `43mnfTQPCbw` | -| `hb_buffer_get_length` | `leHmVbjHyGw` | -| `hb_buffer_guess_segment_properties` | `DcrJr531A4w` | -| `hb_buffer_reset` | `PEggcTbDlfE` | -| `hb_buffer_set_direction` | `845S2XQdxhw` | -| `hb_buffer_set_script` | `eGbbvD8o7go` | | `hb_face_create_for_tables` | `l7GEYs5PMVU` | | `hb_face_destroy` | `ULNHQXciJ3s` | | `hb_font_create` | `FVhYusaWqA4` | @@ -41,3 +30,7 @@ | `hb_ot_layout_table_get_script_tags` | `wf6CfVJj9PQ` | | `hb_ot_tag_to_script` | `uKjh3hI1npw` | | `hb_shape` | `dM3hP4y99rM` | + +### Functional + +- [The eleven buffer exports](../../../core/libs/prx/libharfbuzz/Buffer.cpp) have no console or title verification. Their signatures, 20-byte glyph records, Unicode properties, UTF-16 decoding and buffer behavior follow the pinned [HarfBuzz 10.0.1](https://github.com/harfbuzz/harfbuzz/tree/a1d9bfe62818ef0fa9cf63b6e6d51436b1c93cbc) public API and backend; the console revision and its Unicode data are unknown. Buffers are opaque host allocations, not console object layouts. Returned arrays belong to that backend and remain valid only until the buffer contents change. Segment-property guessing uses the host process's default language, not a console language setting. Handles from another HarfBuzz instance cannot be mixed with these buffers. Null buffer handles throw except for destruction, which accepts null as the public API does; console null-handle behavior is unverified. Shaping, fonts, faces and guest callbacks remain throwing exports.