Fixed RichTextPane somewhat.

Switched to BreakIterator.
This commit is contained in:
Snowyfox 2022-04-15 13:07:22 -04:00
parent 7ede5e1290
commit 695d1057a2
2 changed files with 69 additions and 34 deletions

View File

@ -32,6 +32,8 @@ import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.io.IOException;
import cafe.biskuteri.hinoki.Tree;
import java.text.BreakIterator;
import java.util.Locale;
class
@ -373,8 +375,19 @@ implements ActionListener {
}
if (node.key.equals("text"))
{
for (String word: node.value.split(" "))
b = b.text(word).spacer(" ");
BreakIterator it = BreakIterator.getWordInstance(Locale.ROOT);
String text = node.value;
it.setText(text);
int start = it.first(), end = it.next();
while (end != BreakIterator.DONE)
{
String word = text.substring(start, end);
char c = word.isEmpty() ? ' ' : word.charAt(0);
boolean w = Character.isWhitespace(c);
b = w ? b.spacer(word) : b.text(word);
start = end;
end = it.next();
}
}
if (node.key.equals("emoji"))
{

View File

@ -53,13 +53,12 @@ RichTextPane extends JComponent {
public static List<Segment>
layout(List<Segment> text, FontMetrics fm, int width)
{
if (width < fm.getMaxAdvance()) return new LinkedList<>();
List<Segment> copy = new LinkedList<>();
for (Segment segment: text) copy.add(segment.clone());
text = copy;
ListIterator<Segment> cursor = text.listIterator();
int dy = fm.getHeight(), x = 0, y = dy;
int x = 0, y = fm.getAscent();
int dy = fm.getHeight();
while (cursor.hasNext())
{
Segment curr = cursor.next();
@ -82,8 +81,10 @@ RichTextPane extends JComponent {
dx = 0;
}
// If can readily fit, just do so.
if (x + dx < width || curr.spacer) {
boolean fits = x + dx < width;
if (fits || curr.spacer)
{
curr.x = x;
curr.y = y;
x += dx;
@ -94,47 +95,68 @@ RichTextPane extends JComponent {
continue;
}
// If image, or text that isn't long, just break.
if (curr.image != null || dx < width / 3) {
boolean tooLong = dx > width;
boolean canFitChar = width >= fm.getMaxAdvance();
boolean splittable = curr.image == null;
/*
* A bit of redundancy in my conditions, but the point is
* to exactly express the triggers in my mental model.
* The conditions should read more like English.
*/
if (!tooLong || (tooLong && !splittable))
{
curr.x = 0;
curr.y = y += dy;
x = dx;
continue;
}
// Greedily split string to fit into line.
int offset = splitForFit(curr.text, fm, width - x);
if (offset == 0) {
cursor.add(curr); cursor.previous();
y += dy;
x = dx;
continue;
assert tooLong && splittable;
String s = curr.text;
int splitOffset;
for (splitOffset = 0; splitOffset < s.length(); ++splitOffset)
{
String substring = s.substring(0, splitOffset + 1);
if (fm.stringWidth(substring) > width) break;
}
Segment next = new Segment();
next.text = curr.text.substring(offset);
next.link = curr.link;
cursor.add(next); cursor.previous();
curr.text = curr.text.substring(0, offset);
curr.x = x;
curr.y = y;
if (splitOffset == 0) splitOffset = 1;
/*
* I force a split even if our width supports no characters.
* Because if I don't split, the only alternatives to infinitely
* looping downwards is to emplace this segment or ignore it.
*/
Segment fitted = new Segment();
fitted.text = s.substring(0, splitOffset);
fitted.link = curr.link;
fitted.x = x;
fitted.y = y;
cursor.add(fitted);
curr.text = s.substring(splitOffset);
y += dy;
x = 0;
cursor.add(curr); cursor.previous();
/*
* I had to use a stack and return a new list because,
* splitting can turn a long segment into several spread
* over different lines. Here curr becomes the "after-split"
* and I push it back to the stack.
*
* If #layout wasn't a separate method, but rather only for
* graphical painting. Then I don't need a stack nor return
* a new list. I iterate over the given one, filling the
* nodes' geometric information, if a split occurs I save the
* "after-split" in a variable, which in the next iteration
* I use as curr instead of list.next(). The caller doesn't
* need to know the geometry of these intermediate segments.
*/
continue;
}
return text;
}
// - -%- -
private static int
splitForFit(String s, FontMetrics fm, int width)
{
int max = 0;
for (int o = 1; o < s.length(); max = o++)
if (fm.stringWidth(s.substring(0, o)) > width) break;
return max;
}
// ---%-@-%---
public static class