IndustrialWires/src/main/java/malte0811/industrialwires/client/manual/TextSplitter.java
2018-09-03 20:35:23 +02:00

252 lines
7.8 KiB
Java

/*
* This file is part of Industrial Wires.
* Copyright (C) 2016-2018 malte0811
* Industrial Wires is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* Industrial Wires is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with Industrial Wires. If not, see <http://www.gnu.org/licenses/>.
*/
package malte0811.industrialwires.client.manual;
import blusunrize.lib.manual.IManualPage;
import blusunrize.lib.manual.ManualInstance;
import blusunrize.lib.manual.ManualPages;
import gnu.trove.map.TIntIntMap;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntIntHashMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public class TextSplitter {
private final Function<String, Integer> width;
private final TIntObjectMap<Map<Integer, Page>> specialByAnchor = new TIntObjectHashMap<>();
private final TIntObjectMap<Page> specialByPage = new TIntObjectHashMap<>();
private final List<List<String>> entry = new ArrayList<>();
private final Function<String, String> tokenTransform;
private final int lineWidth;
private final int linesPerPage;
private TIntIntMap pageByAnchor = new TIntIntHashMap();
private final Function<String, IManualPage> defaultPage;
public TextSplitter(Function<String, Integer> w, int lineWidthPixel, int linesPerPage,
Function<String, IManualPage> defPage, Function<String, String> tokenTransform) {
width = w;
this.lineWidth = lineWidthPixel;
this.linesPerPage = linesPerPage;
this.tokenTransform = tokenTransform;
this.defaultPage = defPage;
}
public TextSplitter(ManualInstance m) {
this(m.fontRenderer::getStringWidth, 120, 16, s-> new ManualPages.Text(m, s), (s) -> s);
}
public TextSplitter(ManualInstance m, Function<String, String> tokenTransform) {
this(m.fontRenderer::getStringWidth, 120, 16,s-> new ManualPages.Text(m, s), tokenTransform);
}
public void clearSpecialByPage() {
specialByPage.clear();
}
public void clearSpecialByAnchor() {
specialByAnchor.clear();
}
public void addSpecialPage(int ref, int offset, int lines, Function<String, IManualPage> factory) {
if (offset < 0 || (ref != -1 && ref < 0)) {
throw new IllegalArgumentException();
}
if (!specialByAnchor.containsKey(ref)) {
specialByAnchor.put(ref, new HashMap<>());
}
specialByAnchor.get(ref).put(offset, new Page(lines, factory));
}
// I added labels to all break statements to make it more readable
@SuppressWarnings({"UnnecessaryLabelOnBreakStatement", "UnusedLabel"})
public void split(String in) {
clearSpecialByPage();
entry.clear();
String[] wordsAndSpaces = splitWhitespace(in);
int pos = 0;
List<String> overflow = new ArrayList<>();
updateSpecials(-1, 0, 0, -1);
entry:
while (pos < wordsAndSpaces.length) {
List<String> page = new ArrayList<>(overflow);
overflow.clear();
page:
while (page.size() < getLinesOnPage(entry.size()) && pos < wordsAndSpaces.length) {
String line = "";
int currWidth = 0;
line:
while (pos < wordsAndSpaces.length && currWidth < lineWidth) {
String token = tokenTransform.apply(wordsAndSpaces[pos]);
int textWidth = getWidth(token);
if (currWidth + textWidth < lineWidth || line.length() == 0) {
pos++;
if (token.equals("<np>")) {
page.add(line);
break page;
} else if (token.equals("<br>")) {
break line;
} else if (token.startsWith("<&") && token.endsWith(">")) {
int id = Integer.parseInt(token.substring(2, token.length() - 1));
int pageForId = entry.size();
Map<Integer, Page> specialForId = specialByAnchor.get(id);
if (specialForId != null && specialForId.containsKey(0)) {
if (page.size() >= specialForId.get(0).lines) {
pageForId++;
}
}
//New page if there is already a special element on this page
if (updateSpecials(id, pageForId, page.size(), entry.size())) {
page.add(line);
pos--;
break page;
}
} else if (!Character.isWhitespace(token.charAt(0)) || line.length() != 0) {//Don't add whitespace at the start of a line
line += token;
currWidth += textWidth;
}
} else {
break line;
}
}
line = line.trim();
if (!line.isEmpty())
page.add(line);
}
if (!(page.stream().allMatch(String::isEmpty) && pos == wordsAndSpaces.length)) {
int linesMax = getLinesOnPage(entry.size());
if (page.size() > linesMax) {
overflow.addAll(page.subList(linesMax, page.size()));
page = page.subList(0, linesMax);
}
entry.add(page);
}
}
}
public List<IManualPage> toManualEntry() {
List<IManualPage> ret = new ArrayList<>(entry.size());
for (int i = 0; i < entry.size(); i++) {
String s = entry.get(i).stream().collect(Collectors.joining("\n"));
if (specialByPage.containsKey(i)) {
ret.add(specialByPage.get(i).factory.apply(s));
} else {
ret.add(defaultPage.apply(s));
}
}
return ret;
}
private int getWidth(String text) {
switch (text) {
case "<br>":
case "<np>":
return 0;
default:
if (text.startsWith("<link;")) {
text = text.substring(text.indexOf(';') + 1);
text = text.substring(text.indexOf(';') + 1, text.lastIndexOf(';'));
}
return width.apply(text);
}
}
private int getLinesOnPage(int id) {
if (specialByPage.containsKey(id)) {
return specialByPage.get(id).lines;
}
return linesPerPage;
}
private boolean updateSpecials(int ref, int pageRef, int currLine, int currPage) {
if (specialByAnchor.containsKey(ref)) {
TIntObjectMap<Page> specialByPageTmp = new TIntObjectHashMap<>();
for (Map.Entry<Integer, Page> entry : specialByAnchor.get(ref).entrySet()) {
int specialPage = pageRef + entry.getKey();
if (specialByPage.containsKey(specialPage)) {
return true;
}
if (entry.getKey()==0 && currPage==pageRef&&entry.getValue().lines<=currLine) {
return true;
}
specialByPageTmp.put(specialPage, entry.getValue());
}
specialByPage.putAll(specialByPageTmp);
} else if (ref != -1) {//Default reference for page 0
System.out.println("WARNING: Reference " + ref + " was found, but no special pages were registered for it");
}
pageByAnchor.put(ref, pageRef);
return false;
}
private String[] splitWhitespace(String in) {
List<String> parts = new ArrayList<>();
for (int i = 0; i < in.length(); ) {
StringBuilder here = new StringBuilder();
char first = in.charAt(i);
here.append(first);
i++;
for (; i < in.length(); ) {
char hereC = in.charAt(i);
byte action = shouldSplit(first, hereC);
if ((action & 1) != 0) {
here.append(in.charAt(i));
i++;
}
if ((action & 2) != 0 || (action & 1) == 0) {
break;
}
}
parts.add(here.toString());
}
return parts.toArray(new String[0]);
}
/**
* @return &1: add
* &2: end here
*/
private byte shouldSplit(char start, char here) {
byte ret = 0b01;
if (Character.isWhitespace(start) ^ Character.isWhitespace(here)) {
ret = 0b10;
}
if (here == '<') {
ret = 0b10;
}
if (start == '<') {
ret = 0b01;
if (here == '>') {
ret |= 0b10;
}
}
return ret;
}
private class Page {
final int lines;
final Function<String, IManualPage> factory;
public Page(int l, Function<String, IManualPage> f) {
factory = f;
lines = l;
}
}
}