Skip to content

Commit

Permalink
Add an option to clip text if it goes beyond the Y bounds of paragrap…
Browse files Browse the repository at this point in the history
…h text (#2412)

Fixes #2384
  • Loading branch information
gpeal committed Dec 30, 2023
1 parent 6e11040 commit c9c8bb7
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ import kotlin.math.roundToInt
* size than this composable.
* @param contentScale Define how the animation should be scaled if it has a different size than this Composable.
* @param clipToCompositionBounds Determines whether or not Lottie will clip the animation to the original animation composition bounds.
* @param clipTextToBoundingBox When true, if there is a bounding box set on a text layer (paragraph text), any text
* that overflows past its height will not be drawn.
* @param fontMap A map of keys to Typefaces. The key can be: "fName", "fFamily", or "fFamily-fStyle" as specified in your Lottie file.
* @param asyncUpdates When set to true, some parts of animation updates will be done off of the main thread.
* For more details, refer to the docs of [AsyncUpdates].
Expand All @@ -83,6 +85,7 @@ fun LottieAnimation(
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
clipToCompositionBounds: Boolean = true,
clipTextToBoundingBox: Boolean = false,
fontMap: Map<String, Typeface>? = null,
asyncUpdates: AsyncUpdates = AsyncUpdates.AUTOMATIC,
) {
Expand Down Expand Up @@ -121,6 +124,7 @@ fun LottieAnimation(
drawable.isApplyingOpacityToLayersEnabled = applyOpacityToLayers
drawable.maintainOriginalImageBounds = maintainOriginalImageBounds
drawable.clipToCompositionBounds = clipToCompositionBounds
drawable.clipTextToBoundingBox = clipTextToBoundingBox
drawable.progress = progress()
drawable.setBounds(0, 0, bounds.width(), bounds.height())
drawable.draw(canvas.nativeCanvas, matrix)
Expand Down Expand Up @@ -194,6 +198,7 @@ fun LottieAnimation(
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
clipToCompositionBounds: Boolean = true,
clipTextToBoundingBox: Boolean = false,
fontMap: Map<String, Typeface>? = null,
asyncUpdates: AsyncUpdates = AsyncUpdates.AUTOMATIC,
) {
Expand All @@ -219,6 +224,7 @@ fun LottieAnimation(
alignment = alignment,
contentScale = contentScale,
clipToCompositionBounds = clipToCompositionBounds,
clipTextToBoundingBox = clipTextToBoundingBox,
fontMap = fontMap,
asyncUpdates = asyncUpdates,
)
Expand Down
19 changes: 19 additions & 0 deletions lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ private void init(@Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
setClipToCompositionBounds(ta.getBoolean(R.styleable.LottieAnimationView_lottie_clipToCompositionBounds, true));
}

if (ta.hasValue(R.styleable.LottieAnimationView_lottie_clipTextToBoundingBox)) {
setClipTextToBoundingBox(ta.getBoolean(R.styleable.LottieAnimationView_lottie_clipTextToBoundingBox, false));
}

if (ta.hasValue(R.styleable.LottieAnimationView_lottie_defaultFontFileExtension)) {
setDefaultFontFileExtension(ta.getString(R.styleable.LottieAnimationView_lottie_defaultFontFileExtension));
}
Expand Down Expand Up @@ -1217,6 +1221,21 @@ public void setApplyingOpacityToLayersEnabled(boolean isApplyingOpacityToLayersE
lottieDrawable.setApplyingOpacityToLayersEnabled(isApplyingOpacityToLayersEnabled);
}

/**
* @see #setClipTextToBoundingBox(boolean)
*/
public boolean getClipTextToBoundingBox() {
return lottieDrawable.getClipTextToBoundingBox();
}

/**
* When true, if there is a bounding box set on a text layer (paragraph text), any text
* that overflows past its height will not be drawn.
*/
public void setClipTextToBoundingBox(boolean clipTextToBoundingBox) {
lottieDrawable.setClipTextToBoundingBox(clipTextToBoundingBox);
}

/**
* This API no longer has any effect.
*/
Expand Down
19 changes: 19 additions & 0 deletions lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ private enum OnVisibleAction {
private boolean performanceTrackingEnabled;
private boolean outlineMasksAndMattes;
private boolean isApplyingOpacityToLayersEnabled;
private boolean clipTextToBoundingBox = false;

private RenderMode renderMode = RenderMode.AUTOMATIC;
/**
Expand Down Expand Up @@ -539,6 +540,24 @@ public boolean isApplyingOpacityToLayersEnabled() {
return isApplyingOpacityToLayersEnabled;
}

/**
* @see #setClipTextToBoundingBox(boolean)
*/
public boolean getClipTextToBoundingBox() {
return clipTextToBoundingBox;
}

/**
* When true, if there is a bounding box set on a text layer (paragraph text), any text
* that overflows past its height will not be drawn.
*/
public void setClipTextToBoundingBox(boolean clipTextToBoundingBox) {
if (clipTextToBoundingBox != this.clipTextToBoundingBox) {
this.clipTextToBoundingBox = clipTextToBoundingBox;
invalidateSelf();
}
}

private void buildCompositionLayer() {
LottieComposition composition = this.composition;
if (composition == null) {
Expand Down
18 changes: 13 additions & 5 deletions lottie/src/main/java/com/airbnb/lottie/model/layer/TextLayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.Map;

public class TextLayer extends BaseLayer {

// Capacity is 2 because emojis are 2 characters. Some are longer in which case, the capacity will
// be expanded but that should be pretty rare.
private final StringBuilder stringBuilder = new StringBuilder(2);
Expand Down Expand Up @@ -203,8 +204,9 @@ private void drawTextWithGlyphs(

canvas.save();

offsetCanvas(canvas, documentData, lineIndex, line.width);
drawGlyphTextLine(line.text, documentData, font, canvas, parentScale, fontScale, tracking);
if (offsetCanvas(canvas, documentData, lineIndex, line.width)) {
drawGlyphTextLine(line.text, documentData, font, canvas, parentScale, fontScale, tracking);
}

canvas.restore();
}
Expand Down Expand Up @@ -271,20 +273,24 @@ private void drawTextWithFont(DocumentData documentData, Font font, Canvas canva

canvas.save();

offsetCanvas(canvas, documentData, lineIndex, line.width);
drawFontTextLine(line.text, documentData, canvas, tracking);
if (offsetCanvas(canvas, documentData, lineIndex, line.width)) {
drawFontTextLine(line.text, documentData, canvas, tracking);
}

canvas.restore();
}
}
}

private void offsetCanvas(Canvas canvas, DocumentData documentData, int lineIndex, float lineWidth) {
private boolean offsetCanvas(Canvas canvas, DocumentData documentData, int lineIndex, float lineWidth) {
PointF position = documentData.boxPosition;
PointF size = documentData.boxSize;
float dpScale = Utils.dpScale();
float lineStartY = position == null ? 0f : documentData.lineHeight * dpScale + position.y;
float lineOffset = (lineIndex * documentData.lineHeight * dpScale) + lineStartY;
if (lottieDrawable.getClipTextToBoundingBox() && size != null && position != null && lineOffset >= position.y + size.y + documentData.size) {
return false;
}
float lineStart = position == null ? 0f : position.x;
float boxWidth = size == null ? 0f : size.x;
switch (documentData.justification) {
Expand All @@ -298,6 +304,7 @@ private void offsetCanvas(Canvas canvas, DocumentData documentData, int lineInde
canvas.translate(lineStart + boxWidth / 2f - lineWidth / 2f, lineOffset);
break;
}
return true;
}

@Nullable
Expand Down Expand Up @@ -608,6 +615,7 @@ public <T> void addValueCallback(T property, @Nullable LottieValueCallback<T> ca
}

private static class TextSubLine {

private String text = "";
private float width = 0f;

Expand Down
3 changes: 2 additions & 1 deletion lottie/src/main/res/values/attrs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<attr name="lottie_ignoreDisabledSystemAnimations" format="boolean" />
<attr name="lottie_useCompositionFrameRate" format="boolean" />
<attr name="lottie_clipToCompositionBounds" format="boolean" />
<attr name="lottie_clipTextToBoundingBox" format="boolean" />
<!-- The default file extension that Lottie will use when finding fonts in assets/fonts/fontFamily.* -->
<attr name="lottie_defaultFontFileExtension" format="string" />
<!-- These values must be kept in sync with the RenderMode enum -->
Expand All @@ -37,4 +38,4 @@
<enum name="disabled" value="2" />
</attr>
</declare-styleable>
</resources>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.airbnb.lottie.model.LottieCompositionCache
import com.airbnb.lottie.snapshots.tests.ApplyOpacityToLayerTestCase
import com.airbnb.lottie.snapshots.tests.AssetsTestCase
import com.airbnb.lottie.snapshots.tests.ClipChildrenTestCase
import com.airbnb.lottie.snapshots.tests.ClipTextToBoundingBoxTestCase
import com.airbnb.lottie.snapshots.tests.ColorStateListColorFilterTestCase
import com.airbnb.lottie.snapshots.tests.ComposeDynamicPropertiesTestCase
import com.airbnb.lottie.snapshots.tests.ComposeScaleTypesTestCase
Expand Down Expand Up @@ -160,6 +161,7 @@ class LottieSnapshotTest {
SoftwareRenderingDynamicPropertiesInvalidationTestCase(),
SeekBarTestCase(),
CompositionFrameRate(),
ClipTextToBoundingBoxTestCase(),
)

withTimeout(TimeUnit.MINUTES.toMillis(45)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.airbnb.lottie.snapshots.tests

import com.airbnb.lottie.snapshots.SnapshotTestCase
import com.airbnb.lottie.snapshots.SnapshotTestCaseContext
import com.airbnb.lottie.snapshots.withFilmStripView

class ClipTextToBoundingBoxTestCase : SnapshotTestCase {
override suspend fun SnapshotTestCaseContext.run() {
withFilmStripView(
"Tests/SzGlyph.json",
"Clip glyph text to bounding box",
"Enabled"
) { filmStripView ->
filmStripView.setClipTextToBoundingBox(true)
}
withFilmStripView(
"Tests/SzGlyph.json",
"Clip glyph text to bounding box",
"Disabled"
) { filmStripView ->
filmStripView.setClipTextToBoundingBox(false)
}

withFilmStripView(
"Tests/SzFont.json",
"Clip font text to bounding box",
"Enabled"
) { filmStripView ->
filmStripView.setClipTextToBoundingBox(true)
}
withFilmStripView(
"Tests/SzFont.json",
"Clip font text to bounding box",
"Disabled"
) { filmStripView ->
filmStripView.setClipTextToBoundingBox(false)
}
}
}
1 change: 1 addition & 0 deletions snapshot-tests/src/main/assets/Tests/SzFont.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"v":"5.12.1","fr":60,"ip":0,"op":60,"w":400,"h":400,"nm":"Comp 1","ddd":0,"assets":[],"fonts":{"list":[{"origin":0,"fPath":"","fClass":"","fFamily":"Helvetica","fWeight":"","fStyle":"Regular","fName":"Helvetica","ascent":72.8994140625}]},"layers":[{"ddd":0,"ind":1,"ty":5,"nm":"1 test test 2 test test 3 test test 4 test test 5 test test","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[198,252,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"t":{"d":{"k":[{"s":{"sz":[280,218],"ps":[-140,-138],"s":48,"f":"Helvetica","t":"1 test test\u00032 test test\u00033 test test\u00034 test test\u00035 test test","ca":0,"j":2,"tr":0,"lh":48,"ls":0,"fc":[0,0,0],"sc":[1,0,0],"sw":1,"of":true},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[]},"ip":0,"op":60,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,226,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[83.632,89.231,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[294.844,243.43],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-8.279,-24.092],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[87.802,91.523],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}}
Loading

0 comments on commit c9c8bb7

Please sign in to comment.