Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement baseline alignment function on the new architecture #45102

Closed
Closed
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
eaa1752
Setup baseline func
j-piasecki Jun 17, 2024
ce3d20b
Add context
j-piasecki Jun 17, 2024
0f61afb
Implement iOS
j-piasecki Jun 17, 2024
346b0e1
Implement Android
j-piasecki Jun 17, 2024
b29ab4a
Extract the common code
j-piasecki Jun 20, 2024
9d7f269
Use correct method name
j-piasecki Jun 21, 2024
c8c08c6
Use `lastBaseline` method
j-piasecki Jun 21, 2024
07f5ffe
Restore format
j-piasecki Jun 21, 2024
62931c9
Rename baseline getter
j-piasecki Jun 21, 2024
4aa7b46
Adjust indent
j-piasecki Jun 21, 2024
00ccf02
Remove duplicate method
j-piasecki Jun 21, 2024
f576413
Unify baseline functions
j-piasecki Jun 25, 2024
0a0d4b5
Align RNTester examples
j-piasecki Jun 25, 2024
57b7fbe
Remove interleaving
j-piasecki Jun 25, 2024
bf798be
I removed too much
j-piasecki Jun 25, 2024
0ea302c
Implement `baseline` on top of `measureLines`
j-piasecki Jun 26, 2024
c29777c
Remove `getLastBaseline` fromUnify baseline calculations
j-piasecki Jun 26, 2024
02b109d
Use first line as baseline
j-piasecki Jun 27, 2024
fa62579
Use `getContentWithMeasuredAttachments`
j-piasecki Jun 28, 2024
804eb85
Change how line measurements are done on iOS
j-piasecki Jul 1, 2024
7e25a42
Merge branch 'main' into @jpiasecki/baseline-func-new-arch
j-piasecki Jul 1, 2024
c67f15c
Use lineRect
j-piasecki Jul 1, 2024
1e8a5be
Remove todos
j-piasecki Jul 2, 2024
bfd6008
Add multiline text example
j-piasecki Jul 2, 2024
e7f695c
Fix formatting
j-piasecki Jul 3, 2024
b7be700
Use public yoga apis
j-piasecki Jul 4, 2024
418f1e0
Remove unused method
j-piasecki Jul 4, 2024
3fca450
`getLastBaseline` -> `baseline`
j-piasecki Jul 4, 2024
c6ac596
Don't copy string multiple times
j-piasecki Jul 4, 2024
08c8d8d
Inline `doMeasureLines`
j-piasecki Jul 4, 2024
1235a96
Remove unused font variable
j-piasecki Jul 4, 2024
3b97820
Use `Float` instead of `float`
j-piasecki Jul 4, 2024
828deaa
Use line fragment rect
j-piasecki Jul 8, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,35 @@ Size ParagraphShadowNode::measureContent(
.size;
}

Float ParagraphShadowNode::baseline(
const LayoutContext& layoutContext,
Size size) const {
auto layoutMetrics = getLayoutMetrics();
auto layoutConstraints =
LayoutConstraints{size, size, layoutMetrics.layoutDirection};
auto content =
getContentWithMeasuredAttachments(layoutContext, layoutConstraints);
auto attributedString = content.attributedString;

if (attributedString.isEmpty()) {
// Note: `zero-width space` is insufficient in some cases (e.g. when we need
// to measure the "height" of the font).
// TODO T67606511: We will redefine the measurement of empty strings as part
// of T67606511
auto string = BaseTextShadowNode::getEmptyPlaceholder();
auto textAttributes = TextAttributes::defaultTextAttributes();
textAttributes.fontSizeMultiplier = layoutContext.fontSizeMultiplier;
textAttributes.apply(getConcreteProps().textAttributes);
attributedString.appendFragment({string, textAttributes, {}});
}

return textLayoutManager_
->baseline(
attributedString,
getConcreteProps().paragraphAttributes,
size);
}

void ParagraphShadowNode::layout(LayoutContext layoutContext) {
ensureUnsealed();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class ParagraphShadowNode final : public ConcreteViewShadowNode<
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
traits.set(ShadowNodeTraits::Trait::MeasurableYogaNode);
traits.set(ShadowNodeTraits::Trait::BaselineYogaNode);

#ifdef ANDROID
// Unsetting `FormsStackingContext` trait is essential on Android where we
Expand All @@ -69,6 +70,10 @@ class ParagraphShadowNode final : public ConcreteViewShadowNode<
const LayoutContext& layoutContext,
const LayoutConstraints& layoutConstraints) const override;

Float baseline(
const LayoutContext& layoutContext,
Size size) const override;

/*
* Internal representation of the nested content of the node in a format
* suitable for future processing.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,28 @@ Size AndroidTextInputShadowNode::measureContent(
.size;
}

Float AndroidTextInputShadowNode::baseline(
const LayoutContext& layoutContext,
Size size) const {
AttributedString attributedString = getMostRecentAttributedString();

if (attributedString.isEmpty()) {
attributedString = getPlaceholderAttributedString();
}

// Yoga expects a baseline relative to the Node's border-box edge instead of
// the content, so we need to adjust by the padding and border widths, which
// have already been set by the time of baseline alignment
auto top = YGNodeLayoutGetBorder(&yogaNode_, YGEdgeTop) +
YGNodeLayoutGetPadding(&yogaNode_, YGEdgeTop);

return textLayoutManager_
->baseline(
attributedString,
getConcreteProps().paragraphAttributes,
size) + top;
}

void AndroidTextInputShadowNode::layout(LayoutContext layoutContext) {
updateStateIfNeeded();
ConcreteViewShadowNode::layout(layoutContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class AndroidTextInputShadowNode final
static ShadowNodeTraits BaseTraits() {
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
traits.set(ShadowNodeTraits::Trait::BaselineYogaNode);
return traits;
}

Expand Down Expand Up @@ -65,6 +66,10 @@ class AndroidTextInputShadowNode final
const LayoutConstraints& layoutConstraints) const override;
void layout(LayoutContext layoutContext) override;

Float baseline(
const LayoutContext& layoutContext,
Size size) const override;

private:
ContextContainer* contextContainer_{};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,33 @@ Size TextInputShadowNode::measureContent(
.size;
}

Float TextInputShadowNode::baseline(
const LayoutContext& layoutContext,
Size size) const {
auto attributedString = getAttributedString(layoutContext);

if (attributedString.isEmpty()) {
auto placeholderString = !getConcreteProps().placeholder.empty()
? getConcreteProps().placeholder
: BaseTextShadowNode::getEmptyPlaceholder();
auto textAttributes = getConcreteProps().getEffectiveTextAttributes(
layoutContext.fontSizeMultiplier);
attributedString.appendFragment({std::move(placeholderString), textAttributes, {}});
}

// Yoga expects a baseline relative to the Node's border-box edge instead of
// the content, so we need to adjust by the padding and border widths, which
// have already been set by the time of baseline alignment
auto top = YGNodeLayoutGetBorder(&yogaNode_, YGEdgeTop) +
YGNodeLayoutGetPadding(&yogaNode_, YGEdgeTop);

return textLayoutManager_
->baseline(
attributedString,
getConcreteProps().getEffectiveParagraphAttributes(),
size) + top;
}

void TextInputShadowNode::layout(LayoutContext layoutContext) {
updateStateIfNeeded(layoutContext);
ConcreteViewShadowNode::layout(layoutContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class TextInputShadowNode final : public ConcreteViewShadowNode<
auto traits = ConcreteViewShadowNode::BaseTraits();
traits.set(ShadowNodeTraits::Trait::LeafYogaNode);
traits.set(ShadowNodeTraits::Trait::MeasurableYogaNode);
traits.set(ShadowNodeTraits::Trait::BaselineYogaNode);
return traits;
}

Expand All @@ -58,6 +59,10 @@ class TextInputShadowNode final : public ConcreteViewShadowNode<
const LayoutConstraints& layoutConstraints) const override;
void layout(LayoutContext layoutContext) override;

Float baseline(
const LayoutContext& layoutContext,
Size size) const override;

private:
/*
* Creates a `State` object if needed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ YogaLayoutableShadowNode::YogaLayoutableShadowNode(
YogaLayoutableShadowNode::yogaNodeMeasureCallbackConnector);
}

if (getTraits().check(ShadowNodeTraits::Trait::BaselineYogaNode)) {
yogaNode_.setBaselineFunc(
YogaLayoutableShadowNode::yogaNodeBaselineCallbackConnector);
}

updateYogaProps();
updateYogaChildren();

Expand Down Expand Up @@ -845,6 +850,22 @@ YGSize YogaLayoutableShadowNode::yogaNodeMeasureCallbackConnector(
yogaFloatFromFloat(size.width), yogaFloatFromFloat(size.height)};
}

float YogaLayoutableShadowNode::yogaNodeBaselineCallbackConnector(
YGNodeConstRef yogaNode,
float width,
float height) {
SystraceSection s(
"YogaLayoutableShadowNode::yogaNodeBaselineCallbackConnector");

auto& shadowNode = shadowNodeFromContext(yogaNode);
auto baseline = shadowNode.baseline(
threadLocalLayoutContext,
{.width = floatFromYogaFloat(width),
.height = floatFromYogaFloat(height)});

return yogaFloatFromFloat(baseline);
}

YogaLayoutableShadowNode& YogaLayoutableShadowNode::shadowNodeFromContext(
YGNodeConstRef yogaNode) {
return dynamic_cast<YogaLayoutableShadowNode&>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ class YogaLayoutableShadowNode : public LayoutableShadowNode {
YGMeasureMode widthMode,
float height,
YGMeasureMode heightMode);
static float yogaNodeBaselineCallbackConnector(
YGNodeConstRef yogaNode,
float width,
float height);
static YogaLayoutableShadowNode& shadowNodeFromContext(
YGNodeConstRef yogaNode);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,7 @@ Size LayoutableShadowNode::measure(
return layoutableShadowNode.getLayoutMetrics().frame.size;
}

Float LayoutableShadowNode::firstBaseline(Size /*size*/) const {
return 0;
}

Float LayoutableShadowNode::lastBaseline(Size /*size*/) const {
Float LayoutableShadowNode::baseline(const LayoutContext& /*layoutContext*/, Size /*size*/) const {
return 0;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,7 @@ class LayoutableShadowNode : public ShadowNode {
/*
* Unifed methods to access text layout metrics.
*/
virtual Float firstBaseline(Size size) const;
virtual Float lastBaseline(Size size) const;
virtual Float baseline(const LayoutContext& layoutContext, Size size) const;

virtual bool canBeTouchTarget() const;
virtual bool canChildrenBeTouchTarget() const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ class ShadowNodeTraits {

// Indicates that direct children of the node should not be collapsed
ChildrenFormStackingContext = 1 << 10,

// Inherits `YogaLayoutableShadowNode` and has a custom baseline function.
BaselineYogaNode = 1 << 11,
};

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ class TextMeasureCacheKey final {
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{};
};

/*
* Maximum size of the Cache.
* The number was empirically chosen based on approximation of an average amount
Expand All @@ -82,6 +92,15 @@ using TextMeasureCache = SimpleThreadSafeCache<
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) {
Expand Down Expand Up @@ -199,6 +218,21 @@ inline bool operator!=(
return !(lhs == rhs);
}

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 LineMeasureCacheKey& lhs,
const LineMeasureCacheKey& rhs) {
return !(lhs == rhs);
}

} // namespace facebook::react

namespace std {
Expand All @@ -213,4 +247,14 @@ struct hash<facebook::react::TextMeasureCacheKey> {
}
};

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);
}
};

} // namespace std
Loading
Loading