Skip to content

Table Rendering Issue with CJK Characters #1223

@jiangood

Description

@jiangood

🐛 Bug Report: Incorrect Table Border Alignment

📄 Description

When using the Table component to display data that contains CJK (Chinese, Japanese, Korean) characters or other full-width symbols , the table borders become misaligned and broken.

🖥️ Environment

  • spring-shell version: 3.4.x
  • spring-boot version: 3.5.8
  • java version: 17
  • os: windows 10, Ubuntu 22

🔬 Steps to Reproduce

project that replicates the problem: https://github.com/jiangood/spring-shell-cjk-issue-demo

clone and run mvnw package && java -jar target/app.jar hi

  • windows cmd
Image
  • windows powershell
Image
  • linux (by XShell)
Image

project main code :

@Command
public class TestCommand {

    @Command
    public Table hi() {
        String[][] data = {
                {"name", "addr"},
                {"Alexander", "America"},
                {"张三",  "中国北京"},
        };

        TableModel model = new ArrayTableModel(data);
        TableBuilder builder = new TableBuilder(model)
                .addFullBorder(BorderStyle.fancy_light);
        Table table = builder.build();

        return table;
    }
}

💡 Possible Solution

Use Jline utils to calculate the width

AttributedString.fromAnsi(text).columnLength()

I found two place to fix it (spring-shell version: 3.4.x)

  1. AutoSizeConstraints

    public class AutoSizeConstraints implements SizeConstraints {
    
        @Override
        public Extent width(String[] raw, int tableWidth, int nbColumns) {
            int max = 0;
            int min = 0;
            for (String line : raw) {
                String[] words = line.split(" ");
                for (String word : words) {
                    // -- min = Math.max(min, word.length());
                    // ++
                    min = Math.max(min, AttributedString.fromAnsi(word).columnLength());
                }
                // -- max = Math.max(max, line.length());
                // ++
                max = Math.max(max, AttributedString.fromAnsi(line).columnLength());
            }
            return new Extent(min, max);
        }
    }
  2. SimpleHorizontalAligner

    public enum SimpleHorizontalAligner implements Aligner {
    
        left, center, right;
    
        @Override
        public String[] align(String[] text, int cellWidth, int cellHeight) {
            String[] result = new String[cellHeight];
            for (int i = 0; i < cellHeight; i++) {
                String line = (i < text.length && text[i] != null) ? text[i].trim() : "";
    
                // -- int paddingToDistribute = cellWidth - line.length();
                // ++
                int paddingToDistribute = cellWidth - AttributedString.fromAnsi(line).columnLength(); 
    
                int padLeft;
                int padRight;
    
                switch (this) {
                    case center: {
                        int carry = paddingToDistribute % 2;
                        paddingToDistribute = paddingToDistribute - carry;
                        padLeft = padRight = paddingToDistribute / 2;
                        padRight += carry;
                        break;
                    }
                    case right: {
                        padLeft = paddingToDistribute;
                        padRight = 0;
                        break;
                    }
                    case left: {
                        padLeft = 0;
                        padRight = paddingToDistribute;
                        break;
                    }
                    default:
                        throw new AssertionError();
                }
                StringBuilder sb = new StringBuilder(cellWidth);
                for (int j = 0; j < padLeft; j++) {
                    sb.append(' ');
                }
                sb.append(line);
                for (int j = 0; j < padRight; j++) {
                    sb.append(' ');
                }
    
                result[i] = sb.toString();
            }
            return result;
        }
    
    } 

Metadata

Metadata

Assignees

No one assigned

    Labels

    status/need-triageTeam needs to triage and take a first look

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions