Coverage Summary for Class: AsciiTable (it.polimi.ingsw.gc14.View.TUI)
| Class |
Class, %
|
Method, %
|
Branch, %
|
Line, %
|
| AsciiTable |
100%
(1/1)
|
90.9%
(10/11)
|
52.6%
(40/76)
|
82.2%
(60/73)
|
package it.polimi.ingsw.gc14.View.TUI;
import java.util.*;
/**
* General-purpose helper for building fixed-width ASCII/Unicode tables in a TUI.
*
* <p>A table is built by adding rows one at a time, then calling {@link #build()}
* to obtain the fully rendered string. Column width is computed automatically
* from the widest cell across all rows.
*
* <p>Cell widths are measured in terminal columns, not Java {@code char} units:
* wide characters (emoji, CJK) count as 2 columns each.
*
* <p>Example usage:
* <pre>{@code
* AsciiTable table = new AsciiTable(BorderStyle.UNICODE, 3);
* table.addHeader("Name", "🍖", "⭐");
* table.addRow("Alice", "4", "12");
* table.addRow("Bob", "2", "8");
* System.out.println(table.build());
* }</pre>
*/
public class AsciiTable {
//TODO
private final BorderStyle s;
//TODO
private final int cols;
//TODO
private final List<List<String>> rows = new ArrayList<>();
//TODO
private final List<Integer> separators = new ArrayList<>();
//TODO
public AsciiTable(BorderStyle s, int cols) {
this.s = s; this.cols = cols;
}
//TODO
public void addRow(String... cells) { rows.add(Arrays.asList(cells)); }
//TODO
public void addRow(List<String> cells) { rows.add(cells); }
//TODO
public void addHeader(String... cells) {
rows.add(0, Arrays.asList(cells));
separators.add(0);
}
//TODO
public void addSeparator() { separators.add(rows.size() - 1); }
/**
* Renders the table to a multi-line string.
* Column width is the widest cell in display columns (wide chars = 2), plus 1.
*/
public String build() {
var sb = new StringBuilder();
int maxWidth = rows.stream()
.mapToInt(x -> x.stream().mapToInt(AsciiTable::displayWidth).max().orElse(0))
.max().orElse(0) + 1;
sb.append(hline(s.tl(), s.mt(), s.tr(), maxWidth)).append('\n');
for (int i = 0; i < rows.size(); i++) {
sb.append(s.v());
for (String cell : rows.get(i))
sb.append(rpad(" " + cell, maxWidth)).append(s.v());
sb.append('\n');
if (separators.contains(i) && i < rows.size() - 1)
sb.append(hline(s.sl(), s.sx(), s.sr(), maxWidth)).append('\n');
}
sb.append(hline(s.bl(), s.mb(), s.br(), maxWidth));
return sb.toString();
}
//TODO
private String hline(String l, String m, String r, int maxWidth) {
var sb = new StringBuilder(l);
for (int i = 0; i < cols; i++) {
sb.append(s.h().repeat(maxWidth));
if (i < cols - 1) sb.append(m);
}
return sb.append(r).toString();
}
/**
* Pads {@code s} with trailing spaces so its display width equals {@code w}.
* If the string is already at or over {@code w} columns, it is returned as-is.
*/
private static String rpad(String s, int w) {
int dw = displayWidth(s);
if (dw >= w) return s;
return s + " ".repeat(w - dw);
}
/**
* Places two pre-rendered text blocks side by side, separated by a gap.
* Left-block lines are padded to a uniform display width so the right block
* always starts at the same column. Uses {@link #displayWidth} for measurement.
*/
public static String sideBySide(List<String> left, List<String> right, int gap) {
int leftWidth = left.stream().mapToInt(AsciiTable::displayWidth).max().orElse(0);
int maxHeight = Math.max(left.size(), right.size());
String padding = " ".repeat(gap);
var sb = new StringBuilder();
for (int i = 0; i < maxHeight; i++) {
String l = i < left.size() ? left.get(i) : " ".repeat(leftWidth);
l = rpad(l, leftWidth);
String r = i < right.size() ? right.get(i) : "";
sb.append(l).append(padding).append(r).append('\n');
}
return sb.toString();
}
// ── Wide-character width ──────────────────────────────────────────────────
/**
* Returns the number of terminal columns required to display {@code s}.
* Wide characters (emoji, CJK, full-width) count as 2; all others as 1.
*/
public static int displayWidth(String s) {
s = s.replaceAll("\033\\[[^m]*m", "");
int w = 0;
for (int i = 0; i < s.length(); ) {
int cp = s.codePointAt(i);
w += isWide(cp) ? 2 : 1;
i += Character.charCount(cp);
}
return w;
}
/**
* Returns {@code true} when {@code cp} is a wide (2-column) character.
* Covers CJK blocks, Hangul, full-width forms, and emoji (including
* the specific emoji used in this project: 🍖 U+1F357, ⭐ U+2B50).
*/
private static boolean isWide(int cp) {
if (cp < 0x1100) return false;
if (cp <= 0x115F) return true; // Hangul Jamo
if (cp < 0x2E80) {
// Specific wide symbols below 0x2E80
return cp == 0x2B50 // ⭐ STAR
|| cp == 0x2B55; // ⭕ HEAVY LARGE CIRCLE
}
if (cp <= 0x303E) return true; // CJK Radicals, Kangxi, CJK Symbols
if (cp < 0x3041) return false;
if (cp <= 0xA4CF) return true; // Hiragana, Katakana, Bopomofo, CJK Unified
if (cp < 0xA960) return false;
if (cp <= 0xA97F) return true; // Hangul Jamo Extended-A
if (cp < 0xAC00) return false;
if (cp <= 0xD7AF) return true; // Hangul Syllables
if (cp < 0xF900) return false;
if (cp <= 0xFAFF) return true; // CJK Compatibility Ideographs
if (cp < 0xFE10) return false;
if (cp <= 0xFE1F) return true; // Vertical Forms
if (cp < 0xFE30) return false;
if (cp <= 0xFE6F) return true; // CJK Compatibility Forms
if (cp < 0xFF00) return false;
if (cp <= 0xFF60) return true; // Fullwidth Latin, punctuation
if (cp < 0xFFE0) return false;
if (cp <= 0xFFE6) return true; // Fullwidth Signs
if (cp < 0x1F004) return false;
if (cp <= 0x1FAFF) return true; // Emoji (mahjong, playing cards, flags,
// misc symbols, transport, 🍖 U+1F357, ...)
if (cp < 0x20000) return false;
return cp <= 0x3FFFD; // CJK Extension B through G
}
}