LineBreakMeasurerを使用して複数行のテキストを描画する方法については数千の記事がありますが、\ n(テキストの特定の位置に新しい行を強制する場合だけでなく、右-または左-マージンが終了します)。
秘密はBreakIteratorにあるようですが、\nを処理する実装が見つかりませんでした。
LineBreakMeasurer
's(LBM
' s)メソッドの代わりにnextLayout(float)
、オーバーロードされたLBM.nextLayout(float, int, boolean)
メソッドを使用します。LBM
これにより、返されるに含まれるテキストを制限できますTextLayout
。あなたの場合、次の改行を超えないように指示します。
このコードスニペットはあなたにアイデアを与えるはずです。最初LBM.nextOffset
に、どの文字インデックスが次のレイアウトの終わりになるかを「ピーク」するために使用します。次に、そのオフセットまで文字列の内容を繰り返し処理して、改行文字が見つかるかどうかを確認します。そうした場合、その見つかった制限を、改行を超えないようnextLayout(float, int, boolean)
に指示する2番目の引数として使用します。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を各トークンに適用します。
アーロンのコードは常に正しく機能するとは限らないので、ここで私のために機能しているいくつかの微調整されたコードがあります:
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)!!
}
}