Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions experiments/WebScene.NativeEngine.Probe/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ include(CTest)
include(FetchContent)

if(BUILD_TESTING)
add_executable(webscene_css_ascii_number_tests
tests/css_ascii_number_tests.cpp)
target_compile_features(webscene_css_ascii_number_tests PRIVATE cxx_std_20)
target_include_directories(webscene_css_ascii_number_tests PRIVATE native)
add_test(NAME webscene_css_ascii_number_tests
COMMAND webscene_css_ascii_number_tests)
set_tests_properties(webscene_css_ascii_number_tests PROPERTIES
LABELS "native;css;effects;portability;performance" TIMEOUT 10)
add_executable(webscene_file_panel_v2_contract_tests
tests/file_panel_v2_contract_tests.cpp)
target_compile_features(webscene_file_panel_v2_contract_tests PRIVATE cxx_std_20)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#pragma once

#include <cmath>
#include <cstddef>
#include <limits>
#include <optional>
#include <string_view>

namespace webscene_native::css {

struct ascii_number_prefix final {
float value{};
size_t consumed{};
};

// Parse the locale-independent CSS <number> prefix needed by retained paint.
// Returning the consumed prefix lets callers validate their property-specific
// unit without copying or reading past the supplied string_view.
inline std::optional<ascii_number_prefix> parse_ascii_number_prefix(
std::string_view input) noexcept
{
if (input.empty()) return std::nullopt;
size_t cursor = 0U;
auto negative = false;
if (input[cursor] == '+' || input[cursor] == '-') {
negative = input[cursor] == '-';
if (++cursor == input.size()) return std::nullopt;
}

long double significand = 0.0L;
auto fractional_digits = 0;
auto digits = 0U;
auto significand_overflow = false;
const auto append_digit = [&](unsigned digit) {
++digits;
if (significand
> (std::numeric_limits<long double>::max() - digit) / 10.0L) {
significand_overflow = true;
return;
}
significand = significand * 10.0L + static_cast<long double>(digit);
};

while (cursor < input.size() && input[cursor] >= '0' && input[cursor] <= '9') {
append_digit(static_cast<unsigned>(input[cursor] - '0'));
++cursor;
}
if (cursor < input.size() && input[cursor] == '.') {
++cursor;
while (cursor < input.size() && input[cursor] >= '0' && input[cursor] <= '9') {
append_digit(static_cast<unsigned>(input[cursor] - '0'));
if (fractional_digits < 100000) ++fractional_digits;
else significand_overflow = true;
++cursor;
}
}
if (digits == 0U || significand_overflow) return std::nullopt;

auto exponent = 0;
auto exponent_negative = false;
auto exponent_overflow = false;
if (cursor < input.size() && (input[cursor] == 'e' || input[cursor] == 'E')) {
++cursor;
if (cursor < input.size() && (input[cursor] == '+' || input[cursor] == '-')) {
exponent_negative = input[cursor] == '-';
++cursor;
}
const auto exponent_start = cursor;
while (cursor < input.size() && input[cursor] >= '0' && input[cursor] <= '9') {
const auto digit = static_cast<int>(input[cursor] - '0');
if (exponent > 10000) exponent_overflow = true;
else exponent = exponent * 10 + digit;
++cursor;
}
if (cursor == exponent_start) return std::nullopt;
}
if (significand == 0.0L) {
return ascii_number_prefix{negative ? -0.0F : 0.0F, cursor};
}
if (exponent_overflow) return std::nullopt;
if (exponent_negative) exponent = -exponent;

const auto decimal_exponent = exponent - fractional_digits;
if (decimal_exponent > 10000 || decimal_exponent < -10000) return std::nullopt;
auto scaled = significand * std::pow(10.0L, static_cast<long double>(decimal_exponent));
if (negative) scaled = -scaled;
if (!std::isfinite(scaled)
|| std::abs(scaled) > std::numeric_limits<float>::max()) return std::nullopt;
const auto result = static_cast<float>(scaled);
if (!std::isfinite(result) || (result == 0.0F && significand != 0.0L)) {
return std::nullopt;
}
return ascii_number_prefix{result, cursor};
}

struct ascii_inset_length final {
float value{};
bool percent{};
};

inline std::optional<ascii_inset_length> parse_ascii_inset_length(
std::string_view token) noexcept
{
const auto parsed = parse_ascii_number_prefix(token);
if (!parsed) return std::nullopt;
const auto suffix = token.substr(parsed->consumed);
if (suffix.empty()) {
if (std::abs(parsed->value) > 0.0001F) return std::nullopt;
return ascii_inset_length{0.0F, false};
}
if (suffix.size() == 2U
&& (suffix[0] == 'p' || suffix[0] == 'P')
&& (suffix[1] == 'x' || suffix[1] == 'X')) {
return ascii_inset_length{parsed->value, false};
}
if (suffix == "%") return ascii_inset_length{parsed->value, true};
return std::nullopt;
}

} // namespace webscene_native::css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "webscene_native_dom.h"
#include "webscene_css_ascii_number.h"

#include <algorithm>
#include <array>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,10 +420,6 @@ void native_document::append_scene(
? node.style.visibility_hidden
: inherited_visibility_hidden;
const auto paint_self = !visibility_hidden && paint_target == nullptr && !pseudo_only;
struct inset_clip_length final {
float value{};
bool percent{};
};
const auto ascii_equal = [](char left, char right) {
const auto lower = [](unsigned char character) {
return character >= 'A' && character <= 'Z'
Expand All @@ -433,30 +429,6 @@ void native_document::append_scene(
return lower(static_cast<unsigned char>(left))
== lower(static_cast<unsigned char>(right));
};
const auto parse_inset_clip_length = [](std::string_view token)
-> std::optional<inset_clip_length> {
if (token.empty()) return std::nullopt;
auto first = token.data();
if (*first == '+') ++first;
if (first == token.data() + token.size()) return std::nullopt;
float number = 0.0F;
const auto parsed = std::from_chars(first, token.data() + token.size(), number);
if (parsed.ec != std::errc{} || parsed.ptr == first || !std::isfinite(number)) {
return std::nullopt;
}
const std::string_view suffix(parsed.ptr, token.data() + token.size() - parsed.ptr);
if (suffix.empty()) {
if (std::abs(number) > 0.0001F) return std::nullopt;
return inset_clip_length{0.0F, false};
}
if (suffix.size() == 2U
&& (suffix[0] == 'p' || suffix[0] == 'P')
&& (suffix[1] == 'x' || suffix[1] == 'X')) {
return inset_clip_length{number, false};
}
if (suffix == "%") return inset_clip_length{number, true};
return std::nullopt;
};
const auto resolve_inset_clip = [&]() -> std::optional<layout_rect> {
const auto& effects = node.style.textual().effect_values;
const auto known = effects.find("clip-path");
Expand All @@ -468,7 +440,7 @@ void native_document::append_scene(
if (!ascii_equal(value[index], inset_prefix[index])) return std::nullopt;
}
const std::string_view arguments(value.data() + 6U, value.size() - 7U);
std::array<inset_clip_length, 4> parsed{};
std::array<css::ascii_inset_length, 4> parsed{};
size_t count = 0U;
size_t cursor = 0U;
while (cursor < arguments.size()) {
Expand All @@ -480,7 +452,7 @@ void native_document::append_scene(
cursor,
end == std::string_view::npos ? arguments.size() - cursor : end - cursor);
if (count == parsed.size()) return std::nullopt;
const auto length = parse_inset_clip_length(token);
const auto length = css::parse_ascii_inset_length(token);
if (!length.has_value()) return std::nullopt;
parsed[count++] = *length;
if (end == std::string_view::npos) break;
Expand All @@ -491,7 +463,7 @@ void native_document::append_scene(
const auto right = count > 1U ? parsed[1] : parsed[0];
const auto bottom = count > 2U ? parsed[2] : parsed[0];
const auto left = count > 3U ? parsed[3] : count > 1U ? parsed[1] : parsed[0];
const auto pixels = [](inset_clip_length length, float available) {
const auto pixels = [](css::ascii_inset_length length, float available) {
return length.percent ? available * length.value / 100.0F : length.value;
};
const auto top_pixels = pixels(top, node.layout.height);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include "webscene_css_ascii_number.h"

#include <chrono>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <string_view>

namespace {
[[noreturn]] void fail(std::string_view message)
{
std::cerr << "webscene_css_ascii_number_tests: " << message << '\n';
std::exit(1);
}

void require(bool condition, std::string_view message)
{
if (!condition) fail(message);
}

void require_length(std::string_view source, float expected, bool percent)
{
const auto parsed = webscene_native::css::parse_ascii_inset_length(source);
require(parsed.has_value(), "portable inset length was rejected");
require(std::abs(parsed->value - expected) < 0.0001F,
"portable inset length value differed");
require(parsed->percent == percent, "portable inset length unit differed");
}
} // namespace

int main()
{
require_length("0", 0.0F, false);
require_length("+0PX", 0.0F, false);
require_length("0e999999px", 0.0F, false);
require_length("-0e-999999%", -0.0F, true);
require_length("-.5px", -0.5F, false);
require_length("1.25e2%", 125.0F, true);
require_length("1e-2px", 0.01F, false);
for (const auto invalid : {
std::string_view{}, std::string_view{"+"}, std::string_view{"."},
std::string_view{"1"}, std::string_view{"1e+px"}, std::string_view{"nanpx"},
std::string_view{"infpx"}, std::string_view{"1e9999px"},
std::string_view{"1px "}, std::string_view{"1em"},
std::string_view{"1\0px", 4U}}) {
require(!webscene_native::css::parse_ascii_inset_length(invalid).has_value(),
"invalid portable inset length was accepted");
}

const auto started = std::chrono::steady_clock::now();
auto checksum = 0.0F;
for (auto iteration = 0U; iteration < 100000U; ++iteration) {
const auto parsed = webscene_native::css::parse_ascii_inset_length("-12.5e-1px");
require(parsed.has_value(), "repeated portable parse failed");
checksum += parsed->value;
}
const auto elapsed = std::chrono::duration<double, std::milli>(
std::chrono::steady_clock::now() - started).count();
require(std::abs(checksum + 125000.0F) < 0.01F, "portable parse checksum differed");
require(elapsed < 500.0, "100000 portable parses exceeded 500 ms");
std::cout << "css-ascii-number parses=100000 elapsed-ms=" << elapsed << '\n';
}
Loading