Permalink
Browse files

Text to Spannable conversion is now using PRIORITY flag to enforce th…

…e order of spans

Summary:
When we convert nested <Text> components to Spannable object we must enforce the order of spans somehow,
otherwise we will have Spannable object with unpredictable order of spans, which will produce unpredictalbe text layout.
We can do it only using `Spannable.SPAN_PRIORITY` feature because Spannable objects do not maintain the order of spans internally.

We also have to fix this to implement autoexpandable <TextInput>.

Reviewed By: achen1

Differential Revision: D5811172

fbshipit-source-id: 5bc68b869e58aba27d6986581af9fe3343d116a7
  • Loading branch information...
shergin authored and facebook-github-bot committed Sep 18, 2017
1 parent 3ff463f commit 7efd4fabfde2213faf2ba570ab05ebfd0a57715b
@@ -231,6 +231,27 @@ class TextExample extends React.Component<{}> {
<RNTesterBlock title="Nested">
<Text onPress={() => console.log('1st')}>
(Normal text,
<Text style={{color: 'red', fontWeight: 'bold'}}>
(R)red
<Text style={{color: 'green', fontWeight: 'normal'}}>
(G)green
<Text style={{color: 'blue', fontWeight: 'bold'}}>
(B)blue
<Text style={{color: 'cyan', fontWeight: 'normal'}}>
(C)cyan
<Text style={{color: 'magenta', fontWeight: 'bold'}}>
(M)magenta
<Text style={{color: 'yellow', fontWeight: 'normal'}}>
(Y)yellow
<Text style={{color: 'black', fontWeight: 'bold'}}>
(K)black
</Text>
</Text>
</Text>
</Text>
</Text>
</Text>
</Text>
<Text style={{fontWeight: 'bold'}} onPress={() => console.log('2nd')}>
(and bold
<Text style={{fontStyle: 'italic', fontSize: 11, color: '#527fe4'}} onPress={() => console.log('3rd')}>
@@ -65,13 +65,17 @@
this.what = what;
}
public void execute(SpannableStringBuilder sb) {
public void execute(SpannableStringBuilder sb, int priority) {
// All spans will automatically extend to the right of the text, but not the left - except
// for spans that start at the beginning of the text.
int spanFlags = Spannable.SPAN_EXCLUSIVE_INCLUSIVE;
if (start == 0) {
spanFlags = Spannable.SPAN_INCLUSIVE_INCLUSIVE;
}
spanFlags &= ~Spannable.SPAN_PRIORITY;
spanFlags |= (priority << Spannable.SPAN_PRIORITY_SHIFT) & Spannable.SPAN_PRIORITY;
sb.setSpan(what, start, end, spanFlags);
}
}
@@ -167,29 +171,28 @@ protected static Spannable spannedFromShadowNode(
// up-to-bottom, otherwise all the spannables that are withing the region for which one may set
// a new spannable will be wiped out
List<SetSpanOperation> ops = new ArrayList<>();
buildSpannedFromShadowNode(textShadowNode, sb, ops);
if (text != null) {
sb.append(text);
}
if (textShadowNode.mFontSize == UNSET) {
sb.setSpan(
new AbsoluteSizeSpan(
textShadowNode.mAllowFontScaling
? (int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP))
: (int) Math.ceil(PixelUtil.toPixelFromDIP(ViewDefaults.FONT_SIZE_SP))),
0,
sb.length(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
int defaultFontSize =
textShadowNode.mAllowFontScaling
? (int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP))
: (int) Math.ceil(PixelUtil.toPixelFromDIP(ViewDefaults.FONT_SIZE_SP));
ops.add(new SetSpanOperation(0, sb.length(), new AbsoluteSizeSpan(defaultFontSize)));
}
textShadowNode.mContainsImages = false;
textShadowNode.mHeightOfTallestInlineImage = Float.NaN;
// While setting the Spans on the final text, we also check whether any of them are images
for (int i = ops.size() - 1; i >= 0; i--) {
SetSpanOperation op = ops.get(i);
// While setting the Spans on the final text, we also check whether any of them are images.
int priority = 0;
for (SetSpanOperation op : ops) {
if (op.what instanceof TextInlineImageSpan) {
int height = ((TextInlineImageSpan) op.what).getHeight();
textShadowNode.mContainsImages = true;
@@ -198,8 +201,13 @@ protected static Spannable spannedFromShadowNode(
textShadowNode.mHeightOfTallestInlineImage = height;
}
}
op.execute(sb);
// Actual order of calling {@code execute} does NOT matter,
// but the {@code priority} DOES matter.
op.execute(sb, priority);
priority++;
}
return sb;
}

0 comments on commit 7efd4fa

Please sign in to comment.