Skip to content

Instantly share code, notes, and snippets.

@marius-m
Created March 4, 2022 12:37
Show Gist options
  • Save marius-m/ad086f677f2ef2cb79ec2e1df9678412 to your computer and use it in GitHub Desktop.
Save marius-m/ad086f677f2ef2cb79ec2e1df9678412 to your computer and use it in GitHub Desktop.
Text area line counter with parsing
/**
* Parses text to fit in [TextProvider.formatWidth] and wraps whenever needed
*/
class TextAreaLineCounter(
private val textProvider: TextProvider
) {
private val formatWidth: Float
get() = textProvider.formatWidth
fun parseParagraph(
font: Font,
fontMetrics: FontMetrics
): WrappedParagraph {
return countLinesInParagraphs(
textRaw = textProvider.text,
font = font,
fontMetrics = fontMetrics,
formatWidth = formatWidth
)
}
/**
* Counts lines in [JTextArea]
* Includes line breaks ('\n')
*/
private fun countLinesInParagraphs(
textRaw: String,
font: Font,
fontMetrics: FontMetrics,
formatWidth: Float
): WrappedParagraph {
val paragraphsAsString: List<String> = textRaw.split("\n")
val sentences = paragraphsAsString.map { paragraph ->
countLinesInSentence(paragraph, font, fontMetrics, formatWidth)
}
return WrappedParagraph(sentences = sentences)
}
/**
* Counts lines in wrapped [JTextArea]
* Does not include line breaks.
*/
private fun countLinesInSentence(
textRaw: String,
font: Font,
fontMetrics: FontMetrics,
formatWidth: Float
): Sentence {
if (textRaw.isEmpty()) {
return Sentence(
wraps = listOf(
SentenceWrap(
wrapIndex = -1,
wrapText = textRaw
)
)
)
}
val text = AttributedString(textRaw)
text.addAttribute(TextAttribute.FONT, font)
val frc = fontMetrics.fontRenderContext
val charIt = text.iterator
val words = mutableListOf<SentenceWrap>()
val lineMeasurer = LineBreakMeasurer(
charIt,
BreakIterator.getLineInstance(),
frc
)
lineMeasurer.position = charIt.beginIndex
var posBegin = 0
var posEnd = lineMeasurer.position
var noLines = 0
do {
lineMeasurer.nextLayout(formatWidth)
posBegin = posEnd
posEnd = lineMeasurer.position
words.add(
SentenceWrap(
wrapIndex = noLines,
wrapText = textRaw.substring(posBegin, posEnd)
)
)
noLines++
} while (posEnd < charIt.endIndex)
return Sentence(words)
}
/**
* Holds wrapped [Sentence]s that break during 'wrap text' or text break symbols
*/
data class WrappedParagraph(
val sentences: List<Sentence>
) {
fun lineCount(): Int {
val sentenceCount = sentences.fold(0) { currentCount: Int, sentence: Sentence ->
val newCount = currentCount + sentence.lineCount()
newCount
}
return sentenceCount
}
}
/**
* Sentence contains text pieces which are broken by 'wrapText'
*/
data class Sentence(
val wraps: List<SentenceWrap>
) {
fun lineCount(): Int = wraps.size
}
/**
* Entity for holding a wrapped text snippet
*/
data class SentenceWrap(
val wrapIndex: Int,
val wrapText: String
)
interface TextProvider {
val text: String
val formatWidth: Float
}
companion object {
val l = LoggerFactory.getLogger(TextAreaLineCounter::class.java)!!
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment