in extensions/vw/tabular/pdf/src/main/java/org/apache/causeway/extensions/tabular/pdf/factory/internal/Paragraph.java [124:533]
public List<String> getLines() {
// memoize this function because it is very expensive
if (lines != null) {
return lines;
}
final List<String> result = new ArrayList<>();
// text and wrappingFunction are immutable, so we only ever need to compute tokens once
if (tokens == null) {
tokens = Tokenizer.tokenize(text, wrappingFunction);
}
int lineCounter = 0;
boolean italic = false;
boolean bold = false;
boolean listElement = false;
PDFont currentFont = font;
int orderListElement = 1;
int numberOfOrderedLists = 0;
int listLevel = 0;
Stack<HTMLListNode> stack = new Stack<>();
final PipelineLayer textInLine = new PipelineLayer();
final PipelineLayer sinceLastWrapPoint = new PipelineLayer();
for (final Token token : tokens) {
switch (token.type()) {
case OPEN_TAG:
if (isBold(token)) {
bold = true;
currentFont = getFont(bold, italic);
} else if (isItalic(token)) {
italic = true;
currentFont = getFont(bold, italic);
} else if (isList(token)) {
listLevel++;
if (token.text().equals("ol")) {
numberOfOrderedLists++;
if(listLevel > 1){
stack.add(new HTMLListNode(orderListElement-1, stack.isEmpty() ? String.valueOf(orderListElement-1)+"." : stack.peek().value() + String.valueOf(orderListElement-1) + "."));
}
orderListElement = 1;
textInLine.push(sinceLastWrapPoint);
// check if you have some text before this list, if you don't then you really don't need extra line break for that
if (textInLine.trimmedWidth() > 0) {
// this is our line
result.add(textInLine.trimmedText());
lineWidths.put(lineCounter, textInLine.trimmedWidth());
mapLineTokens.put(lineCounter, textInLine.tokens());
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
textInLine.reset();
lineCounter++;
}
} else if (token.text().equals("ul")) {
textInLine.push(sinceLastWrapPoint);
// check if you have some text before this list, if you don't then you really don't need extra line break for that
if (textInLine.trimmedWidth() > 0) {
// this is our line
result.add(textInLine.trimmedText());
lineWidths.put(lineCounter, textInLine.trimmedWidth());
mapLineTokens.put(lineCounter, textInLine.tokens());
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
textInLine.reset();
lineCounter++;
}
}
}
sinceLastWrapPoint.push(token);
break;
case CLOSE_TAG:
if (isBold(token)) {
bold = false;
currentFont = getFont(bold, italic);
sinceLastWrapPoint.push(token);
} else if (isItalic(token)) {
italic = false;
currentFont = getFont(bold, italic);
sinceLastWrapPoint.push(token);
} else if (isList(token)) {
listLevel--;
if (token.text().equals("ol")) {
numberOfOrderedLists--;
// reset elements
if(numberOfOrderedLists>0){
orderListElement = stack.peek().orderingNumber()+1;
stack.pop();
}
}
// ensure extra space after each lists
// no need to worry about current line text because last closing <li> tag already done that
if(listLevel == 0){
result.add(" ");
lineWidths.put(lineCounter, 0.0f);
mapLineTokens.put(lineCounter, new ArrayList<Token>());
lineCounter++;
}
} else if (isListElement(token)) {
// wrap at last wrap point?
if (textInLine.width() + sinceLastWrapPoint.trimmedWidth() > width) {
// this is our line
result.add(textInLine.trimmedText());
lineWidths.put(lineCounter, textInLine.trimmedWidth());
mapLineTokens.put(lineCounter, textInLine.tokens());
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
textInLine.reset();
lineCounter++;
// wrapping at last wrap point
if (numberOfOrderedLists>0) {
String orderingNumber = stack.isEmpty() ? String.valueOf(orderListElement) + "." : stack.pop().value() + ".";
stack.add(new HTMLListNode(orderListElement, orderingNumber));
try {
float tab = indentLevel(DEFAULT_TAB);
float orderingNumberAndTab = font.getStringWidth(orderingNumber) + tab;
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING, String
.valueOf(orderingNumberAndTab / 1000 * getFontSize())));
} catch (IOException e) {
e.printStackTrace();
}
orderListElement++;
} else {
try {
// if it's not left aligned then ignore list and list element and deal with it as normal text where <li> mimic <br> behaviour
float tabBullet = getAlign().equals(HorizontalAlignment.LEFT) ? indentLevel(DEFAULT_TAB*Math.max(listLevel - 1, 0) + DEFAULT_TAB_AND_BULLET) : indentLevel(DEFAULT_TAB);
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING,
String.valueOf(tabBullet / 1000 * getFontSize())));
} catch (IOException e) {
e.printStackTrace();
}
}
textInLine.push(sinceLastWrapPoint);
}
// wrapping at this must-have wrap point
textInLine.push(sinceLastWrapPoint);
// this is our line
result.add(textInLine.trimmedText());
lineWidths.put(lineCounter, textInLine.trimmedWidth());
mapLineTokens.put(lineCounter, textInLine.tokens());
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
textInLine.reset();
lineCounter++;
listElement = false;
}
if (isParagraph(token)) {
if (textInLine.width() + sinceLastWrapPoint.trimmedWidth() > width) {
// this is our line
result.add(textInLine.trimmedText());
lineWidths.put(lineCounter, textInLine.trimmedWidth());
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
mapLineTokens.put(lineCounter, textInLine.tokens());
lineCounter++;
textInLine.reset();
}
// wrapping at this must-have wrap point
textInLine.push(sinceLastWrapPoint);
// this is our line
result.add(textInLine.trimmedText());
lineWidths.put(lineCounter, textInLine.trimmedWidth());
mapLineTokens.put(lineCounter, textInLine.tokens());
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
textInLine.reset();
lineCounter++;
// extra spacing because it's a paragraph
result.add(" ");
lineWidths.put(lineCounter, 0.0f);
mapLineTokens.put(lineCounter, new ArrayList<Token>());
lineCounter++;
}
break;
case POSSIBLE_WRAP_POINT:
if (textInLine.width() + sinceLastWrapPoint.trimmedWidth() > width) {
// this is our line
if (!textInLine.isEmpty()) {
result.add(textInLine.trimmedText());
lineWidths.put(lineCounter, textInLine.trimmedWidth());
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
mapLineTokens.put(lineCounter, textInLine.tokens());
lineCounter++;
textInLine.reset();
}
// wrapping at last wrap point
if (listElement) {
if (numberOfOrderedLists>0) {
try {
float tab = getAlign().equals(HorizontalAlignment.LEFT) ? indentLevel(DEFAULT_TAB*Math.max(listLevel - 1, 0) + DEFAULT_TAB) : indentLevel(DEFAULT_TAB);
String orderingNumber = stack.isEmpty() ? String.valueOf(orderListElement) + "." : stack.peek().value() + "." + String.valueOf(orderListElement-1) + ".";
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING,
String.valueOf((tab + font.getStringWidth(orderingNumber)) / 1000 * getFontSize())));
} catch (IOException e) {
e.printStackTrace();
}
} else {
try {
// if it's not left aligned then ignore list and list element and deal with it as normal text where <li> mimic <br> behavior
float tabBullet = getAlign().equals(HorizontalAlignment.LEFT) ? indentLevel(DEFAULT_TAB*Math.max(listLevel - 1, 0) + DEFAULT_TAB_AND_BULLET) : indentLevel(DEFAULT_TAB);
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING,
String.valueOf(tabBullet / 1000 * getFontSize())));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
textInLine.push(sinceLastWrapPoint);
} else {
textInLine.push(sinceLastWrapPoint);
}
break;
case WRAP_POINT:
// wrap at last wrap point?
if (textInLine.width() + sinceLastWrapPoint.trimmedWidth() > width) {
// this is our line
result.add(textInLine.trimmedText());
lineWidths.put(lineCounter, textInLine.trimmedWidth());
mapLineTokens.put(lineCounter, textInLine.tokens());
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
textInLine.reset();
lineCounter++;
// wrapping at last wrap point
if (listElement) {
if(!getAlign().equals(HorizontalAlignment.LEFT)) {
listLevel = 0;
}
if (numberOfOrderedLists>0) {
// String orderingNumber = String.valueOf(orderListElement) + ". ";
String orderingNumber = stack.isEmpty() ? String.valueOf("1") + "." : stack.pop().value() + ". ";
try {
float tab = indentLevel(DEFAULT_TAB);
float orderingNumberAndTab = font.getStringWidth(orderingNumber) + tab;
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING, String
.valueOf(orderingNumberAndTab / 1000 * getFontSize())));
} catch (IOException e) {
e.printStackTrace();
}
} else {
try {
// if it's not left aligned then ignore list and list element and deal with it as normal text where <li> mimic <br> behaviour
float tabBullet = getAlign().equals(HorizontalAlignment.LEFT) ? indentLevel(DEFAULT_TAB*Math.max(listLevel - 1, 0) + DEFAULT_TAB_AND_BULLET) : indentLevel(DEFAULT_TAB);
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING,
String.valueOf(tabBullet / 1000 * getFontSize())));
} catch (IOException e) {
e.printStackTrace();
}
}
}
textInLine.push(sinceLastWrapPoint);
}
if (isParagraph(token)) {
// check if you have some text before this paragraph, if you don't then you really don't need extra line break for that
if (textInLine.trimmedWidth() > 0) {
// extra spacing because it's a paragraph
result.add(" ");
lineWidths.put(lineCounter, 0.0f);
mapLineTokens.put(lineCounter, new ArrayList<Token>());
lineCounter++;
}
} else if (isListElement(token)) {
listElement = true;
// token padding, token bullet
try {
// if it's not left aligned then ignore list and list element and deal with it as normal text where <li> mimic <br> behaviour
float tab = getAlign().equals(HorizontalAlignment.LEFT) ? indentLevel(DEFAULT_TAB*Math.max(listLevel - 1, 0) + DEFAULT_TAB) : indentLevel(DEFAULT_TAB);
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING,
String.valueOf(tab / 1000 * getFontSize())));
if (numberOfOrderedLists>0) {
// if it's ordering list then move depending on your: ordering number + ". "
String orderingNumber;
if(listLevel > 1){
orderingNumber = stack.peek().value() + String.valueOf(orderListElement) + ". ";
} else {
orderingNumber = String.valueOf(orderListElement) + ". ";
}
textInLine.push(currentFont, fontSize, new Token(TokenType.ORDERING, orderingNumber));
orderListElement++;
} else {
// if it's unordered list then just move by bullet character (take care of alignment!)
textInLine.push(currentFont, fontSize, new Token(TokenType.BULLET, " "));
}
} catch (IOException e) {
e.printStackTrace();
}
} else {
// wrapping at this must-have wrap point
textInLine.push(sinceLastWrapPoint);
result.add(textInLine.trimmedText());
lineWidths.put(lineCounter, textInLine.trimmedWidth());
mapLineTokens.put(lineCounter, textInLine.tokens());
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
textInLine.reset();
lineCounter++;
if(listLevel>0){
// preserve current indent
try {
if (numberOfOrderedLists>0) {
float tab = getAlign().equals(HorizontalAlignment.LEFT) ? indentLevel(DEFAULT_TAB*Math.max(listLevel - 1, 0)) : indentLevel(DEFAULT_TAB);
// if it's ordering list then move depending on your: ordering number + ". "
String orderingNumber;
if(listLevel > 1){
orderingNumber = stack.peek().value() + String.valueOf(orderListElement) + ". ";
} else {
orderingNumber = String.valueOf(orderListElement) + ". ";
}
float tabAndOrderingNumber = tab + font.getStringWidth(orderingNumber);
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING, String.valueOf(tabAndOrderingNumber / 1000 * getFontSize())));
orderListElement++;
} else {
if(getAlign().equals(HorizontalAlignment.LEFT)){
float tab = indentLevel(DEFAULT_TAB*Math.max(listLevel - 1, 0) + DEFAULT_TAB + BULLET_SPACE);
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING,
String.valueOf(tab / 1000 * getFontSize())));
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
break;
case TEXT:
try {
String word = token.text();
float wordWidth = token.getWidth(currentFont);
if(wordWidth / 1000f * fontSize > width && width > font.getAverageFontWidth() / 1000f * fontSize) {
// you need to check if you have already something in your line
boolean alreadyTextInLine = false;
if(textInLine.trimmedWidth()>0){
alreadyTextInLine = true;
}
while (wordWidth / 1000f * fontSize > width) {
float width = 0;
float firstPartWordWidth = 0;
float restOfTheWordWidth = 0;
String lastTextToken = word;
StringBuilder firstPartOfWord = new StringBuilder();
StringBuilder restOfTheWord = new StringBuilder();
for (int i = 0; i < lastTextToken.length(); i++) {
char c = lastTextToken.charAt(i);
try {
width += (currentFont.getStringWidth(String.valueOf(c)) / 1000f * fontSize);
} catch (IOException e) {
e.printStackTrace();
}
if(alreadyTextInLine){
if (width < this.width - textInLine.trimmedWidth()) {
firstPartOfWord.append(c);
firstPartWordWidth = Math.max(width, firstPartWordWidth);
} else {
restOfTheWord.append(c);
restOfTheWordWidth = Math.max(width, restOfTheWordWidth);
}
} else {
if (width < this.width) {
firstPartOfWord.append(c);
firstPartWordWidth = Math.max(width, firstPartWordWidth);
} else {
if(i==0){
firstPartOfWord.append(c);
for (int j = 1; j< lastTextToken.length(); j++){
restOfTheWord.append(lastTextToken.charAt(j));
}
break;
} else {
restOfTheWord.append(c);
restOfTheWordWidth = Math.max(width, restOfTheWordWidth);
}
}
}
}
// reset
alreadyTextInLine = false;
sinceLastWrapPoint.push(currentFont, fontSize,
Token.text(firstPartOfWord.toString()));
textInLine.push(sinceLastWrapPoint);
// this is our line
result.add(textInLine.trimmedText());
lineWidths.put(lineCounter, textInLine.trimmedWidth());
mapLineTokens.put(lineCounter, textInLine.tokens());
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
textInLine.reset();
lineCounter++;
word = restOfTheWord.toString();
wordWidth = currentFont.getStringWidth(word);
}
sinceLastWrapPoint.push(currentFont, fontSize, Token.text(word));
} else {
sinceLastWrapPoint.push(currentFont, fontSize, token);
}
} catch (IOException e) {
e.printStackTrace();
}
break;
case BULLET, ORDERING, PADDING:
break;
}
}
if (sinceLastWrapPoint.trimmedWidth() + textInLine.trimmedWidth() > 0) {
textInLine.push(sinceLastWrapPoint);
result.add(textInLine.trimmedText());
lineWidths.put(lineCounter, textInLine.trimmedWidth());
mapLineTokens.put(lineCounter, textInLine.tokens());
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
}
lines = result;
return result;
}