All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pe.puyu.jticketdesing.core.table.SweetTableDesign Maven / Gradle / Ivy

The newest version!
package pe.puyu.jticketdesing.core.table;

import com.github.anastaciocintra.escpos.EscPos;
import com.github.anastaciocintra.escpos.Style.FontSize;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import pe.puyu.jticketdesing.core.DesignerHelper;
import pe.puyu.jticketdesing.metadata.PrinterPropertiesReader;
import pe.puyu.jticketdesing.util.JsonUtil;
import pe.puyu.jticketdesing.util.StringUtils;
import pe.puyu.jticketdesing.util.escpos.*;

import java.io.ByteArrayOutputStream;
import java.util.*;

public class SweetTableDesign {

	private final JsonObject data;
	private EscPos escpos;
	private final DesignerHelper helper;

	public SweetTableDesign(JsonObject data) {
		this.data = data;
		this.helper = new DesignerHelper<>(new PrinterPropertiesReader(data));
	}

	public SweetTableDesign(String jsonString) {
		this(JsonParser.parseString(jsonString).getAsJsonObject());
	}

	public byte[] getBytes() {
		try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
			// Important!! initialize escpos, because it is not initialized in the constructor
			this.escpos = new EscPos(buffer);
			this.escpos.setCharacterCodeTable(helper.properties().charCodeTable());
			designTable();
			return buffer.toByteArray();
		} catch (Exception e) {
			throw new RuntimeException(String.format("SweetTableDesign exception with message: %s", e.getMessage()), e);
		}
	}

	private void designTable() throws Exception {
		JsonArray tables = Optional.ofNullable(this.data.get("tables")).orElseGet(JsonArray::new).getAsJsonArray();
		titleAndDetailsLayout();
		for (JsonElement table : tables) {
			JsonObject tableObject = table.getAsJsonObject();
			int numberOfColumns = calcNumberOfColumns(tableObject);

			// ##### calcular los anchos de celda #########
			JsonArray widthPercents = new JsonArray();
			if (tableObject.has("widthPercents") && tableObject.get("widthPercents").isJsonArray()) {
				widthPercents = tableObject.get("widthPercents").getAsJsonArray();
			}
			List listWidthPercents = fillMissingWidthPercents(widthPercents, numberOfColumns);
			listWidthPercents = calculateWidthPercent(listWidthPercents);
			List cellWidths = calculateCellWidths(listWidthPercents);

			JsonArray headers = Optional.ofNullable(tableObject.get("headers")).orElseGet(JsonArray::new).getAsJsonArray();
			JsonArray footer = Optional.ofNullable(tableObject.get("footer")).orElseGet(JsonArray::new).getAsJsonArray();

			// ##### normalizar encabezados por capas #########
			headers = fillMissingColumns(headers, cellWidths.size(), getDefaultHeaderProperties());
			headers = normalizeHeaders(headers);      // standard objects

			titleTableLayout(tableObject);
			char separatorCell = getSeparator(tableObject);
			headersTableLayout(headers, cellWidths, separatorCell);
			bodyTableLayout(tableObject, cellWidths, separatorCell);
			footerTableLayout(footer, cellWidths, separatorCell);
		}
		finalDetailsLayout();
		helper.paperCut(escpos);
	}

	private void titleAndDetailsLayout() throws Exception {
		EscPosWrapper escPosWrapper = new EscPosWrapper(this.escpos);
		StyleText titleStyle = helper.styleNormalizeBuilder().bold(true).align(JustifyAlign.CENTER).build();
		StyleText detailsStyle = helper.styleNormalizeBuilder().align(JustifyAlign.LEFT).build();
		String[] fields = {"title", "details"};
		for (String field : fields) {
			StyleText style = field.equalsIgnoreCase("title") ? titleStyle : detailsStyle;
			if (this.data.has(field) && !this.data.get(field).isJsonNull() && !this.data.get(field).isJsonObject()) {
				JsonElement value = this.data.get(field);
				JsonArray values = JsonUtil.normalizeToJsonArray(value);
				for (JsonElement element : values) {
					var lines = StringUtils.wrapText(element.getAsString(), helper.properties().width(), 1);
					for (String line : lines) {
						escPosWrapper.printText(line, helper.properties().width(), style);
					}
				}
			}
			escPosWrapper.printLine(' ', 1);
		}
	}

	private int calcNumberOfColumns(JsonObject table) {
		JsonArray headers = Optional.ofNullable(table.get("headers")).orElseGet(JsonArray::new).getAsJsonArray();
		JsonArray body = Optional.ofNullable(table.get("body")).orElseGet(JsonArray::new).getAsJsonArray();
		body = JsonUtil.filter(body, JsonElement::isJsonArray);
		int max = headers.getAsJsonArray().size();
		for (JsonElement element : body) {
			JsonArray row = element.getAsJsonArray();
			if (row.size() > max)
				max = row.size();
		}
		return max;
	}

	private void titleTableLayout(JsonObject table) throws Exception {
		if (!table.has("title")) {
			return;
		}
		JsonObject defaults = new JsonObject();
		defaults.addProperty("align", "center");
		defaults.addProperty("text", ""); // default value
		defaults.addProperty("fontSize", 1);
		defaults.addProperty("decorator", "-");
		JsonObject title = JsonUtil.normalizeToJsonObject(table.get("title"), "text", "", defaults);
		JustifyAlign align = JustifyAlign.fromValue(title.get("align").getAsString()); // on error fromValue return LEFT
		FontSize fontSize = StyleEscPosUtil.toFontSize(title.get("fontSize").getAsInt()); // on error toFontSize return 1
		String text = title.get("text").getAsString();
		String decorator = title.get("decorator").getAsString();
		char borderCharDecorator = decorator.isEmpty() ? ' ' : decorator.charAt(0);
		EscPosWrapper escPosWrapper = new EscPosWrapper(escpos);
		StyleText styleText = helper.styleNormalizeBuilder()
			.fontSize(fontSize)
			.align(align)
			.bold(true)
			.build();
		printBoldLine(borderCharDecorator);
		escPosWrapper.printText(text, helper.properties().width(), styleText);
		printBoldLine(borderCharDecorator);
	}

	private void headersTableLayout(JsonArray headers, List cellWidths, char separator) throws Exception {
		// construyendo los table cell
		if (headers.isEmpty())
			return;
		List row = JsonUtil.mapToList(headers, (currentIndex, currentElement) -> {
			JsonObject header = currentElement.getAsJsonObject();
			String text = header.get("text").getAsString();
			int width = cellWidths.get(currentIndex);
			String align = header.get("align").getAsString();
			StyleText styleText = helper.styleNormalizeBuilder().align(align).bold(true).build();
			return new TableCell(text, width, styleText);
		});
		// imprimir la fila de table cells
		printRow(row, separator);
		printLine();
	}

	private void bodyTableLayout(JsonObject tableObject, List cellWidths, char separator) throws Exception {
		JsonArray body = Optional.ofNullable(tableObject.get("body")).orElseGet(JsonArray::new).getAsJsonArray();
		body = normalizeBody(body); //normalize to list of standard objects or json array
		if (cellWidths.isEmpty()) return;
		EscPosWrapper escPosWrapper = new EscPosWrapper(escpos);
		for (int i = 0; i < body.size(); ++i) {
			JsonElement element = body.get(i);
			if (element.isJsonObject()) {
				JsonObject subtitle = element.getAsJsonObject();
				String text = subtitle.get("text").getAsString();
				String align = subtitle.get("align").getAsString();
				boolean bold = subtitle.get("bold").getAsBoolean();
				StyleText styleText = helper.styleNormalizeBuilder().align(align).bold(bold).build();
				if (text.length() == 1) {
					escPosWrapper.printLine(text.charAt(0), helper.properties().width(), styleText);
					continue;
				}
				escPosWrapper.printText(text, helper.properties().width(), styleText);
			} else if (element.isJsonArray()) {
				JsonArray row = element.getAsJsonArray();
				row = fillMissingColumns(row, cellWidths.size(), new JsonPrimitive(""));
				List cells = JsonUtil.mapToList(row, (currentIndex, currentItem) -> {
					JsonObject defaultItem = new JsonObject();
					defaultItem.addProperty("text", "");
					JsonObject item = JsonUtil.normalizeToJsonObject(currentItem, "text", "", defaultItem);
					String text = item.get("text").getAsString();
					StyleText styleText = getCellBodyStyle(tableObject, currentIndex);
					if (item.has("align") && item.get("align").isJsonPrimitive()) {
						styleText = StyleText.builder(styleText).align(item.get("align").getAsString()).build();
					}
					int width = cellWidths.get(currentIndex);
					return new TableCell(text, width, styleText);
				});
				printRow(cells, separator);
			}
		}
	}

	private void footerTableLayout(JsonArray footer, List cellWidths, char separator) throws Exception {
		printBoldLine('-');
		EscPosWrapper escPosWrapper = new EscPosWrapper(escpos);
		int startCell = 0;
		List row = new LinkedList<>();
		for (int i = 0; i < footer.size(); ++i) {
			JsonElement element = footer.get(i);
			JsonObject footerItem = JsonUtil.normalizeToJsonObject(element, "text", "", getDefaultFooterProperties());
			int span = footerItem.get("span").getAsInt();
			int endCell = Math.min(startCell + span, cellWidths.size());
			int width = cellWidths.subList(startCell, endCell).stream().reduce(0, Integer::sum);
			String text = footerItem.get("text").getAsString();
			StyleText styleText = helper.styleNormalizeBuilder()
				.bold(footerItem.get("bold").getAsBoolean())
				.align(footerItem.get("align").getAsString())
				.build();
			row.add(new TableCell(text, width, styleText));
			startCell = endCell;
		}
		printRow(row, separator);
		escPosWrapper.printLine(' ', helper.properties().width());
	}

	private void finalDetailsLayout() throws Exception {
		if (!this.data.has("finalDetails") || !this.data.get("finalDetails").isJsonArray())
			return;
		JsonArray finalDetails = this.data.getAsJsonArray("finalDetails");
		JsonObject defaultFinalDetail = new JsonObject();
		defaultFinalDetail.addProperty("align", "center");
		defaultFinalDetail.addProperty("bold", false);
		defaultFinalDetail.addProperty("text", "");
		EscPosWrapper escPosWrapper = new EscPosWrapper(escpos);
		for (JsonElement element : finalDetails) {
			JsonObject finalDetail = JsonUtil.normalizeToJsonObject(element, "text", "", defaultFinalDetail);
			String text = finalDetail.get("text").getAsString();
			StyleText styleText = helper.styleNormalizeBuilder()
				.align(finalDetail.get("align").getAsString())
				.bold(finalDetail.get("bold").getAsBoolean())
				.build();
			escPosWrapper.printText(text, helper.properties().width(), styleText);
		}
	}

	private JsonArray normalizeHeaders(JsonArray headers) {
		return JsonUtil.map(
			headers,
			element -> JsonUtil.normalizeToJsonObject(element, "text", "", getDefaultHeaderProperties())
		);
	}

	private JsonArray normalizeBody(JsonArray body) {
		JsonObject subtitleDefaultProperties = new JsonObject();
		subtitleDefaultProperties.addProperty("text", "");
		subtitleDefaultProperties.addProperty("align", "center");
		subtitleDefaultProperties.addProperty("bold", false);
		body = JsonUtil.map(body, row -> {
			if (row.isJsonArray()) return row;
			return JsonUtil.normalizeToJsonObject(row, "text", "", subtitleDefaultProperties);
		});
		return body;
	}

	private JsonArray fillMissingColumns(JsonArray array, int maxColumns, JsonElement defaultValue) {
		JsonArray result = new JsonArray();
		array.forEach(result::add);
		int missing = result.isEmpty() ? 0 : Math.max(maxColumns - result.size(), 0); // evitar llenar un array vacio
		for (int i = 0; i < missing; ++i) {
			result.add(defaultValue);
		}
		return result;
	}

	private JsonObject getDefaultFooterProperties() {
		JsonObject defaults = new JsonObject();
		defaults.addProperty("align", "left");
		defaults.addProperty("bold", false);
		defaults.addProperty("text", "");
		defaults.addProperty("span", 1);
		return defaults;
	}

	private JsonObject getDefaultHeaderProperties() {
		JsonObject defaults = new JsonObject();
		defaults.addProperty("align", "center");
		defaults.addProperty("text", ""); // default value
		return defaults;
	}

	private List fillMissingWidthPercents(JsonArray cellWidths, int maxNumberOfColumns) {
		List widthPercentsArray = new ArrayList<>();
		for (int i = 0; i < maxNumberOfColumns; ++i) {
			int widthPercent = 0;
			if (i < cellWidths.size() && cellWidths.get(i).isJsonPrimitive()) {
				widthPercent = Math.max(0, cellWidths.get(i).getAsInt());
			}
			widthPercentsArray.add(widthPercent);
		}
		return widthPercentsArray;
	}


	private List calculateWidthPercent(List widthPercentsArray) {
		// asignar widthPercent a las celdas con ancho automatico (0)
		int numberOfCellsWithAutoWidth = (int) widthPercentsArray.stream().filter(widthPercent -> widthPercent <= 0).count();
		int coveredWidthPercent = Math.min(widthPercentsArray.stream().reduce(0, Integer::sum), 100);
		int uncoveredWidthPercent = Math.max(100 - coveredWidthPercent, 0);
		return widthPercentsArray.stream()
			.map(widthPercent -> {
				if (widthPercent == 0) {
					return uncoveredWidthPercent / numberOfCellsWithAutoWidth;
				}
				return widthPercent;
			})
			.toList();
	}

	private List calculateCellWidths(List widthPercentsArray) {
		int paperWidth = helper.properties().width();
		List cellWidths = widthPercentsArray.stream()
			.map(widthPercent -> paperWidth * widthPercent / 100)
			.toList();
		final int totalCoveredWidth = cellWidths.stream().reduce(0, Integer::sum);
		final int maxWidth = cellWidths.stream().max(Integer::compare).orElse(0);
		boolean hasModify = false;
		List result = new ArrayList<>();
		for (int width : cellWidths) {
			if (width == maxWidth && !hasModify) {
				result.add(width + (paperWidth - totalCoveredWidth));
				hasModify = true;
			} else {
				result.add(width);
			}
		}
		return result;
	}

	private StyleText getCellBodyStyle(JsonObject tableObject, int index) {
		JsonObject defaultCellBodyStyle = new JsonObject();
		defaultCellBodyStyle.addProperty("align", "left");
		defaultCellBodyStyle.addProperty("bold", false);
		StyleTextBuilder defaultStyle = helper.styleNormalizeBuilder()
			.bold(defaultCellBodyStyle.get("bold").getAsBoolean())
			.align(defaultCellBodyStyle.get("align").getAsString());
		if (!tableObject.has("cellBodyStyles")) {
			return defaultStyle.build();
		}
		JsonObject cellBodyStyles = tableObject.get("cellBodyStyles").getAsJsonObject();
		String key = String.valueOf(Math.max(index, 0));
		if (!cellBodyStyles.has(key)) {
			return defaultStyle.build();
		}
		JsonElement cellBodyStyle = cellBodyStyles.get(key);
		JsonObject styleObject = JsonUtil.normalizeToJsonObject(cellBodyStyle, "align", "left", defaultCellBodyStyle);
		return defaultStyle
			.align(styleObject.get("align").getAsString())
			.bold(styleObject.get("bold").getAsBoolean())
			.build();
	}

	private char getSeparator(JsonObject tableObject) {
		if (!tableObject.has("separator") || !tableObject.get("separator").isJsonPrimitive()) {
			return ' ';
		}
		return tableObject.get("separator").getAsString().charAt(0);
	}

	private void printRow(List row, char separatorChar) throws Exception {
		List> matrix = new LinkedList<>();
		int maxNumberOfCuts = -1;
		for (int i = 0; i < row.size(); ++i) {
			boolean isLastRow = i + 1 == row.size();
			TableCell cell = row.get(i);
			List splitCells = new LinkedList<>();
			int width = !isLastRow ? cell.width() - 1 : cell.width(); // ancho reducido para los espacios intermedios
			List splits = StringUtils.wrapText(cell.text(), width, StyleEscPosUtil.valueFontSize(cell.style().getFontWidth()));
			maxNumberOfCuts = Math.max(splits.size(), maxNumberOfCuts);
			for (String split : splits) {
				splitCells.add(new TableCell(split, cell.width(), cell.style().copy()));
			}
			matrix.add(splitCells);
		}
		// print matrix
		EscPosWrapper escPosWrapper = new EscPosWrapper(escpos);
		for (int j = 0; j < maxNumberOfCuts; j++) {
			for (int i = 0; i < matrix.size(); ++i) {
				boolean isLastRow = i + 1 == row.size(); // matrix.size() == row.size()
				List splits = matrix.get(i);
				boolean existsElement = j < splits.size();
				TableCell cell = existsElement ? splits.get(j) : splits.get(0);
				int width = !isLastRow ? cell.width() - 1 : cell.width(); // ancho reducido para los espacios intermedios
				String text = existsElement ? cell.text() : "";
				StyleText styleText = StyleText.builder(cell.style())
					.feed(isLastRow)
					.build();
				escPosWrapper.printText(text, width, styleText);
				if (!isLastRow) {
					String separator = text.isEmpty() ? " " : String.valueOf(separatorChar);
					escPosWrapper.printText(separator, 1, helper.noFeedBuilder().build()); // imprimir el separator
				}
			}
		}
	}

	private void printLine() {
		EscPosWrapper wrapper = new EscPosWrapper(escpos);
		try {
			wrapper.printLine('-', helper.properties().width());
		} catch (Exception ignored) {
		}
	}

	private void printBoldLine(char pad) {
		if(pad == ' ')
			return;
		EscPosWrapper wrapper = new EscPosWrapper(escpos);
		try {
			wrapper.printLine(pad, helper.properties().width(), StyleText.builder().bold(true).build());
		} catch (Exception ignored) {
		}
	}
}






© 2015 - 2025 Weber Informatics LLC | Privacy Policy