2

有数千篇文章如何使用 LineBreakMeasurer 绘制多行文本,但没有关于绘制多行文本的文章也考虑到 \n(当你想在文本中的特定位置强制换行时,而不仅仅是当右 - 或左 - 边距结束)。

秘密似乎在于 BreakIterator,但我找不到处理 \n 的实现。

4

5 回答 5

4

代替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

于 2010-11-21T05:22:16.220 回答
3

我发现此代码适用于换行问题。我使用 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.
}
于 2012-03-21T21:37:42.077 回答
2

首先标记文本,然后将 LineBreakMeasureCode 应用于每个标记。

于 2009-07-13T15:41:41.387 回答
1

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();
于 2013-06-04T10:51:12.490 回答
0

即使这个话题很老了,我自己也遇到过这个问题并且必须解决它。经过大量调查,我提出了可以在包装“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)!!
    }
}
于 2022-03-01T10:15:47.163 回答