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

Add an option to clip text if it goes beyond the Y bounds of paragraph text #2412

Merged
merged 4 commits into from
Dec 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -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