diff --git a/luaj-2.0.3/src/core/org/luaj/vm2/Buffer.java b/luaj-2.0.3/src/core/org/luaj/vm2/Buffer.java index 1789bc39de..5186cbca5c 100644 --- a/luaj-2.0.3/src/core/org/luaj/vm2/Buffer.java +++ b/luaj-2.0.3/src/core/org/luaj/vm2/Buffer.java @@ -21,6 +21,7 @@ ******************************************************************************/ package org.luaj.vm2; +import java.nio.charset.StandardCharsets; /** * String buffer for use in string library methods, optimized for production @@ -166,21 +167,18 @@ public final Buffer append( LuaString str ) { * @see LuaString#encodeToUtf8(char[], byte[], int) */ public final Buffer append( String str ) { - char[] chars = str.toCharArray(); /* DAN200 START */ /* + char[] chars = str.toCharArray(); final int n = LuaString.lengthAsUtf8( chars ); makeroom( 0, n ); LuaString.encodeToUtf8( chars, bytes, offset + length ); length += n; */ - makeroom( 0, chars.length ); - for( int i=0; i=0||i>=j)? b: +// (b<-32||i+1>=j)? (((b&0x3f) << 6) | (bytes[i++]&0x3f)): +// (((b&0xf) << 12) | ((bytes[i++]&0x3f)<<6) | (bytes[i++]&0x3f))); +// } +// return new String(chars); +// } /* DAN200 END */ - int i,j,n,b; - for ( i=offset,j=offset+length,n=0; i=0||i>=j)? b: - (b<-32||i+1>=j)? (((b&0x3f) << 6) | (bytes[i++]&0x3f)): - (((b&0xf) << 12) | ((bytes[i++]&0x3f)<<6) | (bytes[i++]&0x3f))); - } - return new String(chars); - } /** * Count the number of bytes required to encode the string as UTF-8. @@ -590,16 +580,15 @@ private static String decodeAsUtf8(byte[] bytes, int offset, int length) { * @see #isValidUtf8() */ /* DAN200 START */ - //public static int lengthAsUtf8(char[] chars) { - private static int lengthAsUtf8(char[] chars) { +// public static int lengthAsUtf8(char[] chars) { +// int i,b; +// char c; +// for ( i=b=chars.length; --i>=0; ) +// if ( (c=chars[i]) >=0x80 ) +// b += (c>=0x800)? 2: 1; +// return b; +// } /* DAN200 END */ - int i,b; - char c; - for ( i=b=chars.length; --i>=0; ) - if ( (c=chars[i]) >=0x80 ) - b += (c>=0x800)? 2: 1; - return b; - } /** * Encode the given Java string as UTF-8 bytes, writing the result to bytes @@ -615,24 +604,23 @@ private static int lengthAsUtf8(char[] chars) { * @see #isValidUtf8() */ /* DAN200 START */ - //public static void encodeToUtf8(char[] chars, byte[] bytes, int off) { - private static void encodeToUtf8(char[] chars, byte[] bytes, int off) { +// public static void encodeToUtf8(char[] chars, byte[] bytes, int off) { +// final int n = chars.length; +// char c; +// for ( int i=0, j=off; i>6) & 0x1f)); +// bytes[j++] = (byte) (0x80 | ( c & 0x3f)); +// } else { +// bytes[j++] = (byte) (0xE0 | ((c>>12) & 0x0f)); +// bytes[j++] = (byte) (0x80 | ((c>>6) & 0x3f)); +// bytes[j++] = (byte) (0x80 | ( c & 0x3f)); +// } +// } +// } /* DAN200 END */ - final int n = chars.length; - char c; - for ( int i=0, j=off; i>6) & 0x1f)); - bytes[j++] = (byte) (0x80 | ( c & 0x3f)); - } else { - bytes[j++] = (byte) (0xE0 | ((c>>12) & 0x0f)); - bytes[j++] = (byte) (0x80 | ((c>>6) & 0x3f)); - bytes[j++] = (byte) (0x80 | ( c & 0x3f)); - } - } - } /** Check that a byte sequence is valid UTF-8 * @return true if it is valid UTF-8, otherwise false diff --git a/src/main/java/dan200/computercraft/ComputerCraft.java b/src/main/java/dan200/computercraft/ComputerCraft.java index 3b1ce7495f..8293b4b31c 100644 --- a/src/main/java/dan200/computercraft/ComputerCraft.java +++ b/src/main/java/dan200/computercraft/ComputerCraft.java @@ -50,6 +50,7 @@ import dan200.computercraft.shared.turtle.upgrades.*; import dan200.computercraft.shared.util.*; import io.netty.buffer.Unpooled; +import net.minecraft.client.Minecraft; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; @@ -153,6 +154,8 @@ public class ComputerCraft public static int maximumFilesOpen = 128; public static int maxNotesPerTick = 8; + + public static boolean utf8_enable = false; // Blocks and Items public static class Blocks @@ -223,6 +226,8 @@ public static class Config { public static Property maximumFilesOpen; public static Property maxNotesPerTick; + public static Property utf8_enable; + } // Registries @@ -343,6 +348,9 @@ public void preInit( FMLPreInitializationEvent event ) Config.maxNotesPerTick = Config.config.get( Configuration.CATEGORY_GENERAL, "maxNotesPerTick", maxNotesPerTick ); Config.maxNotesPerTick.setComment( "Maximum amount of notes a speaker can play at once" ); + + Config.utf8_enable = Config.config.get( Configuration.CATEGORY_GENERAL, "utf8_enable", utf8_enable ); + Config.utf8_enable.setComment( "Enable the \"utf8 string handling\" API during Computer startup" ); for (Property property : Config.config.getCategory( Configuration.CATEGORY_GENERAL ).getOrderedValues()) { @@ -386,6 +394,8 @@ public static void syncConfig() { turtlesCanPush = Config.turtlesCanPush.getBoolean(); maxNotesPerTick = Math.max(1, Config.maxNotesPerTick.getInt()); + + utf8_enable = Config.utf8_enable.getBoolean(); Config.config.save(); } @@ -397,6 +407,12 @@ public void init( FMLInitializationEvent event ) turtleProxy.init(); } + @Mod.EventHandler + public void init( FMLPostInitializationEvent event ) + { + proxy.postInit(); + } + @Mod.EventHandler public void onServerStarting( FMLServerStartingEvent event ) { @@ -447,6 +463,11 @@ public static void deleteDisplayLists( int list, int range ) proxy.deleteDisplayLists( list, range ); } + public static Object getFont(final String fontName) + { + return proxy.getFont(fontName); + } + public static Object getFixedWidthFontRenderer() { return proxy.getFixedWidthFontRenderer(); @@ -954,7 +975,7 @@ public void close() throws IOException return modClass.getClassLoader().getResourceAsStream( subPath ); } - private static File getContainingJar( Class modClass ) + public static File getContainingJar( Class modClass ) { String path = modClass.getProtectionDomain().getCodeSource().getLocation().getPath(); int bangIndex = path.indexOf( "!" ); @@ -979,7 +1000,7 @@ private static File getContainingJar( Class modClass ) return file; } - private static File getDebugCodeDir( Class modClass ) + public static File getDebugCodeDir( Class modClass ) { String path = modClass.getProtectionDomain().getCodeSource().getLocation().getPath(); int bangIndex = path.indexOf("!"); diff --git a/src/main/java/dan200/computercraft/client/gui/FixedWidthFontRenderer.java b/src/main/java/dan200/computercraft/client/gui/FixedWidthFontRenderer.java index cb0c7684d7..119a692f3b 100644 --- a/src/main/java/dan200/computercraft/client/gui/FixedWidthFontRenderer.java +++ b/src/main/java/dan200/computercraft/client/gui/FixedWidthFontRenderer.java @@ -20,11 +20,11 @@ public class FixedWidthFontRenderer { - private static ResourceLocation font = new ResourceLocation( "computercraft", "textures/gui/term_font.png" ); + public static ResourceLocation background = new ResourceLocation( "computercraft", "textures/gui/term_background.png" ); - public static int FONT_HEIGHT = 9; - public static int FONT_WIDTH = 6; + public static final int FONT_HEIGHT = FontManager.LEGACY.fontHeight(); // original values + public static final int FONT_WIDTH = FontManager.LEGACY.fontWidth(); // original values private TextureManager m_textureManager; @@ -38,10 +38,10 @@ private static void greyscaleify( double[] rgb ) Arrays.fill( rgb, ( rgb[0] + rgb[1] + rgb[2] ) / 3.0f ); } - private void drawChar( BufferBuilder renderer, double x, double y, int index, int color, Palette p, boolean greyscale ) + private void drawChar( FontDefinition fd, BufferBuilder renderer, double x, double y, int index, int color, Palette p, boolean greyscale ) { - int column = index % 16; - int row = index / 16; + int column = index % fd.charsPerLine(); + int row = index / fd.charsPerLine(); double[] colour = p.getColour( 15 - color ); if(greyscale) @@ -52,15 +52,15 @@ private void drawChar( BufferBuilder renderer, double x, double y, int index, in float g = (float)colour[1]; float b = (float)colour[2]; - int xStart = 1 + column * (FONT_WIDTH + 2); - int yStart = 1 + row * (FONT_HEIGHT + 2); - - renderer.pos( x, y, 0.0 ).tex( xStart / 256.0, yStart / 256.0 ).color( r, g, b, 1.0f ).endVertex(); - renderer.pos( x, y + FONT_HEIGHT, 0.0 ).tex( xStart / 256.0, (yStart + FONT_HEIGHT) / 256.0 ).color( r, g, b, 1.0f ).endVertex(); - renderer.pos( x + FONT_WIDTH, y, 0.0 ).tex( (xStart + FONT_WIDTH) / 256.0, yStart / 256.0 ).color( r, g, b, 1.0f ).endVertex(); - renderer.pos( x + FONT_WIDTH, y, 0.0 ).tex( (xStart + FONT_WIDTH) / 256.0, yStart / 256.0 ).color( r, g, b, 1.0f ).endVertex(); - renderer.pos( x, y + FONT_HEIGHT, 0.0 ).tex( xStart / 256.0, (yStart + FONT_HEIGHT) / 256.0 ).color( r, g, b, 1.0f ).endVertex(); - renderer.pos( x + FONT_WIDTH, y + FONT_HEIGHT, 0.0 ).tex( (xStart + FONT_WIDTH) / 256.0, (yStart + FONT_HEIGHT) / 256.0 ).color( r, g, b, 1.0f ).endVertex(); + int xStart = 1 + column * (fd.fontWidth() + 2); + int yStart = 1 + row * (fd.fontHeight() + 2); + + renderer.pos( x, y, 0.0 ).tex( xStart / fd.texWidth(), yStart / fd.texHeight() ).color( r, g, b, 1.0f ).endVertex(); + renderer.pos( x, y + FONT_HEIGHT, 0.0 ).tex( xStart / fd.texWidth(), (yStart + fd.fontHeight()) / fd.texHeight() ).color( r, g, b, 1.0f ).endVertex(); + renderer.pos( x + FONT_WIDTH, y, 0.0 ).tex( (xStart + fd.fontWidth()) / fd.texWidth(), yStart / fd.texHeight() ).color( r, g, b, 1.0f ).endVertex(); + renderer.pos( x + FONT_WIDTH, y, 0.0 ).tex( (xStart + fd.fontWidth()) / fd.texWidth(), yStart / fd.texHeight() ).color( r, g, b, 1.0f ).endVertex(); + renderer.pos( x, y + FONT_HEIGHT, 0.0 ).tex( xStart / fd.texWidth(), (yStart + fd.fontHeight()) / fd.texHeight() ).color( r, g, b, 1.0f ).endVertex(); + renderer.pos( x + FONT_WIDTH, y + FONT_HEIGHT, 0.0 ).tex( (xStart + fd.fontWidth()) / fd.texWidth(), (yStart + fd.fontHeight()) / fd.texHeight() ).color( r, g, b, 1.0f ).endVertex(); } private void drawQuad( BufferBuilder renderer, double x, double y, int color, double width, Palette p, boolean greyscale ) @@ -125,7 +125,7 @@ public void drawStringBackgroundPart( int x, int y, TextBuffer backgroundColour, GlStateManager.enableTexture2D(); } - public void drawStringTextPart( int x, int y, TextBuffer s, TextBuffer textColour, boolean greyScale, Palette p ) + public void drawStringTextPart( FontDefinition fd, int x, int y, TextBuffer s, TextBuffer textColour, boolean greyScale, Palette p ) { // Draw the quads Tessellator tessellator = Tessellator.getInstance(); @@ -142,16 +142,16 @@ public void drawStringTextPart( int x, int y, TextBuffer s, TextBuffer textColou // Draw char int index = (int)s.charAt( i ); - if( index < 0 || index > 255 ) + if( index < 0 || index > fd.maxChars() ) { index = (int)'?'; } - drawChar( renderer, x + i * FONT_WIDTH, y, index, colour, p, greyScale ); + drawChar( fd, renderer, x + i * FONT_WIDTH, y, index, colour, p, greyScale ); } tessellator.draw(); } - public void drawString( TextBuffer s, int x, int y, TextBuffer textColour, TextBuffer backgroundColour, double leftMarginSize, double rightMarginSize, boolean greyScale, Palette p ) + public void drawString( FontDefinition fd, TextBuffer s, int x, int y, TextBuffer textColour, TextBuffer backgroundColour, double leftMarginSize, double rightMarginSize, boolean greyScale, Palette p ) { // Draw background if( backgroundColour != null ) @@ -167,10 +167,10 @@ public void drawString( TextBuffer s, int x, int y, TextBuffer textColour, TextB if( s != null && textColour != null ) { // Bind the font texture - bindFont(); + bindFont(fd); // Draw the quads - drawStringTextPart( x, y, s, textColour, greyScale, p ); + drawStringTextPart( fd, x, y, s, textColour, greyScale, p ); } } @@ -183,9 +183,9 @@ public int getStringWidth(String s) return s.length() * FONT_WIDTH; } - public void bindFont() + public void bindFont(FontDefinition fd) { - m_textureManager.bindTexture( font ); + m_textureManager.bindTexture( fd.font() ); GlStateManager.glTexParameteri( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP ); } } diff --git a/src/main/java/dan200/computercraft/client/gui/FontDefinition.java b/src/main/java/dan200/computercraft/client/gui/FontDefinition.java new file mode 100644 index 0000000000..9debd4ca16 --- /dev/null +++ b/src/main/java/dan200/computercraft/client/gui/FontDefinition.java @@ -0,0 +1,67 @@ +package dan200.computercraft.client.gui; + +import net.minecraft.util.ResourceLocation; + +public class FontDefinition { + + private final ResourceLocation font; + private final int fontHeight; + private final int fontWidth; + private final double texHeight; + private final double texWidth; + private final int maxChars; + private final int charsPerLine; + private final String name; + + public FontDefinition(String name, ResourceLocation font, int fontHeight, int fontWidth, int maxChars, int charsPerLine, double texWidth, double texHeight) { + this.name = name; + this.font = font; + this.fontHeight = fontHeight; + this.fontWidth = fontWidth; + this.maxChars = maxChars; + this.charsPerLine = charsPerLine; + this.texWidth = texWidth; + this.texHeight = texHeight; + } + + public String name() + { + return this.name; + } + + public ResourceLocation font() + { + return this.font; + } + + public int fontHeight() + { + return this.fontHeight; + } + + public int fontWidth() + { + return this.fontWidth; + } + + public int maxChars() + { + return this.maxChars; + } + + public int charsPerLine() + { + return this.charsPerLine; + } + + public double texWidth() + { + return texWidth; + } + + public double texHeight() + { + return texHeight; + } + +} diff --git a/src/main/java/dan200/computercraft/client/gui/FontManager.java b/src/main/java/dan200/computercraft/client/gui/FontManager.java new file mode 100644 index 0000000000..c30ee124c8 --- /dev/null +++ b/src/main/java/dan200/computercraft/client/gui/FontManager.java @@ -0,0 +1,143 @@ +package dan200.computercraft.client.gui; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.core.filesystem.FileMount; +import dan200.computercraft.core.filesystem.JarMount; +import net.minecraft.client.Minecraft; +import net.minecraft.util.ResourceLocation; + +public class FontManager { + + private Map fonts = new HashMap<>(); + + public static final FontDefinition LEGACY = new FontDefinition( + "LEGACY", new ResourceLocation( "computercraft", "textures/gui/term_font.png" ), + 9, 6, 256, 16, 256.0, 256.0); + + public FontManager() + { + this.loadFonts(); + } + + public List getFonts() + { + return new ArrayList<>(fonts.values()); + } + + public FontDefinition get(String name) + { + return fonts.get(name); + } + + public FontDefinition getLegacy() + { + return LEGACY; + } + + private void loadFonts(IMount mount, String name) + { + final List list = new ArrayList<>(); + try + { + mount.list("/", list); + list.stream().filter(f -> f.endsWith(".properties")).forEach(f -> { + final String fname = f.substring(0, f.length()-".properties".length()); + final String png = fname + ".png"; + final Properties props = new Properties(); + try (final InputStream is = mount.openForRead(f)) + { + props.load(is); + fonts.put(fname, new FontDefinition( + fname, new ResourceLocation( "computercraft", "textures/gui/fonts/" + png ), + Integer.parseInt(props.getProperty("fontHeight")), + Integer.parseInt(props.getProperty("fontWidth")), + Integer.parseInt(props.getProperty("maxChars")), + Integer.parseInt(props.getProperty("charsPerLine")), + Integer.parseInt(props.getProperty("texWidth")), + Integer.parseInt(props.getProperty("texHeight")) + )); + } + catch (IOException | NullPointerException | NumberFormatException ex) + { + ComputerCraft.log.error("Error loading font " + fname + " from " + name, ex); + } + }); + } + catch (IOException ex) + { + ComputerCraft.log.error("Error loading fonts from " + name, ex); + } + } + + private void loadFonts() + { + File codeDir = ComputerCraft.getDebugCodeDir( getClass() ); + if( codeDir != null ) + { + File subResource = new File( codeDir, "assets/computercraft/textures/gui/fonts" ); + if( subResource.exists() ) + { + IMount resourcePackMount = new FileMount( subResource, 0 ); + loadFonts(resourcePackMount, "dir:"+codeDir); + } + } + + final File jar = ComputerCraft.getContainingJar(getClass()); + if (jar != null) + { + try + { + final JarMount jarMount = new JarMount( jar, "assets/computercraft/textures/gui/fonts" ); + loadFonts(jarMount, "jar:"+jar); + } + catch (IOException ex) + { + ComputerCraft.log.error("Error loading fonts from jar:"+jar, ex); + } + } + + final File resourcePackDir = new File(Minecraft.getMinecraft().mcDataDir, "resourcepacks"); + if( resourcePackDir.exists() && resourcePackDir.isDirectory() ) + { + String[] resourcePacks = resourcePackDir.list(); + for( String resourcePack1 : resourcePacks ) + { + try + { + File resourcePack = new File( resourcePackDir, resourcePack1 ); + if( !resourcePack.isDirectory() ) + { + // Mount a resource pack from a jar + IMount resourcePackMount = new JarMount( resourcePack, "assets/computercraft/textures/gui/fonts" ); + loadFonts(resourcePackMount, "resourcePack:"+resourcePack); + } + else + { + // Mount a resource pack from a folder + File subResource = new File( resourcePack, "assets/computercraft/textures/gui/fonts" ); + if( subResource.exists() ) + { + IMount resourcePackMount = new FileMount( subResource, 0 ); + loadFonts(resourcePackMount, "resourcePack:"+resourcePack); + } + } + } + catch( IOException e ) + { + // Ignore + } + } + } + } + +} diff --git a/src/main/java/dan200/computercraft/client/gui/GuiPrintout.java b/src/main/java/dan200/computercraft/client/gui/GuiPrintout.java index 06d994150e..4d1be6d9c1 100644 --- a/src/main/java/dan200/computercraft/client/gui/GuiPrintout.java +++ b/src/main/java/dan200/computercraft/client/gui/GuiPrintout.java @@ -30,6 +30,8 @@ public class GuiPrintout extends GuiContainer private final TextBuffer[] m_text; private final TextBuffer[] m_colours; private int m_page; + + private FontDefinition font; // TODO support setting font names in print outs... public GuiPrintout( ContainerHeldItem container ) { @@ -204,7 +206,7 @@ public void drawScreen(int mouseX, int mouseY, float f) int lineIdx = ItemPrintout.LINES_PER_PAGE * m_page + line; if( lineIdx >= 0 && lineIdx < m_text.length ) { - fontRenderer.drawString( m_text[lineIdx], x, y, m_colours[lineIdx], null, 0, 0, false, Palette.DEFAULT ); + fontRenderer.drawString( this.font == null ? FontManager.LEGACY : this.font, m_text[lineIdx], x, y, m_colours[lineIdx], null, 0, 0, false, Palette.DEFAULT ); } y = y + FixedWidthFontRenderer.FONT_HEIGHT; } diff --git a/src/main/java/dan200/computercraft/client/gui/widgets/WidgetTerminal.java b/src/main/java/dan200/computercraft/client/gui/widgets/WidgetTerminal.java index 0544558a78..63ff397f6c 100644 --- a/src/main/java/dan200/computercraft/client/gui/widgets/WidgetTerminal.java +++ b/src/main/java/dan200/computercraft/client/gui/widgets/WidgetTerminal.java @@ -8,6 +8,7 @@ import dan200.computercraft.ComputerCraft; import dan200.computercraft.client.gui.FixedWidthFontRenderer; +import dan200.computercraft.client.gui.FontDefinition; import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.TextBuffer; import dan200.computercraft.shared.computer.core.IComputer; @@ -154,7 +155,7 @@ else if( newLineIndex2 >= 0 ) } ); } - if( (ch >= 32 && ch <= 126) || (ch >= 160 && ch <= 255) ) // printable chars in byte range + if( !Character.isISOControl(ch)) // printable chars in byte range { // Queue the "char" event queueEvent( "char", new Object[]{ @@ -395,13 +396,14 @@ public void draw( Minecraft mc, int xOrigin, int yOrigin, int mouseX, int mouseY // Draw margins TextBuffer emptyLine = new TextBuffer( ' ', tw ); + final FontDefinition font = (FontDefinition) ComputerCraft.getFont(m_computer.getComputer().getFontName()); if( m_topMargin > 0 ) { - fontRenderer.drawString( emptyLine, x, startY, terminal.getTextColourLine( 0 ), terminal.getBackgroundColourLine( 0 ), m_leftMargin, m_rightMargin, greyscale, palette ); + fontRenderer.drawString(font, emptyLine, x, startY, terminal.getTextColourLine( 0 ), terminal.getBackgroundColourLine( 0 ), m_leftMargin, m_rightMargin, greyscale, palette ); } if( m_bottomMargin > 0 ) { - fontRenderer.drawString( emptyLine, x, startY + 2 * m_bottomMargin + ( th - 1 ) * FixedWidthFontRenderer.FONT_HEIGHT, terminal.getTextColourLine( th - 1 ), terminal.getBackgroundColourLine( th - 1 ), m_leftMargin, m_rightMargin, greyscale, palette ); + fontRenderer.drawString(font, emptyLine, x, startY + 2 * m_bottomMargin + ( th - 1 ) * FixedWidthFontRenderer.FONT_HEIGHT, terminal.getTextColourLine( th - 1 ), terminal.getBackgroundColourLine( th - 1 ), m_leftMargin, m_rightMargin, greyscale, palette ); } // Draw lines @@ -410,7 +412,7 @@ public void draw( Minecraft mc, int xOrigin, int yOrigin, int mouseX, int mouseY TextBuffer text = terminal.getLine( line ); TextBuffer colour = terminal.getTextColourLine( line ); TextBuffer backgroundColour = terminal.getBackgroundColourLine( line ); - fontRenderer.drawString( text, x, y, colour, backgroundColour, m_leftMargin, m_rightMargin, greyscale, palette ); + fontRenderer.drawString(font, text, x, y, colour, backgroundColour, m_leftMargin, m_rightMargin, greyscale, palette ); y += FixedWidthFontRenderer.FONT_HEIGHT; } @@ -420,6 +422,7 @@ public void draw( Minecraft mc, int xOrigin, int yOrigin, int mouseX, int mouseY TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 ); fontRenderer.drawString( + font, cursor, x + FixedWidthFontRenderer.FONT_WIDTH * tx, startY + m_topMargin + FixedWidthFontRenderer.FONT_HEIGHT * ty, diff --git a/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java b/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java index db2da93303..7159b916ae 100644 --- a/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java +++ b/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java @@ -68,6 +68,8 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon private long m_tick; private long m_renderFrame; private FixedWidthFontRenderer m_fixedWidthFontRenderer; + + private FontManager fontManager; // IComputerCraftProxy implementation @@ -81,6 +83,11 @@ public void preInit() // Setup client forge handlers registerForgeHandlers(); } + + public FontManager getFontManager() + { + return this.fontManager; + } @SubscribeEvent public void registerModels( ModelRegistryEvent event ) @@ -546,4 +553,16 @@ public int getColorFromItemstack( @Nonnull ItemStack stack, int layer ) return layer == 0 ? 0xFFFFFF : disk.getColour( stack ); } } + + @Override + public Object getFont(String fontName) { + if (fontName == null) return FontManager.LEGACY; + final FontDefinition res = this.fontManager.get(fontName); + return res == null ? FontManager.LEGACY : res; + } + + @Override + public void postInit() { + this.fontManager = new FontManager(); + } } diff --git a/src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java b/src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java index 54d3cc3852..99f7385796 100644 --- a/src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java +++ b/src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java @@ -8,6 +8,7 @@ import dan200.computercraft.ComputerCraft; import dan200.computercraft.client.gui.FixedWidthFontRenderer; +import dan200.computercraft.client.gui.FontDefinition; import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.TextBuffer; import dan200.computercraft.shared.common.ClientTerminal; @@ -60,6 +61,8 @@ private void renderMonitorAt( TileMonitor monitor, double posX, double posY, dou origin.m_lastRenderFrame = renderFrame; } + final FontDefinition font = (FontDefinition) ComputerCraft.getFont(origin.getFontName()); + boolean redraw = origin.pollChanged(); BlockPos monitorPos = monitor.getPos(); BlockPos originPos = origin.getPos(); @@ -178,7 +181,7 @@ private void renderMonitorAt( TileMonitor monitor, double posX, double posY, dou GlStateManager.resetColor(); // Draw text - fontRenderer.bindFont(); + fontRenderer.bindFont(font); if( redraw ) { // Build text display list @@ -189,6 +192,7 @@ private void renderMonitorAt( TileMonitor monitor, double posX, double posY, dou for( int y = 0; y < height; ++y ) { fontRenderer.drawStringTextPart( + font, 0, FixedWidthFontRenderer.FONT_HEIGHT * y, terminal.getLine( y ), terminal.getTextColourLine( y ), @@ -206,7 +210,7 @@ private void renderMonitorAt( TileMonitor monitor, double posX, double posY, dou GlStateManager.resetColor(); // Draw cursor - fontRenderer.bindFont(); + fontRenderer.bindFont(font); if( redraw ) { // Build cursor display list @@ -219,6 +223,7 @@ private void renderMonitorAt( TileMonitor monitor, double posX, double posY, dou TextBuffer cursor = new TextBuffer( "_" ); TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 ); fontRenderer.drawString( + font, cursor, FixedWidthFontRenderer.FONT_WIDTH * cursorX, FixedWidthFontRenderer.FONT_HEIGHT * cursorY, diff --git a/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java b/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java index 541e34ba88..2bf4d60910 100644 --- a/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java +++ b/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java @@ -42,4 +42,7 @@ interface IPeripheralChangeListener String getLabel(); void setLabel( String label ); + + boolean isUtf(); + void setUtf(boolean enabled); } diff --git a/src/main/java/dan200/computercraft/core/apis/OSAPI.java b/src/main/java/dan200/computercraft/core/apis/OSAPI.java index 7ec4b6878c..a2590d2b47 100644 --- a/src/main/java/dan200/computercraft/core/apis/OSAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/OSAPI.java @@ -6,14 +6,25 @@ package dan200.computercraft.core.apis; -import dan200.computercraft.api.lua.ILuaContext; -import dan200.computercraft.api.lua.LuaException; -import dan200.computercraft.shared.util.StringUtil; +import static dan200.computercraft.core.apis.ArgumentHelper.getInt; +import static dan200.computercraft.core.apis.ArgumentHelper.getReal; +import static dan200.computercraft.core.apis.ArgumentHelper.getString; +import static dan200.computercraft.core.apis.ArgumentHelper.optBoolean; +import static dan200.computercraft.core.apis.ArgumentHelper.optString; + +import java.util.Arrays; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.TimeZone; import javax.annotation.Nonnull; -import java.util.*; -import static dan200.computercraft.core.apis.ArgumentHelper.*; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.shared.util.StringUtil; public class OSAPI implements ILuaAPI { @@ -189,7 +200,9 @@ public String[] getMethodNames() "day", "cancelTimer", "cancelAlarm", - "epoch" + "epoch", + "isUtf", + "setUtf" }; } @@ -411,6 +424,19 @@ public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull O throw new LuaException( "Unsupported operation" ); } } + case 16: + { + // isUtf + return new Object[] { Boolean.valueOf(m_apiEnvironment.isUtf()) }; + } + case 17: + { + // setUtf + boolean newValue = optBoolean(args, 0, true); + m_apiEnvironment.setUtf(newValue); + return null; + + } default: { return null; diff --git a/src/main/java/dan200/computercraft/core/apis/StringAPI.java b/src/main/java/dan200/computercraft/core/apis/StringAPI.java new file mode 100644 index 0000000000..848974116d --- /dev/null +++ b/src/main/java/dan200/computercraft/core/apis/StringAPI.java @@ -0,0 +1,1178 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ + +package dan200.computercraft.core.apis; + +import org.luaj.vm2.LuaInteger; +import org.luaj.vm2.LuaString; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; +import org.luaj.vm2.lib.OneArgFunction; +import org.luaj.vm2.lib.StringLib; +import org.luaj.vm2.lib.VarArgFunction; + +// Contributed by mepeisen +// specialized version for utf handling if needed + +public class StringAPI extends OneArgFunction { + + protected IAPIEnvironment m_env; + + public StringAPI( IAPIEnvironment _environment ) + { + this.m_env = _environment; + } + + public LuaValue call(LuaValue arg) { + LuaTable t = new LuaTable(); + t.set("dump", new Dump(t)); + t.set("len", new Len(t)); + t.set("lower", new Lower(t)); + t.set("reverse", new Reverse(t)); + t.set("upper", new Upper(t)); + t.set("byte", new Byte(t)); + t.set("char", new Char(t)); + t.set("find", new Find(t)); + t.set("format", new Format(t)); + t.set("gmatch", new Gmatch(t)); + t.set("gsub", new Gsub(t)); + t.set("match", new Match(t)); + t.set("rep", new Rep(t)); + t.set("sub", new Sub(t)); + return t; + } + + private class Dump extends OneArgFunction + { + + private LuaValue m_orig; + + public Dump(LuaTable t) { + name = "dump"; + env = t; + this.m_orig = StringLib.instance.get("dump").checkfunction(); + } + + public LuaValue call(LuaValue arg) { + // no utf handling needed, pass to orig + return m_orig.call(arg); + } + + } + + private class Len extends OneArgFunction + { + + private LuaValue m_orig; + + public Len(LuaTable t) { + name = "dump"; + env = t; + this.m_orig = StringLib.instance.get("len").checkfunction(); + } + + public LuaValue call(LuaValue arg) { + if (m_env.isUtf()) { + return LuaInteger.valueOf(arg.checkjstring().length()); + } + return m_orig.call(arg); + } + + } + + private class Lower extends OneArgFunction + { + + private LuaValue m_orig; + + public Lower(LuaTable t) { + name = "dump"; + env = t; + this.m_orig = StringLib.instance.get("lower").checkfunction(); + } + + public LuaValue call(LuaValue arg) { + // utf variant is not needed because StringLib already does Java-String-toLowerCase + return m_orig.call(arg); + } + + } + + private class Reverse extends OneArgFunction + { + + private LuaValue m_orig; + + public Reverse(LuaTable t) { + name = "dump"; + env = t; + this.m_orig = StringLib.instance.get("reverse").checkfunction(); + } + + public LuaValue call(LuaValue arg) { + if (m_env.isUtf()) { + return LuaString.valueOf(new StringBuilder(arg.checkjstring()).reverse().toString()); + } + return m_orig.call(arg); + } + + } + + private class Upper extends OneArgFunction + { + + private LuaValue m_orig; + + public Upper(LuaTable t) { + name = "dump"; + env = t; + this.m_orig = StringLib.instance.get("upper").checkfunction(); + } + + public LuaValue call(LuaValue arg) { + // utf variant is not needed because StringLib already does Java-String-toUpperCase + return m_orig.call(arg); + } + + } + + private class Byte extends VarArgFunction + { + + private LuaValue m_orig; + + public Byte(LuaTable t) { + name = "dump"; + env = t; + this.m_orig = StringLib.instance.get("byte").checkfunction(); + } + + public Varargs invoke(Varargs args) { + if (m_env.isUtf()) { + // mostly taken from original StringLib + String s = args.checkjstring(1); + int l = s.length(); + int posi = posrelat( args.optint(2,1), l ); + int pose = posrelat( args.optint(3,posi), l ); + int n,i; + if (posi <= 0) posi = 1; + if (pose > l) pose = l; + if (posi > pose) return NONE; /* empty interval; return no values */ + n = (int)(pose - posi + 1); + if (posi + n <= pose) /* overflow? */ + error("string slice too long"); + LuaValue[] v = new LuaValue[n]; + for (i=0; i=65536) argerror(a, "invalid value"); + bytes[i] = (char) c; + } + return LuaString.valueOf(new String(bytes)); + } + return m_orig.invoke(args); + } + + } + + private class Find extends VarArgFunction + { + + private LuaValue m_orig; + + public Find(LuaTable t) { + name = "dump"; + env = t; + this.m_orig = StringLib.instance.get("find").checkfunction(); + } + + public Varargs invoke(Varargs args) { + if (m_env.isUtf()) { + return str_find_aux( args, true ); + } + return m_orig.invoke(args); + } + + } + + private class Format extends VarArgFunction + { + + private LuaValue m_orig; + + public Format(LuaTable t) { + name = "dump"; + env = t; + this.m_orig = StringLib.instance.get("format").checkfunction(); + } + + public Varargs invoke(Varargs args) { + if (m_env.isUtf()) { + // mostly taken from original StringLib but works on java strings + String fmt = args.checkjstring( 1 ); + final int n = fmt.length(); + StringBuffer result = new StringBuffer(n); + int arg = 1; + char c; + + for ( int i = 0; i < n; ) { + switch ( c = fmt.charAt( i++ ) ) { + case '\n': + result.append( "\n" ); + break; + default: + result.append( c ); + break; + case L_ESC: + if ( i < n ) { + if ( ( c = fmt.charAt( i ) ) == L_ESC ) { + ++i; + result.append( L_ESC ); + } else { + arg++; + FormatDesc fdsc = new FormatDesc(args, fmt, i ); + i += fdsc.length; + switch ( fdsc.conversion ) { + case 'c': + fdsc.format( result, (char)args.checkint( arg ) ); + break; + case 'i': + case 'd': + fdsc.format( result, args.checkint( arg ) ); + break; + case 'o': + case 'u': + case 'x': + case 'X': + fdsc.format( result, args.checklong( arg ) ); + break; + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + fdsc.format( result, args.checkdouble( arg ) ); + break; + case 'q': + addquoted( result, args.checkjstring( arg ) ); + break; + case 's': { + String s = args.checkjstring( arg ); + if ( fdsc.precision == -1 && s.length() >= 100 ) { + result.append( s ); + } else { + fdsc.format( result, s ); + } + } break; + default: + error("invalid option '%"+(char)fdsc.conversion+"' to 'format'"); + break; + } + } + } + } + } + + return LuaString.valueOf(result.toString()); + } + return m_orig.invoke(args); + } + + } + + private class Match extends VarArgFunction + { + + private LuaValue m_orig; + + public Match(LuaTable t) { + name = "dump"; + env = t; + this.m_orig = StringLib.instance.get("match").checkfunction(); + } + + public Varargs invoke(Varargs args) { + if (m_env.isUtf()) { + return str_find_aux( args, false); + } + return m_orig.invoke(args); + } + + } + + private class Rep extends VarArgFunction + { + + private LuaValue m_orig; + + public Rep(LuaTable t) { + name = "dump"; + env = t; + this.m_orig = StringLib.instance.get("rep").checkfunction(); + } + + public Varargs invoke(Varargs args) { + // utf variant is not needed because StringLib duplicates underlying byte arrays which is really ok for utf + return m_orig.invoke(args); + } + + } + + private class Sub extends VarArgFunction + { + + private LuaValue m_orig; + + public Sub(LuaTable t) { + name = "dump"; + env = t; + this.m_orig = StringLib.instance.get("sub").checkfunction(); + } + + public Varargs invoke(Varargs args) { + if (m_env.isUtf()) { + final String s = args.checkjstring( 1 ); + final int l = s.length(); + + int start = posrelat( args.checkint( 2 ), l ); + int end = posrelat( args.optint( 3, -1 ), l ); + + if ( start < 1 ) + start = 1; + if ( end > l ) + end = l; + + if ( start <= end ) { + return LuaString.valueOf(s.substring( start-1 , end )); + } else { + return EMPTYSTRING; + } + } + return m_orig.invoke(args); + } + + } + + // utilities + + + /** + * This utility method implements both string.find and string.match. + * mostly taken for original string lib + */ + static Varargs str_find_aux( Varargs args, boolean find ) { + String s = args.checkjstring( 1 ); + String pat = args.checkjstring( 2 ); + int init = args.optint( 3, 1 ); + + if ( init > 0 ) { + init = Math.min( init - 1, s.length() ); + } else if ( init < 0 ) { + init = Math.max( 0, s.length() + init ); + } + + boolean fastMatch = find && ( args.arg(4).toboolean() || indexOfAny(pat, SPECIALS ) == -1 ); + + if ( fastMatch ) { + int result = s.indexOf( pat, init ); + if ( result != -1 ) { + return varargsOf( valueOf(result+1), valueOf(result+pat.length()) ); + } + } else { + MatchState ms = new MatchState( args, s, pat ); + + boolean anchor = false; + int poff = 0; + if ( pat.charAt( 0 ) == '^' ) { + anchor = true; + poff = 1; + } + + int soff = init; + do { + int res; + ms.reset(); + if ( ( res = ms.match( soff, poff ) ) != -1 ) { + if ( find ) { + return varargsOf( valueOf(soff+1), valueOf(res), ms.push_captures( false, soff, res )); + } else { + return ms.push_captures( true, soff, res ); + } + } + } while ( soff++ < s.length() && !anchor ); + } + return NIL; + } + + // taken from original StringLib + private static final String SPECIALS = "^$*+?.([%-"; + private static final int MAX_CAPTURES = 32; + private static final char L_ESC = '%'; + private static final int CAP_UNFINISHED = -1; + private static final int CAP_POSITION = -2; + private static final byte MASK_ALPHA = 0x01; + private static final byte MASK_LOWERCASE = 0x02; + private static final byte MASK_UPPERCASE = 0x04; + private static final byte MASK_DIGIT = 0x08; + private static final byte MASK_PUNCT = 0x10; + private static final byte MASK_SPACE = 0x20; + private static final byte MASK_CONTROL = 0x40; + private static final byte MASK_HEXDIGIT = (byte)0x80; + private static final byte[] CHAR_TABLE; + + // taken from original StringLib + static { + CHAR_TABLE = new byte[256]; + + for ( int i = 0; i < 256; ++i ) { + final char c = (char) i; + CHAR_TABLE[i] = (byte)( ( Character.isDigit( c ) ? MASK_DIGIT : 0 ) | + ( Character.isLowerCase( c ) ? MASK_LOWERCASE : 0 ) | + ( Character.isUpperCase( c ) ? MASK_UPPERCASE : 0 ) | + ( ( c < ' ' || c == 0x7F ) ? MASK_CONTROL : 0 ) ); + if ( ( c >= 'a' && c <= 'f' ) || ( c >= 'A' && c <= 'F' ) || ( c >= '0' && c <= '9' ) ) { + CHAR_TABLE[i] |= MASK_HEXDIGIT; + } + if ( ( c >= '!' && c <= '/' ) || ( c >= ':' && c <= '@' ) ) { + CHAR_TABLE[i] |= MASK_PUNCT; + } + if ( ( CHAR_TABLE[i] & ( MASK_LOWERCASE | MASK_UPPERCASE ) ) != 0 ) { + CHAR_TABLE[i] |= MASK_ALPHA; + } + } + + CHAR_TABLE[' '] = MASK_SPACE; + CHAR_TABLE['\r'] |= MASK_SPACE; + CHAR_TABLE['\n'] |= MASK_SPACE; + CHAR_TABLE['\t'] |= MASK_SPACE; + /* DAN200 START */ + //CHAR_TABLE[0x0C /* '\v' */ ] |= MASK_SPACE; + CHAR_TABLE[0x0B /* '\v' */ ] |= MASK_SPACE; + /* DAN200 END */ + CHAR_TABLE['\f'] |= MASK_SPACE; + }; + + // similar to LuaString + private static final int indexOfAny(String src, String pattern) + { + final char[] srcarr = src.toCharArray(); + final char[] patarr = pattern.toCharArray(); + for (int i = 0, n = srcarr.length; i < n; i++) + { + for (int j = 0, n2 = patarr.length; j < n2; j++) + { + if (srcarr[i] == patarr[j]) return i; + } + } + return -1; + } + + // mostly taken from StringLib but works on java strings for utf support + static class MatchState { + final String s; + final String p; + final Varargs args; + int level; + int[] cinit; + int[] clen; + + MatchState( Varargs args, String s, String pattern ) { + this.s = s; + this.p = pattern; + this.args = args; + this.level = 0; + this.cinit = new int[ MAX_CAPTURES ]; + this.clen = new int[ MAX_CAPTURES ]; + } + + void reset() { + level = 0; + } + + private void add_s( StringBuffer lbuf, String news, int soff, int e ) { + int l = news.length(); + for ( int i = 0; i < l; ++i ) { + char b = news.charAt( i ); + if ( b != L_ESC ) { + lbuf.append( b ); + } else { + ++i; // skip ESC + b = news.charAt( i ); + if ( !isAsciiDigit( b ) ) { + lbuf.append( b ); + } else if ( b == '0' ) { + lbuf.append( s.substring( soff, e ) ); + } else { + lbuf.append( push_onecapture( b - '1', soff, e ).strvalue().tojstring() ); + } + } + } + } + + public void add_value( StringBuffer lbuf, int soffset, int end, LuaValue repl ) { + switch ( repl.type() ) { + case LuaValue.TSTRING: + case LuaValue.TNUMBER: + add_s( lbuf, repl.strvalue().tojstring(), soffset, end ); + return; + + case LuaValue.TFUNCTION: + repl = repl.invoke( push_captures( true, soffset, end ) ).arg1(); + break; + + case LuaValue.TTABLE: + // Need to call push_onecapture here for the error checking + repl = repl.get( push_onecapture( 0, soffset, end ) ); + break; + + default: + error( "bad argument: string/function/table expected" ); + return; + } + + if ( !repl.toboolean() ) { + lbuf.append( s.substring( soffset, end ) ); + } + else if ( ! repl.isstring() ) { + error( "invalid replacement value (a "+repl.typename()+")" ); + } + else { + lbuf.append( repl.strvalue().tojstring() ); + } + } + + Varargs push_captures( boolean wholeMatch, int soff, int end ) { + int nlevels = ( this.level == 0 && wholeMatch ) ? 1 : this.level; + switch ( nlevels ) { + case 0: return NONE; + case 1: return push_onecapture( 0, soff, end ); + } + LuaValue[] v = new LuaValue[nlevels]; + for ( int i = 0; i < nlevels; ++i ) + v[i] = push_onecapture( i, soff, end ); + return varargsOf(v); + } + + private LuaValue push_onecapture( int i, int soff, int end ) { + if ( i >= this.level ) { + if ( i == 0 ) { + return LuaString.valueOf(s.substring( soff, end )); + } else { + return error( "invalid capture index" ); + } + } else { + int l = clen[i]; + if ( l == CAP_UNFINISHED ) { + return error( "unfinished capture" ); + } + if ( l == CAP_POSITION ) { + return valueOf( cinit[i] + 1 ); + } else { + int begin = cinit[i]; + return LuaString.valueOf(s.substring( begin, begin + l )); + } + } + } + + private int check_capture( int l ) { + l -= '1'; + if ( l < 0 || l >= level || this.clen[l] == CAP_UNFINISHED ) { + error("invalid capture index"); + } + return l; + } + + private int capture_to_close() { + int level = this.level; + for ( level--; level >= 0; level-- ) + if ( clen[level] == CAP_UNFINISHED ) + return level; + error("invalid pattern capture"); + return 0; + } + + int classend( int poffset ) { + switch ( p.charAt( poffset++ ) ) { + case L_ESC: + if ( poffset == p.length() ) { + error( "malformed pattern (ends with %)" ); + } + return poffset + 1; + + case '[': + if ( p.charAt( poffset ) == '^' ) poffset++; + do { + if ( poffset == p.length() ) { + error( "malformed pattern (missing ])" ); + } + if ( p.charAt( poffset++ ) == L_ESC && poffset != p.length() ) + poffset++; + } while ( p.charAt( poffset ) != ']' ); + return poffset + 1; + default: + return poffset; + } + } + + static boolean match_class( int c, char cl ) { + final char lcl = Character.toLowerCase( cl ); + + int cdata; + if (c < CHAR_TABLE.length) + { + cdata = CHAR_TABLE[c]; + } + else + { + final char cc = (char) c; + cdata = 0; + if (Character.isDigit(cc)) cdata |= MASK_DIGIT; + if (Character.isLowerCase(cc)) cdata |= MASK_LOWERCASE | MASK_ALPHA; + if (Character.isUpperCase(cc)) cdata |= MASK_UPPERCASE | MASK_ALPHA; + if (Character.isWhitespace(cc)) cdata |= MASK_SPACE; + } + + boolean res; + switch ( lcl ) { + case 'a': res = ( cdata & MASK_ALPHA ) != 0; break; + case 'd': res = ( cdata & MASK_DIGIT ) != 0; break; + case 'l': res = ( cdata & MASK_LOWERCASE ) != 0; break; + case 'u': res = ( cdata & MASK_UPPERCASE ) != 0; break; + case 'c': res = ( cdata & MASK_CONTROL ) != 0; break; + case 'p': res = ( cdata & MASK_PUNCT ) != 0; break; + case 's': res = ( cdata & MASK_SPACE ) != 0; break; + case 'w': res = ( cdata & ( MASK_ALPHA | MASK_DIGIT ) ) != 0; break; + case 'x': res = ( cdata & MASK_HEXDIGIT ) != 0; break; + case 'z': res = ( c == 0 ); break; + default: return cl == c; + } + return ( lcl == cl ) ? res : !res; + } + + boolean matchbracketclass( int c, int poff, int ec ) { + boolean sig = true; + if ( p.charAt( poff + 1 ) == '^' ) { + sig = false; + poff++; + } + while ( ++poff < ec ) { + if ( p.charAt( poff ) == L_ESC ) { + poff++; + if ( match_class( c, p.charAt( poff ) ) ) + return sig; + } + else if ( ( p.charAt( poff + 1 ) == '-' ) && ( poff + 2 < ec ) ) { + poff += 2; + if ( p.charAt( poff - 2 ) <= c && c <= p.charAt( poff ) ) + return sig; + } + else if ( p.charAt( poff ) == c ) return sig; + } + return !sig; + } + + boolean singlematch( char c, int poff, int ep ) { + switch ( p.charAt( poff ) ) { + case '.': return true; + case L_ESC: return match_class( c, p.charAt( poff + 1 ) ); + case '[': return matchbracketclass( c, poff, ep - 1 ); + default: return p.charAt( poff ) == c; + } + } + + /** + * Perform pattern matching. If there is a match, returns offset into s + * where match ends, otherwise returns -1. + */ + int match( int soffset, int poffset ) { + while ( true ) { + // Check if we are at the end of the pattern - + // equivalent to the '\0' case in the C version, but our pattern + // string is not NUL-terminated. + if ( poffset == p.length() ) + return soffset; + switch ( p.charAt( poffset ) ) { + case '(': + if ( ++poffset < p.length() && p.charAt( poffset ) == ')' ) + return start_capture( soffset, poffset + 1, CAP_POSITION ); + else + return start_capture( soffset, poffset, CAP_UNFINISHED ); + case ')': + return end_capture( soffset, poffset + 1 ); + case L_ESC: + if ( poffset + 1 == p.length() ) + error("malformed pattern (ends with '%')"); + switch ( p.charAt( poffset + 1 ) ) { + case 'b': + soffset = matchbalance( soffset, poffset + 2 ); + if ( soffset == -1 ) return -1; + poffset += 4; + continue; + case 'f': { + poffset += 2; + if ( p.charAt( poffset ) != '[' ) { + error("Missing [ after %f in pattern"); + } + int ep = classend( poffset ); + int previous = ( soffset == 0 ) ? -1 : s.charAt( soffset - 1 ); + if ( matchbracketclass( previous, poffset, ep - 1 ) || + matchbracketclass( s.charAt( soffset ), poffset, ep - 1 ) ) + return -1; + poffset = ep; + continue; + } + default: { + int c = p.charAt( poffset + 1 ); + if ( Character.isDigit( (char) c ) ) { + soffset = match_capture( soffset, c ); + if ( soffset == -1 ) + return -1; + return match( soffset, poffset + 2 ); + } + } + } + case '$': + if ( poffset + 1 == p.length() ) + return ( soffset == s.length() ) ? soffset : -1; + } + int ep = classend( poffset ); + boolean m = soffset < s.length() && singlematch( s.charAt( soffset ), poffset, ep ); + int pc = ( ep < p.length() ) ? p.charAt( ep ) : '\0'; + + switch ( pc ) { + case '?': + int res; + if ( m && ( ( res = match( soffset + 1, ep + 1 ) ) != -1 ) ) + return res; + poffset = ep + 1; + continue; + case '*': + return max_expand( soffset, poffset, ep ); + case '+': + return ( m ? max_expand( soffset + 1, poffset, ep ) : -1 ); + case '-': + return min_expand( soffset, poffset, ep ); + default: + if ( !m ) + return -1; + soffset++; + poffset = ep; + continue; + } + } + } + + int max_expand( int soff, int poff, int ep ) { + int i = 0; + while ( soff + i < s.length() && + singlematch( s.charAt( soff + i ), poff, ep ) ) + i++; + while ( i >= 0 ) { + int res = match( soff + i, ep + 1 ); + if ( res != -1 ) + return res; + i--; + } + return -1; + } + + int min_expand( int soff, int poff, int ep ) { + for ( ;; ) { + int res = match( soff, ep + 1 ); + if ( res != -1 ) + return res; + else if ( soff < s.length() && singlematch( s.charAt( soff ), poff, ep ) ) + soff++; + else return -1; + } + } + + int start_capture( int soff, int poff, int what ) { + int res; + int level = this.level; + if ( level >= MAX_CAPTURES ) { + error( "too many captures" ); + } + cinit[ level ] = soff; + clen[ level ] = what; + this.level = level + 1; + if ( ( res = match( soff, poff ) ) == -1 ) + this.level--; + return res; + } + + int end_capture( int soff, int poff ) { + int l = capture_to_close(); + int res; + clen[l] = soff - cinit[l]; + if ( ( res = match( soff, poff ) ) == -1 ) + clen[l] = CAP_UNFINISHED; + return res; + } + + int match_capture( int soff, int l ) { + l = check_capture( l ); + int len = clen[ l ]; + if ( ( s.length() - soff ) >= len && + LuaString.equals( LuaString.valueOf(s), cinit[l], LuaString.valueOf(s), soff, len ) ) + return soff + len; + else + return -1; + } + + int matchbalance( int soff, int poff ) { + final int plen = p.length(); + if ( poff == plen || poff + 1 == plen ) { + error( "unbalanced pattern" ); + } + /* DAN200 START */ + if ( soff >= s.length() ) + return -1; + /* DAN200 END */ + if ( s.charAt( soff ) != p.charAt( poff ) ) + return -1; + else { + char b = p.charAt( poff ); + char e = p.charAt( poff + 1 ); + int cont = 1; + while ( ++soff < s.length() ) { + if ( s.charAt( soff ) == e ) { + if ( --cont == 0 ) return soff + 1; + } + else if ( s.charAt( soff ) == b ) cont++; + } + } + return -1; + } + } + + // taken from original StringLib + private static int posrelat( int pos, int len ) { + return ( pos >= 0 ) ? pos : len + pos + 1; + } + + // taken from original StringLib but works on java strings + static class FormatDesc { + + private boolean leftAdjust; + private boolean zeroPad; + private boolean explicitPlus; + private boolean space; + private boolean alternateForm; + private static final int MAX_FLAGS = 5; + + private int width; + private int precision; + + public final int conversion; + public final int length; + + public FormatDesc(Varargs args, String strfrmt, final int start) { + int p = start, n = strfrmt.length(); + char c = 0; + + boolean moreFlags = true; + while ( moreFlags ) { + switch ( c = ( (p < n) ? strfrmt.charAt( p++ ) : 0 ) ) { + case '-': leftAdjust = true; break; + case '+': explicitPlus = true; break; + case ' ': space = true; break; + case '#': alternateForm = true; break; + case '0': zeroPad = true; break; + default: moreFlags = false; break; + } + } + if ( p - start > MAX_FLAGS ) + error("invalid format (repeated flags)"); + + width = -1; + if ( isAsciiDigit( c ) ) { + width = c - '0'; + c = ( (p < n) ? strfrmt.charAt( p++ ) : 0 ); + if ( isAsciiDigit( c ) ) { + width = width * 10 + (c - '0'); + c = ( (p < n) ? strfrmt.charAt( p++ ) : 0 ); + } + } + + precision = -1; + if ( c == '.' ) { + c = ( (p < n) ? strfrmt.charAt( p++ ) : 0 ); + if ( isAsciiDigit( c ) ) { + precision = c - '0'; + c = ( (p < n) ? strfrmt.charAt( p++ ) : 0 ); + if ( isAsciiDigit( c ) ) { + precision = precision * 10 + (c - '0'); + c = ( (p < n) ? strfrmt.charAt( p++ ) : 0 ); + } + } + } + + if ( isAsciiDigit( c ) ) + error("invalid format (width or precision too long)"); + + zeroPad &= !leftAdjust; // '-' overrides '0' + conversion = c; + length = p - start; + } + + public void format(StringBuffer buf, char c) { + // TODO original StringLib does not handle this... Should be upgraded as soon as we use some other lua version + buf.append(c); + } + + public void format(StringBuffer buf, long number) { + String digits; + + if ( number == 0 && precision == 0 ) { + digits = ""; + } else { + int radix; + switch ( conversion ) { + case 'x': + case 'X': + radix = 16; + break; + case 'o': + radix = 8; + break; + default: + radix = 10; + break; + } + digits = Long.toString( number, radix ); + if ( conversion == 'X' ) + digits = digits.toUpperCase(); + } + + int minwidth = digits.length(); + int ndigits = minwidth; + int nzeros; + + if ( number < 0 ) { + ndigits--; + } else if ( explicitPlus || space ) { + minwidth++; + } + + if ( precision > ndigits ) + nzeros = precision - ndigits; + else if ( precision == -1 && zeroPad && width > minwidth ) + nzeros = width - minwidth; + else + nzeros = 0; + + minwidth += nzeros; + int nspaces = width > minwidth ? width - minwidth : 0; + + if ( !leftAdjust ) + pad( buf, ' ', nspaces ); + + if ( number < 0 ) { + if ( nzeros > 0 ) { + buf.append( '-' ); + digits = digits.substring( 1 ); + } + } else if ( explicitPlus ) { + buf.append( '+' ); + } else if ( space ) { + buf.append( ' ' ); + } + + if ( nzeros > 0 ) + pad( buf, '0', nzeros ); + + buf.append( digits ); + + if ( leftAdjust ) + pad( buf, ' ', nspaces ); + } + + public void format(StringBuffer buf, double x) { + // TODO original StringLib does not handle this... Should be upgraded as soon as we use some other lua version + buf.append( String.valueOf( x ) ); + } + + public void format(StringBuffer buf, String s) { + int nullindex = s.indexOf( '\0', 0 ); + if ( nullindex != -1 ) + s = s.substring( 0, nullindex ); + buf.append(s); + } + + public static final void pad(StringBuffer buf, char c, int n) { + while ( n-- > 0 ) + buf.append(c); + } + } + + // the original invokations of Character.isDigit will return true for eastern arabic etc. However this may break the logic on how it works the "lua way". + // maybe we could rework the methods to support eastern arabic and other digits. + protected static boolean isAsciiDigit(char c) + { + return c >= '0' && c <= '9'; + } + + // taken from original StringLib but works on java strings + static class GMatchAux extends VarArgFunction { + private final int srclen; + private final MatchState ms; + private int soffset; + public GMatchAux(Varargs args, String src, String pat) { + this.srclen = src.length(); + this.ms = new MatchState(args, src, pat); + this.soffset = 0; + } + public Varargs invoke(Varargs args) { + for ( ; soffset=0 ) { + int soff = soffset; + soffset = res; + /* DAN200 START */ + if (res == soff) soffset++; + /* DAN200 END */ + return ms.push_captures( true, soff, res ); + } + } + return NIL; + } + } + + // taken from StringLib but works on java strings + private static void addquoted(StringBuffer buf, String s) { + char c; + buf.append( '"' ); + for ( int i = 0, n = s.length(); i < n; i++ ) { + switch ( c = s.charAt( i ) ) { + case '"': case '\\': case '\n': + buf.append( '\\' ); + buf.append( c ); + break; + case '\r': + buf.append( "\\r" ); + break; + case '\0': + buf.append( "\\000" ); + break; + default: + /* DAN200 START */ + //buf.append( (byte) c ); + if( (c >= 32 && c <= 126) || c >= 160 ) { + buf.append( c ); + } else { + String str = Integer.toString((int) c); + while( str.length() < 3 ) { + str = "0" + str; + } + buf.append( "\\" + str ); + } + /* DAN200 END */ + break; + } + } + buf.append( (byte) '"' ); + } + + private class Gmatch extends VarArgFunction + { + + private LuaValue m_orig; + + public Gmatch(LuaTable t) { + name = "dump"; + env = t; + this.m_orig = StringLib.instance.get("gmatch").checkfunction(); + } + + public Varargs invoke(Varargs args) { + if (m_env.isUtf()) { + String src = args.checkjstring( 1 ); + String pat = args.checkjstring( 2 ); + return new GMatchAux(args, src, pat); + } + return m_orig.invoke(args); + } + + } + + private class Gsub extends VarArgFunction + { + + private LuaValue m_orig; + + public Gsub(LuaTable t) { + name = "dump"; + env = t; + this.m_orig = StringLib.instance.get("gsub").checkfunction(); + } + + public Varargs invoke(Varargs args) { + if (m_env.isUtf()) { + // mostly taken from original StringLib + String src = args.checkjstring( 1 ); + final int srclen = src.length(); + String p = args.checkjstring( 2 ); + LuaValue repl = args.arg( 3 ); + int max_s = args.optint( 4, srclen + 1 ); + final boolean anchor = p.length() > 0 && p.charAt( 0 ) == '^'; + + StringBuffer lbuf = new StringBuffer( srclen ); + MatchState ms = new MatchState( args, src, p ); + + int soffset = 0; + int n = 0; + while ( n < max_s ) { + ms.reset(); + int res = ms.match( soffset, anchor ? 1 : 0 ); + if ( res != -1 ) { + n++; + ms.add_value( lbuf, soffset, res, repl ); + } + if ( res != -1 && res > soffset ) + soffset = res; + else if ( soffset < srclen ) + lbuf.append( src.charAt( soffset++ ) ); + else + break; + if ( anchor ) + break; + } + lbuf.append( src.substring( soffset, srclen ) ); + return varargsOf(LuaString.valueOf(lbuf.toString()), valueOf(n)); + } + return m_orig.invoke(args); + } + + } + +} diff --git a/src/main/java/dan200/computercraft/core/apis/TermAPI.java b/src/main/java/dan200/computercraft/core/apis/TermAPI.java index 38a64b059d..cc255a0f53 100644 --- a/src/main/java/dan200/computercraft/core/apis/TermAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/TermAPI.java @@ -17,6 +17,8 @@ import static dan200.computercraft.core.apis.ArgumentHelper.*; +import java.nio.charset.StandardCharsets; + public class TermAPI implements ILuaAPI { private final Terminal m_terminal; @@ -78,7 +80,9 @@ public String[] getMethodNames() "setPaletteColour", "setPaletteColor", "getPaletteColour", - "getPaletteColor" + "getPaletteColor", + "getFontName", + "setFontName" }; } @@ -252,6 +256,17 @@ public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull O String text = getString( args, 0 ); String textColour = getString( args, 1 ); String backgroundColour = getString( args, 2 ); + if (!m_environment.isUtf()) + { + // backward compatibility for non utf terminals; we get valid utf strings but want to display them as ascii + final byte[] bytes = text.getBytes(StandardCharsets.UTF_8); + final char[] chars = new char[bytes.length]; + for (int i = 0, n = bytes.length; i < n; i++) + { + chars[i] = bytes[i] < 0 ? '?' : (char)bytes[i]; + } + text = new String(chars); + } if( textColour.length() != text.length() || backgroundColour.length() != text.length() ) { throw new LuaException( "Arguments must be the same length" ); @@ -298,6 +313,21 @@ public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull O } return null; } + case 23: + { + // getFontName + return new Object[] {m_environment.getFontName()}; + } + case 24: + { + // setFontName + final String name = getString(args, 0); + synchronized( m_terminal ) + { + m_environment.setFontName(name); + } + return null; + } default: { return null; diff --git a/src/main/java/dan200/computercraft/core/apis/handles/BinaryOutputHandle.java b/src/main/java/dan200/computercraft/core/apis/handles/BinaryOutputHandle.java index ae90b677d6..0c1619d339 100644 --- a/src/main/java/dan200/computercraft/core/apis/handles/BinaryOutputHandle.java +++ b/src/main/java/dan200/computercraft/core/apis/handles/BinaryOutputHandle.java @@ -8,6 +8,7 @@ import javax.annotation.Nonnull; import java.io.IOException; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; public class BinaryOutputHandle extends HandleGeneric { @@ -48,7 +49,7 @@ public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull O else if( args.length > 0 && args[ 0 ] instanceof String ) { String value = (String) args[ 0 ]; - m_writer.write( StringUtil.encodeString( value ) ); + m_writer.write( value.getBytes(StandardCharsets.UTF_8) ); } else { diff --git a/src/main/java/dan200/computercraft/core/computer/Computer.java b/src/main/java/dan200/computercraft/core/computer/Computer.java index 18e3751f8f..a9ca5a7f2a 100644 --- a/src/main/java/dan200/computercraft/core/computer/Computer.java +++ b/src/main/java/dan200/computercraft/core/computer/Computer.java @@ -172,12 +172,23 @@ public void onPeripheralChanged( int side, IPeripheral peripheral ) } } } + + @Override + public boolean isUtf() { + return m_computer.isUtf(); + } + + @Override + public void setUtf(boolean enabled) { + m_computer.setUtf(enabled); + } } private static IMount s_romMount = null; private int m_id; private String m_label; + private String m_fontName; private final IComputerEnvironment m_environment; private int m_ticksSinceStart; @@ -206,10 +217,14 @@ public void onPeripheralChanged( int side, IPeripheral peripheral ) private boolean m_inputChanged; private final IPeripheral[] m_peripherals; + + private boolean m_isUtf = false; public Computer( IComputerEnvironment environment, Terminal terminal, int id ) { ComputerThread.start(); + + m_isUtf = ComputerCraft.utf8_enable; m_id = id; m_label = null; @@ -246,10 +261,19 @@ public Computer( IComputerEnvironment environment, Terminal terminal, int id ) } m_rootMount = null; + m_fontName = "LEGACY"; createAPIs(); } - public IAPIEnvironment getAPIEnvironment() + public void setUtf(boolean enabled) { + this.m_isUtf = enabled; + } + + public boolean isUtf() { + return this.m_isUtf; + } + + public IAPIEnvironment getAPIEnvironment() { return m_apiEnvironment; } @@ -339,6 +363,20 @@ public void setLabel( String label ) } } + public String getFontName() + { + return m_fontName; + } + + public void setFontName( String name ) + { + if( !Objects.equal( name, m_fontName) ) + { + m_fontName = name; + m_externalOutputChanged = true; + } + } + public void advance( double _dt ) { synchronized( this ) diff --git a/src/main/java/dan200/computercraft/core/computer/IComputerEnvironment.java b/src/main/java/dan200/computercraft/core/computer/IComputerEnvironment.java index 14b9cefc8b..2168f48cf0 100644 --- a/src/main/java/dan200/computercraft/core/computer/IComputerEnvironment.java +++ b/src/main/java/dan200/computercraft/core/computer/IComputerEnvironment.java @@ -22,4 +22,10 @@ public interface IComputerEnvironment IWritableMount createSaveDirMount( String subPath, long capacity ); IMount createResourceMount( String domain, String subPath ); InputStream createResourceFile( String domain, String subPath ); + + void setUtf(boolean enabled); + boolean isUtf(); + String getFontName(); + void setFontName(String fontName); + } diff --git a/src/main/java/dan200/computercraft/core/lua/LuaJLuaMachine.java b/src/main/java/dan200/computercraft/core/lua/LuaJLuaMachine.java index abd7453f34..3ee28a847b 100644 --- a/src/main/java/dan200/computercraft/core/lua/LuaJLuaMachine.java +++ b/src/main/java/dan200/computercraft/core/lua/LuaJLuaMachine.java @@ -12,6 +12,7 @@ import dan200.computercraft.api.lua.ILuaTask; import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.core.apis.ILuaAPI; +import dan200.computercraft.core.apis.StringAPI; import dan200.computercraft.core.computer.Computer; import dan200.computercraft.core.computer.ITask; import dan200.computercraft.core.computer.MainThread; @@ -115,6 +116,8 @@ public LuaValue call() { { m_globals.set( "_CC_DISABLE_LUA51_FEATURES", toValue( true ) ); } + + m_globals.set( "_CC_UTF_SUPPORT", toValue( true ) ); // Our main function will go here m_mainRoutine = null; @@ -122,6 +125,14 @@ public LuaValue call() { m_softAbortMessage = null; m_hardAbortMessage = null; + + // wrap string api for utf support + final StringAPI string = new StringAPI(m_computer.getAPIEnvironment()); + string.setfenv(m_globals); + m_globals.set( "string", string.call() ); + // TODO Do we need a way to wrap lua string metatable? +// if ( LuaString.s_metatable == null ) +// LuaString.s_metatable = tableOf( new LuaValue[] { INDEX, t } ); } @Override diff --git a/src/main/java/dan200/computercraft/server/proxy/ComputerCraftProxyServer.java b/src/main/java/dan200/computercraft/server/proxy/ComputerCraftProxyServer.java index 6605178e11..c6c1ff84b6 100644 --- a/src/main/java/dan200/computercraft/server/proxy/ComputerCraftProxyServer.java +++ b/src/main/java/dan200/computercraft/server/proxy/ComputerCraftProxyServer.java @@ -105,4 +105,13 @@ public File getWorldDir( World world ) { return DimensionManager.getWorld( 0 ).getSaveHandler().getWorldDirectory(); } + + @Override + public Object getFont(String fontName) { + return null; + } + + @Override + public void postInit() { + } } diff --git a/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputerBase.java b/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputerBase.java index 8e3c999e09..8c986582cd 100644 --- a/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputerBase.java +++ b/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputerBase.java @@ -37,6 +37,8 @@ public abstract class TileComputerBase extends TileGeneric protected String m_label; protected boolean m_on; protected boolean m_startOn; + + private String m_fontName; protected TileComputerBase() { @@ -45,6 +47,8 @@ protected TileComputerBase() m_label = null; m_on = false; m_startOn = false; + + m_fontName = "LEGACY"; } @Override @@ -225,6 +229,7 @@ public void update() } m_computerID = computer.getID(); m_label = computer.getLabel(); + m_fontName = computer.getFontName(); m_on = computer.isOn(); } } @@ -257,6 +262,7 @@ public NBTTagCompound writeToNBT( NBTTagCompound nbttagcompound ) nbttagcompound.setString( "label", m_label ); } nbttagcompound.setBoolean( "on", m_on ); + nbttagcompound.setString( "fontName", m_fontName ); return nbttagcompound; } @@ -300,6 +306,10 @@ else if( nbttagcompound.hasKey( "userDir" ) ) // Load power state m_startOn = nbttagcompound.getBoolean( "on" ); m_on = m_startOn; + if (nbttagcompound.hasKey("fontName")) + { + m_fontName = nbttagcompound.getString( "fontName"); + } } protected boolean isPeripheralBlockedOnSide( int localSide ) @@ -541,10 +551,23 @@ protected void transferStateFrom( TileComputerBase copy ) m_instanceID = copy.m_instanceID; m_computerID = copy.m_computerID; m_label = copy.m_label; + m_fontName = copy.m_fontName; m_on = copy.m_on; m_startOn = copy.m_startOn; updateBlock(); } copy.m_instanceID = -1; } + + public String getFontName() { + return this.m_fontName; + } + + public void setFontName(String fontName) { + if( !m_fontName.equals(fontName) ) + { + m_fontName = fontName; + updateBlock(); + } + } } diff --git a/src/main/java/dan200/computercraft/shared/computer/core/ClientComputer.java b/src/main/java/dan200/computercraft/shared/computer/core/ClientComputer.java index 6f1c345c54..11a164b1ad 100644 --- a/src/main/java/dan200/computercraft/shared/computer/core/ClientComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/core/ClientComputer.java @@ -26,6 +26,7 @@ public class ClientComputer extends ClientTerminal private boolean m_blinking; private boolean m_changed; private NBTTagCompound m_userData; + private String m_fontName; private boolean m_changedLastFrame; @@ -41,6 +42,7 @@ public ClientComputer( int instanceID ) m_changed = true; m_userData = null; m_changedLastFrame = false; + m_fontName = "LEGACY"; } @Override @@ -181,6 +183,11 @@ public void readDescription( NBTTagCompound nbttagcompound ) { m_changed = true; } + + if( nbttagcompound.hasKey( "fontName" ) ) + { + m_fontName = nbttagcompound.getString( "fontName" ); + } } @Override @@ -195,4 +202,9 @@ public void handlePacket( ComputerCraftPacket packet, EntityPlayer sender ) } } } + + @Override + public String getFontName() { + return m_fontName; + } } diff --git a/src/main/java/dan200/computercraft/shared/computer/core/IComputer.java b/src/main/java/dan200/computercraft/shared/computer/core/IComputer.java index 05e4efc6c8..daf5917e35 100644 --- a/src/main/java/dan200/computercraft/shared/computer/core/IComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/core/IComputer.java @@ -13,6 +13,7 @@ public interface IComputer extends ITerminal int getInstanceID(); int getID(); String getLabel(); + String getFontName(); boolean isOn(); boolean isCursorDisplayed(); diff --git a/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java b/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java index 4d7c3ed555..0dc0d771da 100644 --- a/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java @@ -354,6 +354,11 @@ public void writeDescription( NBTTagCompound nbttagcompound ) { nbttagcompound.setTag( "userData", m_userData.copy() ); } + String fontName = m_computer.getFontName(); + if( fontName != null ) + { + nbttagcompound.setString( "fontName", fontName ); + } } // INetworkedThing @@ -401,6 +406,22 @@ public void handlePacket( ComputerCraftPacket packet, EntityPlayer sender ) if( packet.m_dataNBT != null ) { arguments = NBTUtil.decodeObjects( packet.m_dataNBT ); + if (!isUtf() && arguments != null) + { + // wrap non supported utf characters in string + for (int i = 0, n = arguments.length; i < n; i++) + { + if (arguments[i] instanceof String) + { + final char[] chars = ((String)arguments[i]).toCharArray(); + for (int j = 0, n1 = chars.length; j < n1; j++) + { + if (chars[j] >= 128) chars[j] = '?'; + } + arguments[i] = new String(chars); + } + } + } } queueEvent( event, arguments ); break; @@ -420,4 +441,24 @@ public void handlePacket( ComputerCraftPacket packet, EntityPlayer sender ) } } } + + @Override + public void setUtf(boolean enabled) { + this.m_computer.setUtf(enabled); + } + + @Override + public boolean isUtf() { + return this.m_computer.isUtf(); + } + + @Override + public String getFontName() { + return m_computer.getFontName(); + } + + @Override + public void setFontName(String fontName) { + m_computer.setFontName(fontName); + } } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorPeripheral.java index a063bef2f8..41ccec91a6 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorPeripheral.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorPeripheral.java @@ -65,7 +65,9 @@ public String[] getMethodNames() "setPaletteColour", "setPaletteColor", "getPaletteColour", - "getPaletteColor" + "getPaletteColor", + "getFontName", + "setFontName" }; } @@ -202,6 +204,20 @@ public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaCont String text = getString( args, 0 ); String textColour = getString( args, 1 ); String backgroundColour = getString( args, 2 ); + + // TODO What to do with monitors. Should they have their own utf flag? +// if (!m_environment.isUtf()) +// { +// // backward compatibility for non utf terminals; we get valid utf strings but want to display them as ascii +// final byte[] bytes = text.getBytes(StandardCharsets.UTF_8); +// final char[] chars = new char[bytes.length]; +// for (int i = 0, n = bytes.length; i < n; i++) +// { +// chars[i] = bytes[i] < 0 ? '?' : (char)bytes[i]; +// } +// text = new String(chars); +// } + if( textColour.length() != text.length() || backgroundColour.length() != text.length() ) { throw new LuaException( "Arguments must be the same length" ); @@ -249,6 +265,18 @@ public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaCont } return null; } + case 24: + { + // getFontName + return new Object[] {m_monitor.getFontName()}; + } + case 25: + { + // setFontName + final String name = getString(args, 0); + m_monitor.setFontName(name); + return null; + } } return null; } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java index 592c8ddb6c..174aa2b037 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java @@ -62,6 +62,9 @@ public class TileMonitor extends TilePeripheralBase private int m_dir; private boolean m_sizeChangedQueued; + + private String m_fontName; + private boolean m_fontChangedQueued; public TileMonitor() { @@ -78,6 +81,8 @@ public TileMonitor() m_changed = false; m_dir = 2; + + m_fontName = "LEGACY"; } @Override @@ -122,6 +127,7 @@ public NBTTagCompound writeToNBT( NBTTagCompound nbttagcompound ) nbttagcompound.setInteger( "width", m_width ); nbttagcompound.setInteger( "height", m_height ); nbttagcompound.setInteger( "dir", m_dir ); + nbttagcompound.setString( "fontName", m_fontName ); return nbttagcompound; } @@ -134,6 +140,10 @@ public void readFromNBT( NBTTagCompound nbttagcompound ) m_width = nbttagcompound.getInteger("width"); m_height = nbttagcompound.getInteger("height"); m_dir = nbttagcompound.getInteger("dir"); + if (nbttagcompound.hasKey("fontName")) + { + m_fontName = nbttagcompound.getString( "fontName"); + } } @Override @@ -153,6 +163,16 @@ public void update() } m_sizeChangedQueued = false; } + if( m_fontChangedQueued ) + { + for( IComputerAccess computer : m_computers ) + { + computer.queueEvent( "monitor_font", new Object[] { + computer.getAttachmentName() + } ); + } + m_fontChangedQueued = false; + } if( m_serverTerminal != null ) { @@ -743,6 +763,11 @@ private void queueSizeChangedEvent() m_sizeChangedQueued = true; } + private void queueFontChangedEvent() + { + m_fontChangedQueued = true; + } + private XYPair convertToXY( float xPos, float yPos, float zPos, int side ) { switch (side) @@ -867,4 +892,24 @@ public boolean shouldRefresh( World world, BlockPos pos, @Nonnull IBlockState ol } } + public String getFontName() { + return this.m_fontName; + } + + public void setFontName(String fontName) { + TileMonitor origin = getOrigin(); + if( origin != null ) + { + synchronized( origin ) + { + if( !origin.m_fontName.equals(fontName) ) + { + origin.m_fontName = fontName; + this.queueFontChangedEvent(); + origin.updateBlock(); + } + } + } + } + } diff --git a/src/main/java/dan200/computercraft/shared/proxy/IComputerCraftProxy.java b/src/main/java/dan200/computercraft/shared/proxy/IComputerCraftProxy.java index 38faf621ff..dbb27931d0 100644 --- a/src/main/java/dan200/computercraft/shared/proxy/IComputerCraftProxy.java +++ b/src/main/java/dan200/computercraft/shared/proxy/IComputerCraftProxy.java @@ -26,12 +26,14 @@ public interface IComputerCraftProxy { void preInit(); void init(); + void postInit(); boolean isClient(); boolean getGlobalCursorBlink(); long getRenderFrame(); void deleteDisplayLists( int list, int range ); Object getFixedWidthFontRenderer(); + Object getFont(String fontName); String getRecordInfo( @Nonnull ItemStack item ); void playRecord( SoundEvent record, String recordInfo, World world, BlockPos pos ); diff --git a/src/main/java/dan200/computercraft/shared/util/StringUtil.java b/src/main/java/dan200/computercraft/shared/util/StringUtil.java index 01b1d964ca..4e5c99358d 100644 --- a/src/main/java/dan200/computercraft/shared/util/StringUtil.java +++ b/src/main/java/dan200/computercraft/shared/util/StringUtil.java @@ -42,16 +42,4 @@ public static String translateToLocalFormatted( String key, Object... format ) return net.minecraft.util.text.translation.I18n.translateToLocalFormatted( key, format ); } - public static byte[] encodeString( String string ) - { - byte[] chars = new byte[ string.length() ]; - - for( int i = 0; i < chars.length; ++i ) - { - char c = string.charAt( i ); - chars[ i ] = c < 256 ? (byte) c : 63; - } - - return chars; - } } diff --git a/src/main/resources/assets/computercraft/lua/bios.lua b/src/main/resources/assets/computercraft/lua/bios.lua index 1b06ac3a27..3a086a15d7 100644 --- a/src/main/resources/assets/computercraft/lua/bios.lua +++ b/src/main/resources/assets/computercraft/lua/bios.lua @@ -317,7 +317,7 @@ function read( _sReplaceChar, _tHistory, _fnComplete, _sDefault ) sLine = "" end local nHistoryPos - local nPos = #sLine + local nPos = string.len(sLine) if _sReplaceChar then _sReplaceChar = string.sub( _sReplaceChar, 1, 1 ) end @@ -827,16 +827,16 @@ function fs.complete( sPath, sLocation, bIncludeFiles, bIncludeDirs ) local tFiles = fs.list( sDir ) for n=1,#tFiles do local sFile = tFiles[n] - if #sFile >= #sName and string.sub( sFile, 1, #sName ) == sName then + if string.len(sFile) >= string.len(sName) and string.sub( sFile, 1, string.len(sName) ) == sName then local bIsDir = fs.isDir( fs.combine( sDir, sFile ) ) - local sResult = string.sub( sFile, #sName + 1 ) + local sResult = string.sub( sFile, string.len(sName) + 1 ) if bIsDir then table.insert( tResults, sResult .. "/" ) - if bIncludeDirs and #sResult > 0 then + if bIncludeDirs and string.len(sResult) > 0 then table.insert( tResults, sResult ) end else - if bIncludeFiles and #sResult > 0 then + if bIncludeFiles and string.len(sResult) > 0 then table.insert( tResults, sResult ) end end diff --git a/src/main/resources/assets/computercraft/lua/rom/apis/gps.lua b/src/main/resources/assets/computercraft/lua/rom/apis/gps.lua index 6a83bf972d..d7ad1d3685 100644 --- a/src/main/resources/assets/computercraft/lua/rom/apis/gps.lua +++ b/src/main/resources/assets/computercraft/lua/rom/apis/gps.lua @@ -1,4 +1,6 @@ +local string_len = string.len + CHANNEL_GPS = 65534 local function trilaterate( A, B, C ) diff --git a/src/main/resources/assets/computercraft/lua/rom/apis/help.lua b/src/main/resources/assets/computercraft/lua/rom/apis/help.lua index 5ce84081ad..646924ae62 100644 --- a/src/main/resources/assets/computercraft/lua/rom/apis/help.lua +++ b/src/main/resources/assets/computercraft/lua/rom/apis/help.lua @@ -1,4 +1,4 @@ - +local string_len = string.len local sPath = "/rom/help" function path() @@ -43,7 +43,7 @@ function topics() for n,sFile in pairs( tList ) do if string.sub( sFile, 1, 1 ) ~= "." then if not fs.isDir( fs.combine( sPath, sFile ) ) then - if #sFile > 4 and sFile:sub(-4) == ".txt" then + if string_len(sFile) > 4 and sFile:sub(-4) == ".txt" then sFile = sFile:sub(1,-5) end tItems[ sFile ] = true @@ -70,8 +70,8 @@ function completeTopic( sText ) local tResults = {} for n=1,#tTopics do local sTopic = tTopics[n] - if #sTopic > #sText and string.sub( sTopic, 1, #sText ) == sText then - table.insert( tResults, string.sub( sTopic, #sText + 1 ) ) + if string_len(sTopic) > string_len(sText) and string.sub( sTopic, 1, string_len(sText) ) == sText then + table.insert( tResults, string.sub( sTopic, string_len(sText) + 1 ) ) end end return tResults diff --git a/src/main/resources/assets/computercraft/lua/rom/apis/window.lua b/src/main/resources/assets/computercraft/lua/rom/apis/window.lua index 154c447271..c450b1500b 100644 --- a/src/main/resources/assets/computercraft/lua/rom/apis/window.lua +++ b/src/main/resources/assets/computercraft/lua/rom/apis/window.lua @@ -21,6 +21,7 @@ local tHex = { local type = type local string_rep = string.rep local string_sub = string.sub +local string_len = string.len local table_unpack = table.unpack function create( parent, nX, nY, nWidth, nHeight, bStartVisible ) @@ -113,7 +114,7 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible ) local function internalBlit( sText, sTextColor, sBackgroundColor ) local nStart = nCursorX - local nEnd = nStart + #sText - 1 + local nEnd = nStart + string_len(sText) - 1 if nCursorY >= 1 and nCursorY <= nHeight then if nStart <= nWidth and nEnd >= 1 then -- Modify line @@ -187,14 +188,14 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible ) function window.write( sText ) sText = tostring( sText ) - internalBlit( sText, string_rep( tHex[ nTextColor ], #sText ), string_rep( tHex[ nBackgroundColor ], #sText ) ) + internalBlit( sText, string_rep( tHex[ nTextColor ], string_len(sText) ), string_rep( tHex[ nBackgroundColor ], string_len(sText) ) ) end function window.blit( sText, sTextColor, sBackgroundColor ) if type( sText ) ~= "string" then error( "bad argument #1 (expected string, got " .. type( sText ) .. ")", 2 ) end if type( sTextColor ) ~= "string" then error( "bad argument #2 (expected string, got " .. type( sTextColor ) .. ")", 2 ) end if type( sBackgroundColor ) ~= "string" then error( "bad argument #3 (expected string, got " .. type( sBackgroundColor ) .. ")", 2 ) end - if #sTextColor ~= #sText or #sBackgroundColor ~= #sText then + if string_len(sTextColor) ~= string_len(sText) or string_len(sBackgroundColor) ~= string_len(sText) then error( "Arguments must be the same length", 2 ) end internalBlit( sText, sTextColor, sBackgroundColor ) diff --git a/src/main/resources/assets/computercraft/lua/rom/help/os.txt b/src/main/resources/assets/computercraft/lua/rom/help/os.txt index 442d4a89be..b2f198dda6 100644 --- a/src/main/resources/assets/computercraft/lua/rom/help/os.txt +++ b/src/main/resources/assets/computercraft/lua/rom/help/os.txt @@ -19,8 +19,14 @@ os.setAlarm( time ) os.cancelAlarm( token ) os.shutdown() os.reboot() +os.isUtf() +os.setUtf(enable) Events emitted by the os API: "timer" when a timeout started by os.startTimer() completes. Argument is the token returned by os.startTimer(). "alarm" when a time passed to os.setAlarm() is reached. Argument is the token returned by os.setAlarm(). -Type "help events" to learn about the event system. \ No newline at end of file +Type "help events" to learn about the event system. + +Notice: +Although possible you should not change utf8 support while using shells/windows. They may get broken and your +computer might reboot. Instead switch utf8 support in startup scripts or via computer craft config. diff --git a/src/main/resources/assets/computercraft/lua/rom/programs/edit.lua b/src/main/resources/assets/computercraft/lua/rom/programs/edit.lua index 0e6ff22138..46d2166c1e 100644 --- a/src/main/resources/assets/computercraft/lua/rom/programs/edit.lua +++ b/src/main/resources/assets/computercraft/lua/rom/programs/edit.lua @@ -179,7 +179,7 @@ local function complete( sLine ) if nStartPos then sLine = string.sub( sLine, nStartPos ) end - if #sLine > 0 then + if string.len(sLine) > 0 then return textutils.complete( sLine, tCompleteEnv ) end end @@ -221,7 +221,7 @@ local function redrawText() local sLine = tLines[ y + scrollY ] if sLine ~= nil then writeHighlighted( sLine ) - if cursorY == y and cursorX == #sLine + 1 then + if cursorY == y and cursorX == string.len(sLine) + 1 then writeCompletion() end end @@ -235,7 +235,7 @@ local function redrawLine(_nY) term.setCursorPos( 1 - scrollX, _nY - scrollY ) term.clearLine() writeHighlighted( sLine ) - if _nY == y and x == #sLine + 1 then + if _nY == y and x == string.len(sLine) + 1 then writeCompletion() end term.setCursorPos( x - scrollX, _nY - scrollY ) diff --git a/src/main/resources/assets/computercraft/lua/rom/programs/fun/advanced/paint.lua b/src/main/resources/assets/computercraft/lua/rom/programs/fun/advanced/paint.lua index 5daa46ce33..d6ea23f1f3 100644 --- a/src/main/resources/assets/computercraft/lua/rom/programs/fun/advanced/paint.lua +++ b/src/main/resources/assets/computercraft/lua/rom/programs/fun/advanced/paint.lua @@ -5,6 +5,8 @@ -- Fields -- ------------ +local string_len = string.len + -- The width and height of the terminal local w,h = term.getSize() @@ -123,7 +125,7 @@ end ]] local function save(path) -- Open file - local sDir = string.sub(sPath, 1, #sPath - #fs.getName(sPath)) + local sDir = string.sub(sPath, 1, string_len(sPath) - string_len(fs.getName(sPath))) if not fs.exists(sDir) then fs.makeDir(sDir) end @@ -270,7 +272,7 @@ local function accessMenu() if selection==k then term.setTextColour(colours.yellow) local ox,_ = term.getCursorPos() - term.write("["..string.rep(" ",#v).."]") + term.write("["..string.rep(" ",string_len(v)).."]") term.setCursorPos(ox+1,h) term.setTextColour(colours.white) term.write(v) diff --git a/src/main/resources/assets/computercraft/lua/rom/programs/shell.lua b/src/main/resources/assets/computercraft/lua/rom/programs/shell.lua index 70d04f4634..25ead021c8 100644 --- a/src/main/resources/assets/computercraft/lua/rom/programs/shell.lua +++ b/src/main/resources/assets/computercraft/lua/rom/programs/shell.lua @@ -223,7 +223,7 @@ function shell.resolve( _sPath ) end local function pathWithExtension( _sPath, _sExt ) - local nLen = #sPath + local nLen = string.len(sPath) local sEndChar = string.sub( _sPath, nLen, nLen ) -- Remove any trailing slashes so we can add an extension to the path safely if sEndChar == "/" or sEndChar == "\\" then @@ -285,7 +285,7 @@ function shell.programs( _bIncludeHidden ) local sFile = tList[n] if not fs.isDir( fs.combine( sPath, sFile ) ) and (_bIncludeHidden or string.sub( sFile, 1, 1 ) ~= ".") then - if #sFile > 4 and sFile:sub(-4) == ".lua" then + if string.len(sFile) > 4 and sFile:sub(-4) == ".lua" then sFile = sFile:sub(1,-5) end tItems[ sFile ] = true @@ -304,7 +304,7 @@ function shell.programs( _bIncludeHidden ) end local function completeProgram( sLine ) - if #sLine > 0 and string.sub( sLine, 1, 1 ) == "/" then + if string.len(sLine) > 0 and string.sub( sLine, 1, 1 ) == "/" then -- Add programs from the root return fs.complete( sLine, "", true, false ) @@ -314,8 +314,8 @@ local function completeProgram( sLine ) -- Add aliases for sAlias, sCommand in pairs( tAliases ) do - if #sAlias > #sLine and string.sub( sAlias, 1, #sLine ) == sLine then - local sResult = string.sub( sAlias, #sLine + 1 ) + if string.len(sAlias) > string.len(sLine) and string.sub( sAlias, 1, string.len(sLine) ) == sLine then + local sResult = string.sub( sAlias, string.len(sLine) + 1 ) if not tSeen[ sResult ] then table.insert( tResults, sResult ) tSeen[ sResult ] = true @@ -327,8 +327,8 @@ local function completeProgram( sLine ) local tPrograms = shell.programs() for n=1,#tPrograms do local sProgram = tPrograms[n] - if #sProgram > #sLine and string.sub( sProgram, 1, #sLine ) == sLine then - local sResult = string.sub( sProgram, #sLine + 1 ) + if string.len(sProgram) > string.len(sLine) and string.sub( sProgram, 1, string.len(sLine) ) == sLine then + local sResult = string.sub( sProgram, string.len(sLine) + 1 ) if not tSeen[ sResult ] then table.insert( tResults, sResult ) tSeen[ sResult ] = true @@ -354,10 +354,10 @@ function shell.complete( sLine ) if type( sLine ) ~= "string" then error( "bad argument #1 (expected string, got " .. type( sLine ) .. ")", 2 ) end - if #sLine > 0 then + if string.len(sLine) > 0 then local tWords = tokenise( sLine ) local nIndex = #tWords - if string.sub( sLine, #sLine, #sLine ) == " " then + if string.sub( sLine, string.len(sLine), string.len(sLine) ) == " " then nIndex = nIndex + 1 end if nIndex == 1 then diff --git a/src/main/resources/assets/computercraft/lua/rom/startup.lua b/src/main/resources/assets/computercraft/lua/rom/startup.lua index def4fd35f7..241b740508 100644 --- a/src/main/resources/assets/computercraft/lua/rom/startup.lua +++ b/src/main/resources/assets/computercraft/lua/rom/startup.lua @@ -43,8 +43,8 @@ local function completeMultipleChoice( sText, tOptions, bAddSpaces ) local tResults = {} for n=1,#tOptions do local sOption = tOptions[n] - if #sOption + (bAddSpaces and 1 or 0) > #sText and string.sub( sOption, 1, #sText ) == sText then - local sResult = string.sub( sOption, #sText + 1 ) + if string.len(sOption) + (bAddSpaces and 1 or 0) > string.len(sText) and string.sub( sOption, 1, string.len(sText) ) == sText then + local sResult = string.sub( sOption, string.len(sText) + 1 ) if bAddSpaces then table.insert( tResults, sResult .. " " ) else @@ -81,7 +81,7 @@ local function completeEitherEither( shell, nIndex, sText, tPreviousText ) local tResults = fs.complete( sText, shell.dir(), true, true ) for n=1,#tResults do local sResult = tResults[n] - if string.sub( sResult, #sResult, #sResult ) ~= "/" then + if string.sub( sResult, string.len(sResult), string.len(sResult) ) ~= "/" then tResults[n] = sResult .. " " end end