【问题标题】:Handling \n in LineBreakMeasurer在 LineBreakMeasurer 中处理 \n
【发布时间】:2010-11-10 08:19:38
【问题描述】:

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

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

【问题讨论】:

    标签: java text drawing awt


    【解决方案1】:

    使用重载的LBM.nextLayout(float, int, boolean) 方法而不是LineBreakMeasurer's (LBM's) nextLayout(float) 方法。这允许您限制LBM 将包含在返回的TextLayout 中的文本。在您的情况下,您将指示它不要超出下一个换行符。

    这段代码 sn-p 应该会给你这个想法。首先使用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

    【讨论】:

    • 这段代码有几个问题,所以我在下面发布了一个适合我的版本。
    【解决方案2】:

    我发现此代码适用于换行问题。我用 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.
    }
    

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 这是可能的,但我想要一个只使用 LineBreakerMeasurer 的解决方案。
      【解决方案4】:

      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();
      

      【讨论】:

        【解决方案5】:

        虽然这个话题很老了,但我自己也遇到过这个问题,必须解决它。经过大量调查,我提出了可以在包含“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)!!
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-12-14
          • 1970-01-01
          • 2012-04-24
          • 1970-01-01
          • 2019-01-24
          • 2022-10-13
          • 2015-12-24
          • 2013-08-27
          相关资源
          最近更新 更多