TextMeasureCache.h 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #pragma once
  8. #include <react/attributedstring/AttributedString.h>
  9. #include <react/attributedstring/ParagraphAttributes.h>
  10. #include <react/core/LayoutConstraints.h>
  11. #include <react/utils/FloatComparison.h>
  12. #include <react/utils/SimpleThreadSafeCache.h>
  13. namespace facebook {
  14. namespace react {
  15. /*
  16. * Describes a result of text measuring.
  17. */
  18. class TextMeasurement final {
  19. public:
  20. class Attachment final {
  21. public:
  22. Rect frame;
  23. bool isClipped;
  24. };
  25. using Attachments = std::vector<Attachment>;
  26. Size size;
  27. Attachments attachments;
  28. };
  29. // The Key type that is used for Text Measure Cache.
  30. // The equivalence and hashing operations of this are defined to respect the
  31. // nature of text measuring.
  32. class TextMeasureCacheKey final {
  33. public:
  34. AttributedString attributedString{};
  35. ParagraphAttributes paragraphAttributes{};
  36. LayoutConstraints layoutConstraints{};
  37. };
  38. /*
  39. * Maximum size of the Cache.
  40. * The number was empirically chosen based on approximation of an average amount
  41. * of meaningful measures per surface.
  42. */
  43. constexpr auto kSimpleThreadSafeCacheSizeCap = size_t{256};
  44. /*
  45. * Thread-safe, evicting hash table designed to store text measurement
  46. * information.
  47. */
  48. using TextMeasureCache = SimpleThreadSafeCache<
  49. TextMeasureCacheKey,
  50. TextMeasurement,
  51. kSimpleThreadSafeCacheSizeCap>;
  52. inline bool areTextAttributesEquivalentLayoutWise(
  53. TextAttributes const &lhs,
  54. TextAttributes const &rhs) {
  55. // Here we check all attributes that affect layout metrics and don't check any
  56. // attributes that affect only a decorative aspect of displayed text (like
  57. // colors).
  58. return std::tie(
  59. lhs.fontFamily,
  60. lhs.fontWeight,
  61. lhs.fontStyle,
  62. lhs.fontVariant,
  63. lhs.allowFontScaling,
  64. lhs.alignment) ==
  65. std::tie(
  66. rhs.fontFamily,
  67. rhs.fontWeight,
  68. rhs.fontStyle,
  69. rhs.fontVariant,
  70. rhs.allowFontScaling,
  71. rhs.alignment) &&
  72. floatEquality(lhs.fontSize, rhs.fontSize) &&
  73. floatEquality(lhs.fontSizeMultiplier, rhs.fontSizeMultiplier) &&
  74. floatEquality(lhs.letterSpacing, rhs.letterSpacing) &&
  75. floatEquality(lhs.lineHeight, rhs.lineHeight);
  76. }
  77. inline size_t textAttributesHashLayoutWise(
  78. TextAttributes const &textAttributes) {
  79. // Taking into account the same props as
  80. // `areTextAttributesEquivalentLayoutWise` mentions.
  81. return folly::hash::hash_combine(
  82. 0,
  83. textAttributes.fontFamily,
  84. textAttributes.fontSize,
  85. textAttributes.fontSizeMultiplier,
  86. textAttributes.fontWeight,
  87. textAttributes.fontStyle,
  88. textAttributes.fontVariant,
  89. textAttributes.allowFontScaling,
  90. textAttributes.letterSpacing,
  91. textAttributes.lineHeight,
  92. textAttributes.alignment);
  93. }
  94. inline bool areAttributedStringFragmentsEquivalentLayoutWise(
  95. AttributedString::Fragment const &lhs,
  96. AttributedString::Fragment const &rhs) {
  97. return lhs.string == rhs.string &&
  98. areTextAttributesEquivalentLayoutWise(
  99. lhs.textAttributes, rhs.textAttributes) &&
  100. // LayoutMetrics of an attachment fragment affects the size of a measured
  101. // attributed string.
  102. (!lhs.isAttachment() ||
  103. (lhs.parentShadowView.layoutMetrics ==
  104. rhs.parentShadowView.layoutMetrics));
  105. }
  106. inline size_t textAttributesHashLayoutWise(
  107. AttributedString::Fragment const &fragment) {
  108. // Here we are not taking `isAttachment` and `layoutMetrics` into account
  109. // because they are logically interdependent and this can break an invariant
  110. // between hash and equivalence functions (and cause cache misses).
  111. return folly::hash::hash_combine(
  112. 0,
  113. fragment.string,
  114. textAttributesHashLayoutWise(fragment.textAttributes));
  115. }
  116. inline bool areAttributedStringsEquivalentLayoutWise(
  117. AttributedString const &lhs,
  118. AttributedString const &rhs) {
  119. auto &lhsFragment = lhs.getFragments();
  120. auto &rhsFragment = rhs.getFragments();
  121. if (lhsFragment.size() != rhsFragment.size()) {
  122. return false;
  123. }
  124. auto size = lhsFragment.size();
  125. for (auto i = size_t{0}; i < size; i++) {
  126. if (!areAttributedStringFragmentsEquivalentLayoutWise(
  127. lhsFragment.at(i), rhsFragment.at(i))) {
  128. return false;
  129. }
  130. }
  131. return true;
  132. }
  133. inline size_t textAttributedStringHashLayoutWise(
  134. AttributedString const &attributedString) {
  135. auto seed = size_t{0};
  136. for (auto const &fragment : attributedString.getFragments()) {
  137. seed =
  138. folly::hash::hash_combine(seed, textAttributesHashLayoutWise(fragment));
  139. }
  140. return seed;
  141. }
  142. inline bool operator==(
  143. TextMeasureCacheKey const &lhs,
  144. TextMeasureCacheKey const &rhs) {
  145. return areAttributedStringsEquivalentLayoutWise(
  146. lhs.attributedString, rhs.attributedString) &&
  147. lhs.paragraphAttributes == rhs.paragraphAttributes &&
  148. lhs.layoutConstraints.maximumSize.width ==
  149. rhs.layoutConstraints.maximumSize.width;
  150. }
  151. inline bool operator!=(
  152. TextMeasureCacheKey const &lhs,
  153. TextMeasureCacheKey const &rhs) {
  154. return !(lhs == rhs);
  155. }
  156. } // namespace react
  157. } // namespace facebook
  158. namespace std {
  159. template <>
  160. struct hash<facebook::react::TextMeasureCacheKey> {
  161. size_t operator()(facebook::react::TextMeasureCacheKey const &key) const {
  162. return folly::hash::hash_combine(
  163. 0,
  164. textAttributedStringHashLayoutWise(key.attributedString),
  165. key.paragraphAttributes,
  166. key.layoutConstraints.maximumSize.width);
  167. }
  168. };
  169. } // namespace std