mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-25 19:07:39 +00:00
Map Unicode to CC's charset for char/paste events
We now convert uncode characters from "char" and "paste" events to CC's charset[^1], rather than just leaving them unconverted. This means you can paste in special characters like "♠" or "🮙" and they will be converted correctly. Characters outside that range will be replaced with "?", as before. It would be nice to make this a bi-directional mapping, and do this for Lua methods too (e.g. os.setComputerLabel). However, that has much wider ramifications (and more likelyhood of breaking something), so avoiding that for now. - Remove the generic "queue event" client->server message, and replace it with separate char/terminate/paste messages. This allows us to delete a chunk of code (all the NBT<->Object conversion), and makes server-side validation of events possible. - Fix os.setComputerLabel accepting the section sign — this is treated as special by Minecraft's formatting code. Sorry, no fun allowed. - Convert paste/char codepoints to CC's charset. Sadly MC's char hook splits the codepoint into surrogate pairs, which we *don't* attempt to reconstruct, so you can't currently use unicode input for block characters — you can paste them though! [^1]: I'm referring this to the "terminal charset" within the code. I've flip-flopped between "CraftOS", "terminal", "ComputerCraft", but feel especially great.
This commit is contained in:
@@ -4,7 +4,10 @@
|
||||
|
||||
package dan200.computercraft.core.computer;
|
||||
|
||||
import dan200.computercraft.core.util.StringUtil;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Built-in events that can be queued on a computer.
|
||||
@@ -21,6 +24,28 @@ public final class ComputerEvents {
|
||||
receiver.queueEvent("key_up", new Object[]{ key });
|
||||
}
|
||||
|
||||
/**
|
||||
* Type a character on the computer.
|
||||
*
|
||||
* @param receiver The computer to queue the event on.
|
||||
* @param chr The character to type.
|
||||
* @see StringUtil#isTypableChar(byte)
|
||||
*/
|
||||
public static void charTyped(Receiver receiver, byte chr) {
|
||||
receiver.queueEvent("char", new Object[]{ new byte[]{ chr } });
|
||||
}
|
||||
|
||||
/**
|
||||
* Paste a string.
|
||||
*
|
||||
* @param receiver The computer to queue the event on.
|
||||
* @param contents The string to paste.
|
||||
* @see StringUtil#getClipboardString(String)
|
||||
*/
|
||||
public static void paste(Receiver receiver, ByteBuffer contents) {
|
||||
receiver.queueEvent("paste", new Object[]{ contents });
|
||||
}
|
||||
|
||||
public static void mouseClick(Receiver receiver, int button, int x, int y) {
|
||||
receiver.queueEvent("mouse_click", new Object[]{ button, x, y });
|
||||
}
|
||||
|
||||
@@ -4,52 +4,116 @@
|
||||
|
||||
package dan200.computercraft.core.util;
|
||||
|
||||
import dan200.computercraft.core.computer.ComputerEvents;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class StringUtil {
|
||||
public static final int MAX_PASTE_LENGTH = 512;
|
||||
|
||||
private StringUtil() {
|
||||
}
|
||||
|
||||
private static boolean isAllowed(char c) {
|
||||
return (c >= ' ' && c <= '~') || (c >= 161 && c <= 172) || (c >= 174 && c <= 255);
|
||||
}
|
||||
|
||||
private static String removeSpecialCharacters(String text, int length) {
|
||||
var builder = new StringBuilder(length);
|
||||
for (var i = 0; i < length; i++) {
|
||||
var c = text.charAt(i);
|
||||
builder.append(isAllowed(c) ? c : '?');
|
||||
/**
|
||||
* Convert a Unicode character to a terminal one.
|
||||
*
|
||||
* @param chr The Unicode character.
|
||||
* @return The terminal character.
|
||||
*/
|
||||
public static byte unicodeToTerminal(int chr) {
|
||||
// ASCII and latin1 map to themselves
|
||||
if (chr == 0 || chr == '\t' || chr == '\n' || chr == '\r' || (chr >= ' ' && chr <= '~') || (chr >= 160 && chr <= 255)) {
|
||||
return (byte) chr;
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
// Teletext block mosaics are *fairly* contiguous.
|
||||
if (chr >= 0x1FB00 && chr <= 0x1FB13) return (byte) (chr + (129 - 0x1fb00));
|
||||
if (chr >= 0x1FB14 && chr <= 0x1FB1D) return (byte) (chr + (150 - 0x1fb14));
|
||||
|
||||
// Everything else is just a manual lookup. For now, we just use a big switch statement, which we spin into a
|
||||
// separate function to hopefully avoid inlining it here.
|
||||
return unicodeToCraftOsFallback(chr);
|
||||
}
|
||||
|
||||
public static String normaliseLabel(String text) {
|
||||
return removeSpecialCharacters(text, Math.min(32, text.length()));
|
||||
private static byte unicodeToCraftOsFallback(int c) {
|
||||
return switch (c) {
|
||||
case 0x263A -> 1;
|
||||
case 0x263B -> 2;
|
||||
case 0x2665 -> 3;
|
||||
case 0x2666 -> 4;
|
||||
case 0x2663 -> 5;
|
||||
case 0x2660 -> 6;
|
||||
case 0x2022 -> 7;
|
||||
case 0x25D8 -> 8;
|
||||
case 0x2642 -> 11;
|
||||
case 0x2640 -> 12;
|
||||
case 0x266A -> 14;
|
||||
case 0x266B -> 15;
|
||||
case 0x25BA -> 16;
|
||||
case 0x25C4 -> 17;
|
||||
case 0x2195 -> 18;
|
||||
case 0x203C -> 19;
|
||||
case 0x25AC -> 22;
|
||||
case 0x21A8 -> 23;
|
||||
case 0x2191 -> 24;
|
||||
case 0x2193 -> 25;
|
||||
case 0x2192 -> 26;
|
||||
case 0x2190 -> 27;
|
||||
case 0x221F -> 28;
|
||||
case 0x2194 -> 29;
|
||||
case 0x25B2 -> 30;
|
||||
case 0x25BC -> 31;
|
||||
case 0x1FB99 -> 127;
|
||||
case 0x258C -> (byte) 149;
|
||||
default -> '?';
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise a string from the clipboard, suitable for pasting into a computer.
|
||||
* Check if a character is capable of being input and passed to a {@linkplain ComputerEvents#charTyped(ComputerEvents.Receiver, byte)
|
||||
* "char" event}.
|
||||
*
|
||||
* @param chr The character to check.
|
||||
* @return Whether this character can be typed.
|
||||
*/
|
||||
public static boolean isTypableChar(byte chr) {
|
||||
return chr != 0 && chr != '\r' && chr != '\n';
|
||||
}
|
||||
|
||||
private static boolean isAllowedInLabel(char c) {
|
||||
// Limit to ASCII and latin1, excluding '§' (Minecraft's formatting character).
|
||||
return (c >= ' ' && c <= '~') || (c >= 161 && c <= 255 && c != 167);
|
||||
}
|
||||
|
||||
public static String normaliseLabel(String text) {
|
||||
var length = Math.min(32, text.length());
|
||||
var builder = new StringBuilder(length);
|
||||
for (var i = 0; i < length; i++) {
|
||||
var c = text.charAt(i);
|
||||
builder.append(isAllowedInLabel(c) ? c : '?');
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Java string to a Lua one (using the terminal charset), suitable for pasting into a computer.
|
||||
* <p>
|
||||
* This removes special characters and strips to the first line of text.
|
||||
*
|
||||
* @param clipboard The text from the clipboard.
|
||||
* @return The normalised clipboard text.
|
||||
* @return The encoded clipboard text.
|
||||
*/
|
||||
public static String normaliseClipboardString(String clipboard) {
|
||||
// Clip to the first occurrence of \r or \n
|
||||
var newLineIndex1 = clipboard.indexOf('\r');
|
||||
var newLineIndex2 = clipboard.indexOf('\n');
|
||||
public static ByteBuffer getClipboardString(String clipboard) {
|
||||
var output = new byte[Math.min(MAX_PASTE_LENGTH, clipboard.length())];
|
||||
var idx = 0;
|
||||
|
||||
int length;
|
||||
if (newLineIndex1 >= 0 && newLineIndex2 >= 0) {
|
||||
length = Math.min(newLineIndex1, newLineIndex2);
|
||||
} else if (newLineIndex1 >= 0) {
|
||||
length = newLineIndex1;
|
||||
} else if (newLineIndex2 >= 0) {
|
||||
length = newLineIndex2;
|
||||
} else {
|
||||
length = clipboard.length();
|
||||
var iterator = clipboard.codePoints().iterator();
|
||||
while (iterator.hasNext() && idx <= output.length) {
|
||||
var chr = unicodeToTerminal(iterator.next());
|
||||
if (!isTypableChar(chr)) break;
|
||||
output[idx++] = chr;
|
||||
}
|
||||
|
||||
return removeSpecialCharacters(clipboard, Math.min(length, 512));
|
||||
return ByteBuffer.wrap(output, 0, idx).asReadOnlyBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user