1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-12-12 19:20:29 +00:00

Rewrite the command system's text padder

This uses narrow near-invisible unicode characters instead of attempting
to toggle between bold and non-bold spaces.
This commit is contained in:
SquidDev 2018-03-25 21:31:31 +01:00
parent d758895578
commit d28694eb57
2 changed files with 168 additions and 142 deletions

View File

@ -0,0 +1,150 @@
package dan200.computercraft.shared.command.framework;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.common.util.FakePlayer;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import static dan200.computercraft.shared.command.framework.ChatHelpers.coloured;
/**
* Adapated from Sponge's PaginationCalculator
*/
public class TextFormatter
{
private static final int SPACE_WIDTH = 4;
private static final char PADDING_CHAR = 'ˌ';
/**
* Yoinked from FontRenderer
*
* @see net.minecraft.client.gui.FontRenderer#charWidth
* @see net.minecraft.client.gui.FontRenderer#getCharWidth(char)
*/
private static final String CHARACTERS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
private static final int[] CHAR_WIDTHS = new int[]{
6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 4,
4, 6, 7, 6, 6, 6, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1,
1, 2, 5, 6, 6, 6, 6, 3, 5, 5, 5, 6, 2, 6, 2, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 5, 6, 5, 6,
7, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 4, 6, 6,
3, 6, 6, 6, 6, 6, 5, 6, 6, 2, 6, 5, 3, 6, 6, 6,
6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 5, 2, 5, 7, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 3, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6,
6, 3, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 2, 6, 6,
8, 9, 9, 6, 6, 6, 8, 8, 6, 8, 8, 8, 8, 8, 6, 6,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 9, 9, 9, 5, 9, 9,
8, 7, 7, 8, 7, 8, 8, 8, 7, 8, 8, 7, 9, 9, 6, 7,
7, 7, 7, 7, 9, 6, 7, 8, 7, 6, 6, 9, 7, 6, 7, 1
};
private static final int[] EXTRA_CHARS = new int[]{
'\u20e2', '\u261b',
};
private static final byte[] EXTRA_WIDTHS = new byte[]{
8, 4,
};
private static int getWidth( int codePoint )
{
// Escape codes
if( codePoint == 167 ) return -1;
// Space and non-breaking space
if( codePoint == 32 || codePoint == 160 ) return 4;
// Built-in characters
int nonUnicodeIdx = CHARACTERS.indexOf( codePoint );
if( codePoint > 0 && nonUnicodeIdx != -1 ) return CHAR_WIDTHS[nonUnicodeIdx];
// Other special characters we use.
int extraIdx = Arrays.binarySearch( EXTRA_CHARS, codePoint );
if( extraIdx >= 0 ) return EXTRA_WIDTHS[extraIdx];
return 0;
}
private static int getWidth( ITextComponent component )
{
int total = 0;
if( component instanceof TextComponentString )
{
String contents = component.getUnformattedComponentText();
int bold = component.getStyle().getBold() ? 1 : 0;
for( int i = 0; i < contents.length(); i++ )
{
int cp = contents.charAt( i );
assert cp != '\n';
int width = getWidth( cp );
if( width < 0 )
{
i++;
}
else
{
total += width + bold;
}
}
}
for( ITextComponent child : component.getSiblings() )
{
total += getWidth( child );
}
return total;
}
public static boolean isPlayer( ICommandSender sender )
{
return sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer);
}
public static int getMaxWidth( ICommandSender sender )
{
return isPlayer( sender ) ? 320 : 80;
}
public static int getWidthFor( ITextComponent component, ICommandSender sender )
{
return isPlayer( sender ) ? getWidth( component ) : component.getUnformattedText().length();
}
public static int getWidthFor( int codepoint, ICommandSender sender )
{
return isPlayer( sender ) ? getWidth( codepoint ) : 1;
}
public static void appendFixedWidth( ITextComponent out, ICommandSender sender, ITextComponent entry, int maxWidth )
{
out.appendSibling( entry );
int width = getWidthFor( entry, sender );
int delta = maxWidth - width;
if( delta > 0 )
{
int spaces = delta / SPACE_WIDTH;
int extra = delta % SPACE_WIDTH;
// Append a fixed number of spaces
if( spaces > 0 ) out.appendSibling( new TextComponentString( StringUtils.repeat( ' ', spaces ) ) );
// Append several minor characters to pad to a full string
if( extra > 0 )
{
out.appendSibling( coloured( StringUtils.repeat( PADDING_CHAR, extra ), TextFormatting.GRAY ) );
}
}
}
}

View File

@ -2,11 +2,9 @@ package dan200.computercraft.shared.command.framework;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import net.minecraft.command.ICommandSender; import net.minecraft.command.ICommandSender;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString; import net.minecraft.util.text.TextComponentString;
import net.minecraft.util.text.TextFormatting; import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.common.util.FakePlayer;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -14,83 +12,13 @@ import java.util.List;
import static dan200.computercraft.shared.command.framework.ChatHelpers.coloured; import static dan200.computercraft.shared.command.framework.ChatHelpers.coloured;
import static dan200.computercraft.shared.command.framework.ChatHelpers.text; import static dan200.computercraft.shared.command.framework.ChatHelpers.text;
import static dan200.computercraft.shared.command.framework.TextFormatter.*;
public class TextTable public class TextTable
{ {
private static final String CHARACTERS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
private static final int[] CHAR_WIDTHS = new int[]{
6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 4,
4, 6, 7, 6, 6, 6, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1,
1, 2, 5, 6, 6, 6, 6, 3, 5, 5, 5, 6, 2, 6, 2, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 5, 6, 5, 6,
7, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 4, 6, 6,
3, 6, 6, 6, 6, 6, 5, 6, 6, 2, 6, 5, 3, 6, 6, 6,
6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 5, 2, 5, 7, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 3, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6,
6, 3, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 2, 6, 6,
8, 9, 9, 6, 6, 6, 8, 8, 6, 8, 8, 8, 8, 8, 6, 6,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 9, 9, 9, 5, 9, 9,
8, 7, 7, 8, 7, 8, 8, 8, 7, 8, 8, 7, 9, 9, 6, 7,
7, 7, 7, 7, 9, 6, 7, 8, 7, 6, 6, 9, 7, 6, 7, 1
};
private static final ITextComponent SEPARATOR = coloured( "| ", TextFormatting.GRAY ); private static final ITextComponent SEPARATOR = coloured( "| ", TextFormatting.GRAY );
private static final ITextComponent LINE = text( "\n" ); private static final ITextComponent LINE = text( "\n" );
private static int getWidth( char character, ICommandSender sender )
{
if( sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer) )
{
// Use font widths here.
if( character == 167 )
{
return -1;
}
else if( character == 32 )
{
return 4;
}
else if( CHARACTERS.indexOf( character ) != -1 )
{
return CHAR_WIDTHS[character];
}
else
{
// Eh, close enough.
return 6;
}
}
else
{
return 1;
}
}
private static int getWidth( ITextComponent text, ICommandSender sender )
{
int sum = 0;
String chars = text.getUnformattedText();
for( int i = 0; i < chars.length(); i++ )
{
sum += getWidth( chars.charAt( i ), sender );
}
return sum;
}
private static boolean isPlayer( ICommandSender sender )
{
return sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer);
}
private static int getMaxWidth( ICommandSender sender )
{
return isPlayer( sender ) ? 320 : 80;
}
private int columns = -1; private int columns = -1;
private final ITextComponent[] header; private final ITextComponent[] header;
private final List<ITextComponent[]> rows = Lists.newArrayList(); private final List<ITextComponent[]> rows = Lists.newArrayList();
@ -134,17 +62,13 @@ public class TextTable
{ {
if( columns <= 0 ) return; if( columns <= 0 ) return;
final int maxWidth = getMaxWidth( sender );
int[] minWidths = new int[columns];
int[] maxWidths = new int[columns]; int[] maxWidths = new int[columns];
int[] rowWidths = new int[columns];
if( header != null ) if( header != null )
{ {
for( int i = 0; i < columns; i++ ) for( int i = 0; i < columns; i++ )
{ {
maxWidths[i] = minWidths[i] = getWidth( header[i], sender ); maxWidths[i] = getWidthFor( header[i], sender );
} }
} }
@ -157,22 +81,16 @@ public class TextTable
ITextComponent[] row = rows.get( y ); ITextComponent[] row = rows.get( y );
for( int i = 0; i < row.length; i++ ) for( int i = 0; i < row.length; i++ )
{ {
int width = getWidth( row[i], sender ); int width = getWidthFor( row[i], sender ) + 3;
rowWidths[i] += width; if( width > maxWidths[i] ) maxWidths[i] = width;
if( width > maxWidths[i] )
{
maxWidths[i] = width;
}
} }
} }
// Calculate the average width // Add a small amount of extra padding. We include this here instead of the separator to allow
for( int i = 0; i < columns; i++ ) // for "extra" characters
{ for( int i = 0; i < maxWidths.length; i++ ) maxWidths[i] += 4;
rowWidths[i] = Math.max( rowWidths[i], rows.size() );
}
int totalWidth = (columns - 1) * getWidth( SEPARATOR, sender ); int totalWidth = (columns - 1) * getWidthFor( SEPARATOR, sender );
for( int x : maxWidths ) totalWidth += x; for( int x : maxWidths ) totalWidth += x;
// TODO: Limit the widths of some entries if totalWidth > maxWidth // TODO: Limit the widths of some entries if totalWidth > maxWidth
@ -181,15 +99,16 @@ public class TextTable
if( header != null ) if( header != null )
{ {
for( int i = 0; i < columns; i++ ) for( int i = 0; i < columns - 1; i++ )
{ {
if( i != 0 ) out.appendSibling( SEPARATOR ); appendFixedWidth( out, sender, header[i], maxWidths[i] );
appendFixed( out, sender, header[i], maxWidths[i] ); out.appendSibling( SEPARATOR );
} }
out.appendSibling( header[columns - 1] );
out.appendSibling( LINE ); out.appendSibling( LINE );
// Round the width up rather than down // Round the width up rather than down
int rowCharWidth = getWidth( '=', sender ); int rowCharWidth = getWidthFor( '=', sender );
int rowWidth = totalWidth / rowCharWidth + (totalWidth % rowCharWidth == 0 ? 0 : 1); int rowWidth = totalWidth / rowCharWidth + (totalWidth % rowCharWidth == 0 ? 0 : 1);
out.appendSibling( coloured( StringUtils.repeat( '=', rowWidth ), TextFormatting.GRAY ) ); out.appendSibling( coloured( StringUtils.repeat( '=', rowWidth ), TextFormatting.GRAY ) );
out.appendSibling( LINE ); out.appendSibling( LINE );
@ -199,11 +118,12 @@ public class TextTable
{ {
ITextComponent[] row = rows.get( i ); ITextComponent[] row = rows.get( i );
if( i != 0 ) out.appendSibling( LINE ); if( i != 0 ) out.appendSibling( LINE );
for( int j = 0; j < columns; j++ ) for( int j = 0; j < columns - 1; j++ )
{ {
if( j != 0 ) out.appendSibling( SEPARATOR ); appendFixedWidth( out, sender, row[j], maxWidths[j] );
appendFixed( out, sender, row[j], maxWidths[j] ); out.appendSibling( SEPARATOR );
} }
out.appendSibling( row[columns - 1] );
} }
if( limit != rows.size() ) if( limit != rows.size() )
@ -214,48 +134,4 @@ public class TextTable
sender.sendMessage( out ); sender.sendMessage( out );
} }
private static void appendFixed( ITextComponent out, ICommandSender sender, ITextComponent entry, int maxWidth )
{
int length = getWidth( entry, sender );
int delta = length - maxWidth;
if( delta < 0 )
{
// Convert to overflow;
delta = -delta;
// We have to remove some padding as there is a padding added between formatted and unformatted text
if( !entry.getStyle().isEmpty() && isPlayer( sender ) ) delta -= 1;
out.appendSibling( entry );
int spaceWidth = getWidth( ' ', sender );
int spaces = delta / spaceWidth;
int missing = delta % spaceWidth;
spaces -= missing;
ITextComponent component = new TextComponentString( StringUtils.repeat( ' ', spaces < 0 ? 0 : spaces ) );
if( missing > 0 )
{
ITextComponent bold = new TextComponentString( StringUtils.repeat( ' ', missing ) );
bold.getStyle().setBold( true );
component.appendSibling( bold );
}
out.appendSibling( component );
}
else if( delta > 0 )
{
out.appendSibling( entry );
}
else
{
out.appendSibling( entry );
// We have to add some padding as we expect a padding between formatted and unformatted text
// and there won't be.
if( entry.getStyle().isEmpty() && isPlayer( sender ) ) out.appendText( " " );
}
}
} }