有数千篇文章如何使用 LineBreakMeasurer 绘制多行文本,但没有关于绘制多行文本的文章也考虑到 \n(当你想在文本中的特定位置强制换行时,而不仅仅是当右 - 或左 - 边距结束)。
秘密似乎在于 BreakIterator,但我找不到处理 \n 的实现。
代替LineBreakMeasurer
's ( LBM
's)nextLayout(float)
方法,使用重载LBM.nextLayout(float, int, boolean)
方法。这允许您限制LBM
将包含在返回的TextLayout
. 在您的情况下,您将指示它不要超出下一个换行符。
这个代码片段应该给你的想法。首先用于LBM.nextOffset
“查看”哪个字符索引将是下一个布局的结尾。然后遍历您的字符串内容直到该偏移量,以查看是否找到任何换行符。如果你这样做了,那么使用找到的限制作为第二个参数,nextLayout(float, int, boolean)
它会告诉你LBM
不要超过换行符:
int next = lineMeasurer.nextOffset(formatWidth);
int limit = next;
if (limit < totalLength) {
for (int i = lineMeasurer.getPosition(); i < next; ++i) {
char c = string.charAt(i);
if (c == '\n') {
limit = i;
break;
}
}
}
TextLayout layout = lineMeasurer.nextLayout(formatWidth, limit, false);
参考
http://java.sun.com/developer/onlineTraining/Media/2DText/style.html#layout http://java.sun.com/developer/onlineTraining/Media/2DText/Code/LineBreakSample.java
我发现此代码适用于换行问题。我使用 atdixon 作为模板来获得这个。
while (measurer.getPosition() < paragraph.getEndIndex()) {
next = measurer.nextOffset(wrappingWidth);
limit = next;
charat = tested.indexOf('\n',measurer.getPosition()+1);
if(next > (charat - measurer.getPosition()) && charat != -1){
limit = charat - measurer.getPosition();
}
layout = measurer.nextLayout(wrappingWidth, measurer.getPosition()+limit, false);
// Do the rest of your layout and pen work.
}
首先标记文本,然后将 LineBreakMeasureCode 应用于每个标记。
Aaron 的代码并不总是能正常工作,所以这里有一些对我有用的调整代码:
int next = measurer.nextOffset(width);
int limit = next;
if (limit <= text.length()) {
for (int i = measurer.getPosition(); i < next; ++i) {
char c = text.charAt(i);
if (c == '\n') {
limit = i + 1;
break;
}
}
}
TextLayout textLayout = measurer.nextLayout(width, limit, false);
如果您需要来自 AttributedString 的文本,您可以事先执行此操作
AttributedCharacterIterator iterator = attributedString.getIterator();
StringBuilder stringBuilder = new StringBuilder(iterator.getEndIndex());
while (iterator.getIndex() < iterator.getEndIndex()) {
stringBuilder.append(iterator.current());
iterator.next();
}
String text = stringBuilder.toString();
即使这个话题很老了,我自己也遇到过这个问题并且必须解决它。经过大量调查,我提出了可以在包装“JTextArea”的单个类中工作的解决方案。
代码在 Kotlin 中,因为这就是我正在使用的。希望它仍然有用。
package [your package name]
import java.awt.Font
import java.awt.FontMetrics
import java.awt.Insets
import java.awt.font.LineBreakMeasurer
import java.awt.font.TextAttribute
import java.text.AttributedString
import java.text.BreakIterator
import javax.swing.JTextArea
class TextAreaLineCounter(
private val textArea: JTextArea
) {
private val font: Font
get() = textArea.font
private val fontMetrics: FontMetrics
get() = textArea.getFontMetrics(font)
private val insets: Insets
get() = textArea.insets
private val formatWidth: Float
get() = (textArea.width - insets.left - insets.right).toFloat()
fun countLines(): Int {
return countLinesInParagraphs(
textRaw = textArea.text,
font = font,
fontMetrics = fontMetrics,
formatWidth = formatWidth
)
}
private fun countLinesInParagraphs(
textRaw: String,
font: Font,
fontMetrics: FontMetrics,
formatWidth: Float
): Int {
val paragraphs: List<String> = textRaw.split("\n")
val lineCount = paragraphs.fold(0) { acc: Int, sentence: String ->
val newCount = acc + countLinesInSentence(sentence, font, fontMetrics, formatWidth)
newCount
}
return lineCount
}
private fun countLinesInSentence(
textRaw: String,
font: Font,
fontMetrics: FontMetrics,
formatWidth: Float
): Int {
val text = AttributedString(textRaw)
text.addAttribute(TextAttribute.FONT, font)
val frc = fontMetrics.fontRenderContext
val charIt = text.iterator
val lineMeasurer = LineBreakMeasurer(
charIt,
BreakIterator.getWordInstance(),
frc
)
lineMeasurer.position = charIt.beginIndex
var noLines = 0
while (lineMeasurer.position < charIt.endIndex) {
lineMeasurer.nextLayout(formatWidth)
noLines++
}
return noLines
}
}
此外,一个可以让您测试行计数器的 GUI 应用程序也可能很有用。
package [your package name]
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.awt.*
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import javax.swing.JFrame
import javax.swing.JPanel
import javax.swing.JTextArea
import javax.swing.SwingUtilities
class MainJTextArea(
private val l: Logger
): JPanel(GridBagLayout()) {
init {
val inputStr = "Lorem ipsum dolor sit amet, consectetur adipisicing\n elit, sed do eiusmo," +
" Lorem ipsum \ndolor sit amet, consectetur adipisicing elit, sed do eiusmo," +
" Lorem ipsum dolor sit amet, \nconsectetur adipisicing elit, sed do eiusmo," +
" Lorem ipsum dolor sit amet, \nconsectetur adipisicing elit, sed do eiusmo"
val textArea = drawTextArea(
text = inputStr,
fontSize = 12.0
)
val textAreaLineCounter = TextAreaLineCounter(textArea)
// Add Components to this panel.
val c = GridBagConstraints().apply {
gridwidth = GridBagConstraints.REMAINDER
fill = GridBagConstraints.BOTH
weightx = 1.0
weighty = 1.0
}
add(textArea, c)
addComponentListener(object : ComponentAdapter() {
override fun componentResized(e: ComponentEvent?) {
super.componentResized(e)
l.debug("Line count: ${textAreaLineCounter.countLines()}")
}
})
}
private fun drawTextArea(
text: String,
fontSize: Double = 12.0
): JTextArea {
val textArea = JTextArea(text)
textArea.size = Dimension(width, height)
textArea.foreground = Color.BLACK
textArea.background = Color(0, 0, 0, 0)
textArea.font = Font(null, Font.LAYOUT_LEFT_TO_RIGHT, fontSize.toInt())
textArea.lineWrap = true
textArea.wrapStyleWord = true
return textArea
}
companion object {
@JvmStatic
fun main(args: Array<String>) {
val logger = LoggerFactory.getLogger(MainJTextArea::class.java)!!
SwingUtilities.invokeLater {
val frame = JFrame("JTextAreaLineCountDemo").apply {
preferredSize = Dimension(400, 360)
defaultCloseOperation = JFrame.EXIT_ON_CLOSE
add(MainJTextArea(logger))
pack()
}
frame.isVisible = true
}
}
}
}
经过进一步调查,我注意到计算器仍然存在问题,需要进行一些定制。因此,我改进了计算机制,以提供内部包含文本中断的详细信息。
这种机制大部分时间都有效。我注意到有几种情况,其中JTextArea
会用空行包裹,但未检测到。因此,使用代码需要您自担风险。
/**
* 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)!!
}
}