Created
March 4, 2022 12:37
-
-
Save marius-m/ad086f677f2ef2cb79ec2e1df9678412 to your computer and use it in GitHub Desktop.
Text area line counter with parsing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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