blob: 8337726d769d1d4a324d390cd3a338318b970578 [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.text
import android.text.BoringLayout
import android.text.Layout
import android.text.TextPaint
import androidx.annotation.RestrictTo
import java.text.BreakIterator
import java.util.PriorityQueue
/**
* Computes and caches the text layout intrinsic values such as min/max width.
*
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
class LayoutIntrinsics(
charSequence: CharSequence,
textPaint: TextPaint,
@LayoutCompat.TextDirection textDirectionHeuristic: Int
) {
/**
* Compute Android platform BoringLayout metrics. A null value means the provided CharSequence
* cannot be laid out using a BoringLayout.
*/
val boringMetrics: BoringLayout.Metrics? by lazy {
val frameworkTextDir = getTextDirectionHeuristic(textDirectionHeuristic)
BoringLayoutCompat.isBoring(charSequence, textPaint, frameworkTextDir)
}
/**
* Calculate minimum intrinsic width of the CharSequence.
*
* @see androidx.text.minIntrinsicWidth
*/
val minIntrinsicWidth: Float by lazy {
minIntrinsicWidth(charSequence, textPaint)
}
/**
* Calculate maximum intrinsic width for the CharSequence. Maximum intrinsic width is the width
* of text where no soft line breaks are applied.
*/
val maxIntrinsicWidth: Float by lazy {
boringMetrics?.width?.toFloat()
?: Layout.getDesiredWidth(charSequence, 0, charSequence.length, textPaint)
}
}
/**
* Returns the word with the longest length. To calculate it in a performant way, it applies a heuristics where
* - it first finds a set of words with the longest length
* - finds the word with maximum width in that set
*
* @hide
*/
fun minIntrinsicWidth(text: CharSequence, paint: TextPaint): Float {
val iterator = BreakIterator.getLineInstance(paint.textLocale)
iterator.text = CharSequenceCharacterIterator(text, 0, text.length)
// 10 is just a random number that limits the size of the candidate list
val heapSize = 10
// min heap that will hold [heapSize] many words with max length
val longestWordCandidates = PriorityQueue<Pair<Int, Int>>(
heapSize,
Comparator<Pair<Int, Int>> { left, right ->
(left.second - left.first) - (right.second - right.first)
}
)
var start = 0
var end = iterator.next()
while (end != BreakIterator.DONE) {
if (longestWordCandidates.size < heapSize) {
longestWordCandidates.add(Pair(start, end))
} else {
longestWordCandidates.peek()?.let { minPair ->
if ((minPair.second - minPair.first) < (end - start)) {
longestWordCandidates.poll()
longestWordCandidates.add(Pair(start, end))
}
}
}
start = end
end = iterator.next()
}
return longestWordCandidates.map { pair ->
Layout.getDesiredWidth(text, pair.first, pair.second, paint)
}.max() ?: 0f
}