Accessibility number formatting improvements for Windows (flutter/engine#29773)

This adds some accessibility improvements for reading out numbers. Currently this code is only used on Windows.

Fixes flutter#78460
This commit is contained in:
Greg Spencer
2021-12-09 13:04:00 -08:00
committed by GitHub
parent 42e1552621
commit 3911c3d5d8
4 changed files with 80 additions and 16 deletions

View File

@@ -53,5 +53,6 @@ source_set("base") {
public_deps = [
"numerics",
"//flutter/third_party/accessibility/ax_build",
"//third_party/dart/runtime/third_party/double-conversion/src:libdouble_conversion",
]
}

View File

@@ -4,12 +4,15 @@
#include "string_utils.h"
#include <array>
#include <cctype>
#include <codecvt>
#include <locale>
#include <regex>
#include <sstream>
#include "third_party/dart/runtime/third_party/double-conversion/src/double-conversion.h"
#if defined(_WIN32)
#include "base/win/string_conversion.h"
#endif
@@ -18,6 +21,48 @@
namespace base {
using double_conversion::DoubleToStringConverter;
using double_conversion::StringBuilder;
namespace {
constexpr char kExponentChar = 'e';
constexpr char kInfinitySymbol[] = "Infinity";
constexpr char kNaNSymbol[] = "NaN";
// The number of digits after the decimal we allow before switching to
// exponential representation.
constexpr int kDecimalInShortestLow = -6;
// The number of digits before the decimal we allow before switching to
// exponential representation.
constexpr int kDecimalInShortestHigh = 12;
constexpr int kConversionFlags =
DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN;
const DoubleToStringConverter& GetDoubleToStringConverter() {
static DoubleToStringConverter converter(
kConversionFlags, kInfinitySymbol, kNaNSymbol, kExponentChar,
kDecimalInShortestLow, kDecimalInShortestHigh, 0, 0);
return converter;
}
std::string NumberToStringImpl(double number, bool is_single_precision) {
if (number == 0.0) {
return "0";
}
constexpr int kBufferSize = 128;
std::array<char, kBufferSize> char_buffer;
StringBuilder builder(char_buffer.data(), char_buffer.size());
if (is_single_precision) {
GetDoubleToStringConverter().ToShortestSingle(static_cast<float>(number),
&builder);
} else {
GetDoubleToStringConverter().ToShortest(number, &builder);
}
return std::string(char_buffer.data(), builder.position());
}
} // namespace
std::u16string ASCIIToUTF16(std::string src) {
return std::u16string(src.begin(), src.end());
}
@@ -73,15 +118,11 @@ std::string NumberToString(unsigned int number) {
}
std::string NumberToString(float number) {
// TODO(gw280): Format decimals to the shortest reasonable representation.
// See: https://github.com/flutter/flutter/issues/78460
return std::to_string(number);
return NumberToStringImpl(number, true);
}
std::string NumberToString(double number) {
// TODO(gw280): Format decimals to the shortest reasonable representation.
// See: https://github.com/flutter/flutter/issues/78460
return std::to_string(number);
return NumberToStringImpl(number, false);
}
std::string JoinString(std::vector<std::string> tokens, std::string delimiter) {

View File

@@ -29,9 +29,9 @@ std::string UTF16ToUTF8(std::u16string src);
std::u16string WideToUTF16(const std::wstring& src);
std::wstring UTF16ToWide(const std::u16string& src);
std::u16string NumberToString16(float number);
std::u16string NumberToString16(unsigned int number);
std::u16string NumberToString16(int32_t number);
std::u16string NumberToString16(float number);
std::u16string NumberToString16(double number);
std::string NumberToString(unsigned int number);

View File

@@ -6,6 +6,7 @@
#include <cerrno>
#include <cstddef>
#include <string>
#include "base/logging.h"
#include "gtest/gtest.h"
@@ -42,17 +43,38 @@ TEST(StringUtilsTest, canUTF16ToUTF8) {
}
TEST(StringUtilsTest, canNumberToString16) {
float number = 1.123;
EXPECT_EQ(NumberToString16(number).compare(u"1.123000"), 0);
EXPECT_EQ(NumberToString16(1.123f), std::u16string(u"1.123"));
}
TEST(StringUtilsTest, canNumberToString) {
float f = 1.123;
EXPECT_EQ(NumberToString(f).compare("1.123000"), 0);
unsigned int s = 11;
EXPECT_EQ(NumberToString(s).compare("11"), 0);
int32_t i = -23;
EXPECT_EQ(NumberToString(i).compare("-23"), 0);
TEST(StringUtilsTest, numberToStringSimplifiesOutput) {
EXPECT_STREQ(NumberToString(0.0).c_str(), "0");
EXPECT_STREQ(NumberToString(0.0f).c_str(), "0");
EXPECT_STREQ(NumberToString(1.123).c_str(), "1.123");
EXPECT_STREQ(NumberToString(1.123f).c_str(), "1.123");
EXPECT_STREQ(NumberToString(-1.123).c_str(), "-1.123");
EXPECT_STREQ(NumberToString(-1.123f).c_str(), "-1.123");
EXPECT_STREQ(NumberToString(1.00001).c_str(), "1.00001");
EXPECT_STREQ(NumberToString(1.00001f).c_str(), "1.00001");
EXPECT_STREQ(NumberToString(1000.000001).c_str(), "1000.000001");
EXPECT_STREQ(NumberToString(10.00001f).c_str(), "10.00001");
EXPECT_STREQ(NumberToString(1.0 + 1e-8).c_str(), "1.00000001");
EXPECT_STREQ(NumberToString(1.0f + 1e-8f).c_str(), "1");
EXPECT_STREQ(NumberToString(1e-6).c_str(), "0.000001");
EXPECT_STREQ(NumberToString(1e-6f).c_str(), "0.000001");
EXPECT_STREQ(NumberToString(1e-8).c_str(), "1e-8");
EXPECT_STREQ(NumberToString(1e-8f).c_str(), "1e-8");
EXPECT_STREQ(NumberToString(100.0).c_str(), "100");
EXPECT_STREQ(NumberToString(100.0f).c_str(), "100");
EXPECT_STREQ(NumberToString(-1.0 - 1e-7).c_str(), "-1.0000001");
EXPECT_STREQ(NumberToString(-1.0f - 1e-7f).c_str(), "-1.0000001");
EXPECT_STREQ(NumberToString(0.00000012345678).c_str(), "1.2345678e-7");
// Difference in output is due to differences in double and float precision.
EXPECT_STREQ(NumberToString(0.00000012345678f).c_str(), "1.2345679e-7");
EXPECT_STREQ(NumberToString(-0.00000012345678).c_str(), "-1.2345678e-7");
// Difference in output is due to differences in double and float precision.
EXPECT_STREQ(NumberToString(-0.00000012345678f).c_str(), "-1.2345679e-7");
EXPECT_STREQ(NumberToString(static_cast<unsigned int>(11)).c_str(), "11");
EXPECT_STREQ(NumberToString(static_cast<int32_t>(-23)).c_str(), "-23");
}
} // namespace base