Files
Fluxup_PAP/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/TextMeasureCache.h
2026-03-10 16:18:05 +00:00

300 lines
9.5 KiB
C++

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <react/renderer/attributedstring/AttributedString.h>
#include <react/renderer/attributedstring/ParagraphAttributes.h>
#include <react/renderer/core/LayoutConstraints.h>
#include <react/utils/FloatComparison.h>
#include <react/utils/SimpleThreadSafeCache.h>
#include <react/utils/hash_combine.h>
namespace facebook::react {
struct LineMeasurement {
std::string text;
Rect frame;
Float descender;
Float capHeight;
Float ascender;
Float xHeight;
LineMeasurement(std::string text, Rect frame, Float descender, Float capHeight, Float ascender, Float xHeight);
LineMeasurement(const folly::dynamic &data);
bool operator==(const LineMeasurement &rhs) const;
static inline Float baseline(const std::vector<LineMeasurement> &lines)
{
if (!lines.empty()) {
return lines[0].ascender;
}
return 0;
}
};
using LinesMeasurements = std::vector<LineMeasurement>;
/*
* Describes a result of text measuring.
*/
class TextMeasurement final {
public:
class Attachment final {
public:
Rect frame;
bool isClipped;
};
using Attachments = std::vector<Attachment>;
Size size;
Attachments attachments;
};
// The Key type that is used for Text Measure Cache.
// The equivalence and hashing operations of this are defined to respect the
// nature of text measuring.
class TextMeasureCacheKey final {
public:
AttributedString attributedString{};
ParagraphAttributes paragraphAttributes{};
LayoutConstraints layoutConstraints{};
};
// The Key type that is used for Line Measure Cache.
// The equivalence and hashing operations of this are defined to respect the
// nature of text measuring.
class LineMeasureCacheKey final {
public:
AttributedString attributedString{};
ParagraphAttributes paragraphAttributes{};
Size size{};
};
/**
* Cache key, mapping an AttributedString under given constraints, to a prepared
* (laid out and drawable) representation of the text.
*/
class PreparedTextCacheKey final {
public:
AttributedString attributedString{};
ParagraphAttributes paragraphAttributes{};
LayoutConstraints layoutConstraints{};
};
/*
* Maximum size of the Cache.
* The number was empirically chosen based on approximation of an average amount
* of meaningful measures per surface.
*/
constexpr auto kSimpleThreadSafeCacheSizeCap = size_t{1024};
/*
* Thread-safe, evicting hash table designed to store text measurement
* information.
*/
using TextMeasureCache = SimpleThreadSafeCache<TextMeasureCacheKey, TextMeasurement, kSimpleThreadSafeCacheSizeCap>;
/*
* Thread-safe, evicting hash table designed to store line measurement
* information.
*/
using LineMeasureCache = SimpleThreadSafeCache<LineMeasureCacheKey, LinesMeasurements, kSimpleThreadSafeCacheSizeCap>;
inline bool areTextAttributesEquivalentLayoutWise(const TextAttributes &lhs, const TextAttributes &rhs)
{
// Here we check all attributes that affect layout metrics and don't check any
// attributes that affect only a decorative aspect of displayed text (like
// colors).
return std::tie(
lhs.fontFamily,
lhs.fontWeight,
lhs.fontStyle,
lhs.fontVariant,
lhs.allowFontScaling,
lhs.dynamicTypeRamp,
lhs.alignment) ==
std::tie(
rhs.fontFamily,
rhs.fontWeight,
rhs.fontStyle,
rhs.fontVariant,
rhs.allowFontScaling,
rhs.dynamicTypeRamp,
rhs.alignment) &&
floatEquality(lhs.fontSize, rhs.fontSize) && floatEquality(lhs.fontSizeMultiplier, rhs.fontSizeMultiplier) &&
floatEquality(lhs.letterSpacing, rhs.letterSpacing) && floatEquality(lhs.lineHeight, rhs.lineHeight);
}
inline size_t textAttributesHashLayoutWise(const TextAttributes &textAttributes)
{
// Taking into account the same props as
// `areTextAttributesEquivalentLayoutWise` mentions.
return facebook::react::hash_combine(
textAttributes.fontFamily,
textAttributes.fontSize,
textAttributes.fontSizeMultiplier,
textAttributes.fontWeight,
textAttributes.fontStyle,
textAttributes.fontVariant,
textAttributes.allowFontScaling,
textAttributes.dynamicTypeRamp,
textAttributes.letterSpacing,
textAttributes.lineHeight,
textAttributes.alignment);
}
inline bool areAttributedStringFragmentsEquivalentLayoutWise(
const AttributedString::Fragment &lhs,
const AttributedString::Fragment &rhs)
{
return lhs.string == rhs.string && areTextAttributesEquivalentLayoutWise(lhs.textAttributes, rhs.textAttributes) &&
// LayoutMetrics of an attachment fragment affects the size of a measured
// attributed string.
(!lhs.isAttachment() || (lhs.parentShadowView.layoutMetrics == rhs.parentShadowView.layoutMetrics));
}
inline bool areAttributedStringFragmentsEquivalentDisplayWise(
const AttributedString::Fragment &lhs,
const AttributedString::Fragment &rhs)
{
return lhs.isContentEqual(rhs) &&
// LayoutMetrics of an attachment fragment affects the size of a measured
// attributed string.
(!lhs.isAttachment() || (lhs.parentShadowView.layoutMetrics == rhs.parentShadowView.layoutMetrics));
}
inline size_t attributedStringFragmentHashLayoutWise(const AttributedString::Fragment &fragment)
{
// Here we are not taking `isAttachment` and `layoutMetrics` into account
// because they are logically interdependent and this can break an invariant
// between hash and equivalence functions (and cause cache misses).
return facebook::react::hash_combine(fragment.string, textAttributesHashLayoutWise(fragment.textAttributes));
}
inline size_t attributedStringFragmentHashDisplayWise(const AttributedString::Fragment &fragment)
{
// Here we are not taking `isAttachment` and `layoutMetrics` into account
// because they are logically interdependent and this can break an invariant
// between hash and equivalence functions (and cause cache misses).
return facebook::react::hash_combine(fragment.string, fragment.textAttributes);
}
inline bool areAttributedStringsEquivalentLayoutWise(const AttributedString &lhs, const AttributedString &rhs)
{
auto &lhsFragment = lhs.getFragments();
auto &rhsFragment = rhs.getFragments();
if (lhsFragment.size() != rhsFragment.size()) {
return false;
}
auto size = lhsFragment.size();
for (auto i = size_t{0}; i < size; i++) {
if (!areAttributedStringFragmentsEquivalentLayoutWise(lhsFragment.at(i), rhsFragment.at(i))) {
return false;
}
}
return true;
}
inline bool areAttributedStringsEquivalentDisplayWise(const AttributedString &lhs, const AttributedString &rhs)
{
auto &lhsFragment = lhs.getFragments();
auto &rhsFragment = rhs.getFragments();
if (lhsFragment.size() != rhsFragment.size()) {
return false;
}
auto size = lhsFragment.size();
for (size_t i = 0; i < size; i++) {
if (!areAttributedStringFragmentsEquivalentDisplayWise(lhsFragment.at(i), rhsFragment.at(i))) {
return false;
}
}
return true;
}
inline size_t attributedStringHashLayoutWise(const AttributedString &attributedString)
{
auto seed = size_t{0};
for (const auto &fragment : attributedString.getFragments()) {
facebook::react::hash_combine(seed, attributedStringFragmentHashLayoutWise(fragment));
}
return seed;
}
inline size_t attributedStringHashDisplayWise(const AttributedString &attributedString)
{
size_t seed = 0;
for (const auto &fragment : attributedString.getFragments()) {
facebook::react::hash_combine(seed, attributedStringFragmentHashDisplayWise(fragment));
}
return seed;
}
inline bool operator==(const TextMeasureCacheKey &lhs, const TextMeasureCacheKey &rhs)
{
return areAttributedStringsEquivalentLayoutWise(lhs.attributedString, rhs.attributedString) &&
lhs.paragraphAttributes == rhs.paragraphAttributes && lhs.layoutConstraints == rhs.layoutConstraints;
}
inline bool operator==(const LineMeasureCacheKey &lhs, const LineMeasureCacheKey &rhs)
{
return areAttributedStringsEquivalentLayoutWise(lhs.attributedString, rhs.attributedString) &&
lhs.paragraphAttributes == rhs.paragraphAttributes && lhs.size == rhs.size;
}
inline bool operator==(const PreparedTextCacheKey &lhs, const PreparedTextCacheKey &rhs)
{
return areAttributedStringsEquivalentDisplayWise(lhs.attributedString, rhs.attributedString) &&
lhs.paragraphAttributes == rhs.paragraphAttributes && lhs.layoutConstraints == rhs.layoutConstraints;
}
} // namespace facebook::react
namespace std {
template <>
struct hash<facebook::react::TextMeasureCacheKey> {
size_t operator()(const facebook::react::TextMeasureCacheKey &key) const
{
return facebook::react::hash_combine(
attributedStringHashLayoutWise(key.attributedString), key.paragraphAttributes, key.layoutConstraints);
}
};
template <>
struct hash<facebook::react::LineMeasureCacheKey> {
size_t operator()(const facebook::react::LineMeasureCacheKey &key) const
{
return facebook::react::hash_combine(
attributedStringHashLayoutWise(key.attributedString), key.paragraphAttributes, key.size);
}
};
template <>
struct hash<facebook::react::PreparedTextCacheKey> {
size_t operator()(const facebook::react::PreparedTextCacheKey &key) const
{
return facebook::react::hash_combine(
attributedStringHashDisplayWise(key.attributedString), key.paragraphAttributes, key.layoutConstraints);
}
};
} // namespace std