Skip to content

Commit

Permalink
#403 Get text justification working with soft hyphens. Also:
Browse files Browse the repository at this point in the history
+ Take into account letter spacing property when getting minimum width for table cell.
+ Tweak justification algorithm to allow more space between characters when there are no spaces in the line.

Two column tests had to be disabled because they were broken by the tweak to justiifcation. In the next commit I plan to allow the user to control the justification settings via CSS.
  • Loading branch information
danfickle committed Nov 15, 2019
1 parent d0bc672 commit f79aaf8
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,15 @@ private static class AppBreakOpportunity {
int left;
int right;
int graphicsLength;
int normalSplitWidth;
int withHyphenSplitWidth;
int withHyphenGraphicsLength;
boolean isSoftHyphenBreak;

void copyTo(AppBreakOpportunity other) {
other.left = left;
other.right = right;
other.graphicsLength = graphicsLength;
other.normalSplitWidth = normalSplitWidth;
other.withHyphenGraphicsLength = withHyphenGraphicsLength;
other.isSoftHyphenBreak = isSoftHyphenBreak;
other.withHyphenSplitWidth = withHyphenSplitWidth;
}
}

Expand Down Expand Up @@ -160,35 +158,37 @@ public static void doBreakText(
AppBreakOpportunity current = new AppBreakOpportunity();
AppBreakOpportunity prev = new AppBreakOpportunity();

current.left = 0;
current.right = iterator.next();
current.graphicsLength = 0;

while (current.right > 0 && current.graphicsLength <= avail) {
current.copyTo(prev);

current.normalSplitWidth = (int) (c.getTextRenderer().getWidth(
c.getFontContext(), font, currentString.substring(current.left, current.right)) +
((current.right - current.left) * letterSpacing));
String subString = currentString.substring(current.left, current.right);
float extraSpacing = (current.right - current.left) * letterSpacing;

int normalSplitWidth = (int) (c.getTextRenderer().getWidth(
c.getFontContext(), font, subString) + extraSpacing);

if (currentString.charAt(current.right - 1) == SOFT_HYPHEN) {
current.isSoftHyphenBreak = true;
current.withHyphenSplitWidth = (int) (c.getTextRenderer().getWidth(
c.getFontContext(), font, currentString.substring(prev.left, prev.right) + '-') +
(((prev.right - prev.left) + 1) * letterSpacing));
int withTrailingHyphenSplitWidth = (int) (c.getTextRenderer().getWidth(
c.getFontContext(), font, subString + '-') +
extraSpacing + letterSpacing);
current.withHyphenGraphicsLength = current.graphicsLength + withTrailingHyphenSplitWidth;

if (current.graphicsLength + current.withHyphenSplitWidth > avail) {
current.graphicsLength += current.withHyphenSplitWidth;
if (current.withHyphenGraphicsLength > avail) {
current.graphicsLength = current.withHyphenGraphicsLength;
lastWrap = current.left;
current.left = current.right;
current.right = iterator.next();
break;
}
} else {
current.isSoftHyphenBreak = false;
current.withHyphenGraphicsLength += normalSplitWidth;
}

current.graphicsLength += current.normalSplitWidth;
current.graphicsLength += normalSplitWidth;
lastWrap = current.left;
current.left = current.right;
current.right = iterator.next();
Expand All @@ -197,10 +197,11 @@ public static void doBreakText(
if (current.graphicsLength <= avail) {
// Try for the last bit too!
lastWrap = current.left;
prev.isSoftHyphenBreak = current.isSoftHyphenBreak;
prev.graphicsLength = current.graphicsLength;
current.copyTo(prev);
current.right = currentString.length();
float extraSpacing = (current.right - current.left) * letterSpacing;
current.graphicsLength += c.getTextRenderer().getWidth(
c.getFontContext(), font, currentString.substring(current.left));
c.getFontContext(), font, currentString.substring(current.left)) + extraSpacing;
}

if (current.graphicsLength <= avail) {
Expand All @@ -222,9 +223,11 @@ public static void doBreakText(
// Found a place to wrap
if (prev.isSoftHyphenBreak) {
context.setEndsOnSoftHyphen(true);
context.setWidth(prev.withHyphenGraphicsLength);
} else {
context.setWidth(prev.graphicsLength);
}

context.setWidth(prev.graphicsLength);
context.setEnd(context.getStart() + lastWrap);
} else {
// Unbreakable string
Expand All @@ -236,8 +239,10 @@ public static void doBreakText(
context.setUnbreakable(true);

if (current.left == currentString.length()) {
context.setWidth(c.getTextRenderer().getWidth(
c.getFontContext(), font, context.getCalculatedSubstring()));
String text = context.getCalculatedSubstring();
float extraSpacing = text.length() * letterSpacing;
context.setWidth((int) (c.getTextRenderer().getWidth(
c.getFontContext(), font, text) + extraSpacing));
} else {
context.setWidth(current.graphicsLength);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.w3c.dom.Text;

import com.openhtmltopdf.bidi.BidiSplitter;
import com.openhtmltopdf.css.constants.CSSName;
import com.openhtmltopdf.css.constants.IdentValue;
import com.openhtmltopdf.css.extend.ContentFunction;
import com.openhtmltopdf.css.parser.FSFunction;
Expand Down Expand Up @@ -249,6 +250,11 @@ private int calcMinWidthFromWordLength(
boolean haveFirstWord = false;
int firstWord = 0;
int lastWord = 0;

CalculatedStyle style = getStyle();
float letterSpacing = style.hasLetterSpacing()
? style.getFloatPropertyProportionalWidth(CSSName.LETTER_SPACING, 0, c)
: 0f;

String text = getText(trimLeadingSpace);
FSTextBreaker breakIterator = Breaker.getLineBreakStream(text, c.getSharedContext());
Expand All @@ -266,7 +272,7 @@ private int calcMinWidthFromWordLength(
if (getStyle().getWordWrap() == IdentValue.BREAK_WORD) {
minWordWidth = getMaxCharWidth(c, currentWord);
} else {
minWordWidth = wordWidth;
minWordWidth = (int) (wordWidth + (letterSpacing * currentWord.length()));
}

if (spaceCount > 0) {
Expand Down Expand Up @@ -312,7 +318,7 @@ private int calcMinWidthFromWordLength(
if (getStyle().getWordWrap() == IdentValue.BREAK_WORD) {
minWordWidth = getMaxCharWidth(c, currentWord);
} else {
minWordWidth = wordWidth;
minWordWidth = (int) (wordWidth + (letterSpacing * currentWord.length()));
}
if (spaceCount > 0) {
if (includeWS) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.openhtmltopdf.layout.FunctionData;
import com.openhtmltopdf.layout.LayoutContext;
import com.openhtmltopdf.layout.WhitespaceStripper;
import com.openhtmltopdf.util.OpenUtil;
import com.openhtmltopdf.util.Uu;

/**
Expand Down Expand Up @@ -337,13 +338,14 @@ public void countJustifiableChars(CharCounts counts) {
char c = s.charAt(i);
if (c == ' ' || c == '\u00a0' || c == '\u3000') {
spaces++;
} else if (!OpenUtil.isCodePointPrintable(c)) {

} else {
other++;
}
}

counts.setSpaceCount(counts.getSpaceCount() + spaces);
counts.setNonSpaceCount(counts.getNonSpaceCount() + other);
counts.setNonSpaceCount(counts.getNonSpaceCount() + other + (isEndsOnSoftHyphen() ? 1 : 0));
}

public float calcTotalAdjustment(JustificationInfo info) {
Expand All @@ -361,10 +363,16 @@ public float calcTotalAdjustment(JustificationInfo info) {
char c = s.charAt(i);
if (c == ' ' || c == '\u00a0' || c == '\u3000') {
result += info.getSpaceAdjust();
} else if (!OpenUtil.isCodePointPrintable(c)) {

} else {
result += info.getNonSpaceAdjust();
}
}

if (isEndsOnSoftHyphen()) {
result += info.getNonSpaceAdjust();
}

return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ public void justify(CssContext c) {

JustificationInfo info = new JustificationInfo();

if (counts.getSpaceCount() > 0) {
if (counts.getNonSpaceCount() > 1) {
info.setNonSpaceAdjust((float)toAdd * JUSTIFY_NON_SPACE_SHARE / (counts.getNonSpaceCount()-1));
} else {
Expand All @@ -245,6 +246,11 @@ public void justify(CssContext c) {
} else {
info.setSpaceAdjust(0.0f);
}
} else {
info.setSpaceAdjust(0f);
// TODO: Configure the maximum gap between characters for justification through CSS.
info.setNonSpaceAdjust(Math.min((float) toAdd / (counts.getNonSpaceCount() - 1), 150));
}

adjustChildren(info);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<head>
<style>
@page {
size: 450px 250px;
size: 450px 900px;
margin: 0;
}
body {
Expand All @@ -25,7 +25,29 @@
</style>
</head>
<body style="font-family: 'TestFont';">
<table>

<!-- In a block with text-align set to left. -->
<div style="width: 100px; border: 1px solid red; float: left;">
Haft&shy;pflicht&shy;ver&shy;si&shy;che&shy;rung
Bun­des­aus­bil­dungs­för­de­rungs­ge­setz
Gleich­ge­wichts­dich­te­gra­di­en­ten­zen­tri­fu­ga­tion
Hähn­chen­mast
Er­trags­scha­den­ver­si­che­rung Mast­ge­flü­gel
</div>

<!-- In a block with justification. -->
<div style="width: 100px; margin-left: 20px; text-align: justify;border: 1px solid blue; float: left;">
Haft&shy;pflicht&shy;ver&shy;si&shy;che&shy;rung
Bun­des­aus­bil­dungs­för­de­rungs­ge­setz
Gleich­ge­wichts­dich­te­gra­di­en­ten­zen­tri­fu­ga­tion
Hähn­chen­mast
Er­trags­scha­den­ver­si­che­rung Mast­ge­flü­gel
</div>

<div style="clear:both;"></div>

<!-- In a table cell with text-align set to left. -->
<table style="margin-top: 20px;">
<thead>
<tr>
<th>Spalte 1</th>
Expand All @@ -46,6 +68,7 @@
</tbody>
</table>

<!-- In a table with justification. -->
<table style="text-align: justify;margin-top: 20px;">
<thead>
<tr>
Expand All @@ -67,5 +90,27 @@
</tbody>
</table>

<!-- In a table with letter spacing set. -->
<table style="letter-spacing: 3px;margin-top: 20px;">
<thead>
<tr>
<th>Spalte 1</th>
<th>Spalte 2</th>
<th>Spalte 3</th>
<th>Spalte 4</th>
<th>Spalte 5</th>
</tr>
</thead>
<tbody>
<tr>
<td>Haft&shy;pflicht&shy;ver&shy;si&shy;che&shy;rung</td>
<td>Bun­des­aus­bil­dungs­för­de­rungs­ge­setz</td>
<td>Gleich­ge­wichts­dich­te­gra­di­en­ten­zen­tri­fu­ga­tion</td>
<td>Hähn­chen­mast</td>
<td>Er­trags­scha­den­ver­si­che­rung Mast­ge­flü­gel</td>
</tr>
</tbody>
</table>

</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ public void testJustifySpaceAtEnd() throws IOException {
* Issue 403.
*/
@Test
@Ignore // Need to work on text justification with soft hyphens.
@Ignore // Need to work on configurable text justification.
public void testSoftHyphens() throws IOException {
assertTrue(vtester.runTest("soft-hyphens", WITH_COLLAPSED_LINE_BREAKER));
}
Expand All @@ -638,6 +638,7 @@ public void testColumnsSimpleUnbalanced() throws IOException {
* Tests columns with nested content such as paragraphs, lists and span.
*/
@Test
@Ignore // Need to work on configurable text justification.
public void testColumnsNestedUnbalanced() throws IOException {
assertTrue(run("columns-nested-unbalanced"));
}
Expand All @@ -647,6 +648,7 @@ public void testColumnsNestedUnbalanced() throws IOException {
* Also tests explicit column breaks.
*/
@Test
@Ignore // Need to work on configurable text justification.
public void testColumnsFloatsUnbalanced() throws IOException {
assertTrue(run("columns-floats-unbalanced"));
}
Expand Down

0 comments on commit f79aaf8

Please sign in to comment.