Skip to content

Commit

Permalink
Unicode line-breaking.
Browse files Browse the repository at this point in the history
  • Loading branch information
shartte committed Mar 9, 2025
1 parent 9efaf83 commit 45d5b80
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 21 deletions.
4 changes: 4 additions & 0 deletions docs/docs/02-changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import Video from '@site/src/components/Video';

# Changelog

## 21.1.4 (Minecraft 1.21.1)

- Switch to Unicode line-breaking to improve breaking long lines for Chinese, Japanese, Korean, and other languages.

## 21.1.3 (Minecraft 1.21.1)

- Disable superfluous tracing code slowing down startup.
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/guideme/internal/screen/GuideNavBar.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,12 @@ public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float par
return; // do not render the navbar if there are no nodes.
}

updateLayout();

widthTransition.update();

if (state != State.CLOSED) {
updateLayout();
}

var renderContext = new SimpleRenderContext(graphics);

double currentTime = GLFW.glfwGetTime();
Expand Down
45 changes: 27 additions & 18 deletions src/main/java/guideme/layout/flow/LineBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import guideme.layout.LayoutContext;
import guideme.style.ResolvedTextStyle;
import guideme.style.TextAlignment;
import java.text.BreakIterator;
import java.util.List;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -202,18 +203,13 @@ private void appendText(String text, LytFlowContent flowContent) {
}

private void iterateRuns(CharSequence text, ResolvedTextStyle style, char lastChar, LineConsumer consumer) {
int lastBreakOpportunity = -1;
float widthAtBreakOpportunity = 0;
float curLineWidth = 0;

var fontScale = style.fontScale();
var lineBuffer = new StringBuilder();

boolean lastCharWasWhitespace = Character.isWhitespace(lastChar);
// When starting after a whitespace on an existing line, we have a break opportunity at the start
if (lastCharWasWhitespace) {
lastBreakOpportunity = 0;
}
boolean canBreakAtStart = lastCharWasWhitespace;

for (var i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
Expand All @@ -236,8 +232,6 @@ private void iterateRuns(CharSequence text, ResolvedTextStyle style, char lastCh
} else {
consumer.visitRun(lineBuffer, curLineWidth, true);
lineBuffer.setLength(0);
widthAtBreakOpportunity = curLineWidth = 0;
lastBreakOpportunity = 0;
lastCharWasWhitespace = true;
remainingLineWidth = getAvailableHorizontalSpace();
continue;
Expand All @@ -249,9 +243,6 @@ private void iterateRuns(CharSequence text, ResolvedTextStyle style, char lastCh
if (lastCharWasWhitespace && style.whiteSpace().isCollapseWhitespace()) {
continue; // White space collapsing
}
// Treat spaces as a safe-point for going back to when needing to line-break later
lastBreakOpportunity = lineBuffer.length();
widthAtBreakOpportunity = curLineWidth;
lastCharWasWhitespace = true;
} else {
lastCharWasWhitespace = false;
Expand All @@ -260,12 +251,32 @@ private void iterateRuns(CharSequence text, ResolvedTextStyle style, char lastCh
var advance = context.getAdvance(codePoint, style) * fontScale;
// Break line if necessary
if (curLineWidth + advance > remainingLineWidth) {
// If we had a break opportunity, use it
// In this scenario, the space itself is discarded
if (lastBreakOpportunity != -1) {
consumer.visitRun(lineBuffer.subSequence(0, lastBreakOpportunity), widthAtBreakOpportunity, true);
int precedingBreakOpportunity;

// BreakIterator will only ever break *AFTER* whitespace, but since we ignore the last break opportunity
// we need to also ignore that...
if (lastCharWasWhitespace) {
precedingBreakOpportunity = lineBuffer.length();
} else {
// Find break opportunities and include the current character in it.
var breakIterator = BreakIterator.getLineInstance();
breakIterator.setText(lineBuffer.toString() + (char) codePoint);
precedingBreakOpportunity = breakIterator.preceding(lineBuffer.length() + 1);
}

// If the preceding text chunk ended on a whitespace, we can break there if the
// current word does not offer us any opportunity to.
if (precedingBreakOpportunity > 0 || precedingBreakOpportunity == 0 && canBreakAtStart) {
// Determine width up until the break opportunity.
var widthAtBreakOpportunity = 0f;
for (var j = 0; j < precedingBreakOpportunity; j++) {
widthAtBreakOpportunity += context.getAdvance(lineBuffer.charAt(j), style) * fontScale;
}

consumer.visitRun(lineBuffer.subSequence(0, precedingBreakOpportunity), widthAtBreakOpportunity,
true);
curLineWidth -= widthAtBreakOpportunity;
lineBuffer.delete(0, lastBreakOpportunity);
lineBuffer.delete(0, precedingBreakOpportunity);
if (!lineBuffer.isEmpty() && Character.isWhitespace(lineBuffer.charAt(0))) {
var firstChar = lineBuffer.charAt(0);
lineBuffer.deleteCharAt(0);
Expand All @@ -278,8 +289,6 @@ private void iterateRuns(CharSequence text, ResolvedTextStyle style, char lastCh
lineBuffer.setLength(0);
curLineWidth = 0;
}
lastBreakOpportunity = 0;
widthAtBreakOpportunity = curLineWidth;
remainingLineWidth = getAvailableHorizontalSpace();
// If a white-space character broke the line, ignore it as it
// would otherwise be at the start of the next line
Expand Down
13 changes: 12 additions & 1 deletion src/test/java/guideme/layout/flow/LineBuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ void dontBreakInWords() {
var lines = getLines(3, "A BC");

assertThat(lines).extracting(this::getTextContent).containsExactly(
"A",
"A ",
"BC");
}

Expand All @@ -57,6 +57,17 @@ void testWhitespaceCollapsing() {
"A B");
}

/**
* Consider CJK characters as break opportunities and don't fall back to the last whitespace in mixed language text.
*/
@Test
void testBreakBetweenCJKCharacters() {
var lines = getLines(5, "A Bを変更す");

assertThat(lines).extracting(this::getTextContent).containsExactly(
"A Bを変", "更す");
}

private static ArrayList<Line> getLines(int charsPerLine, String... textChunks) {
var lines = new ArrayList<Line>();
var floats = new ArrayList<LineBlock>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ item_ids:

# Start Page

[Japanese](./japanese.md)

[Markdown](./markdown.md)

Welcome to the world of <ItemImage id="minecraft:stone" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
navigation:
title: チャンネル
---

# チャンネル

マインクラフト1.18用のAE2 10.0.0では、ワールド内でのAE2チャンネルの動作を変更する新しいオプションが導入されています。

0 comments on commit 45d5b80

Please sign in to comment.