RichTextFX icon indicating copy to clipboard operation
RichTextFX copied to clipboard

Cannot copy long text to clipboard

Open yososs opened this issue 2 years ago • 3 comments

Exception thrown from writeUTF method.

https://github.com/FXMisc/RichTextFX/blob/5f70d254ab44889cfdcf4451a8c2e18483ec33ac/richtextfx/src/main/java/org/fxmisc/richtext/model/Codec.java#L32-L42

See the following JavaDoc.

https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/io/DataOutputStream.html#writeUTF(java.lang.String)

Throws: UTFDataFormatException - if the modified UTF-8 encoding of str would exceed 65535 bytes in length

yososs avatar Jan 13 '23 23:01 yososs

Thank you for reporting, Pull Requests are welcome :-)

Jugen avatar Jan 15 '23 05:01 Jugen

I propose this modified code.

This fix extends the string encoding/decoding limit of 65,535( Modified UTF-8) bytes to the maximum number of elements in a char array in Java.

Note that this implementation is UTF-16 encoded and therefore memory inefficient. Standard UTF-8 encoding/decoding may not pass the test due to code point changes. Modified UTF-8 encoding may work around this, but Modified code would not be simple.

Codec.java

	static final Codec<String> STRING_CODEC = new Codec<String>() {

		@Override
		public String getName() {
			return "string";
		}

		@Override
		public void encode(DataOutputStream os, String s) throws IOException {
//    		os.writeUTF(s);
			os.writeInt(s.length());
			os.writeChars(s);
		}

		@Override
		public String decode(DataInputStream is) throws IOException {
//            return is.readUTF();

			final int len = is.readInt();
			final char[] chars = new char[len];
			for (int i = 0; i < len; i++) {
				chars[i] = is.readChar();
			}
			return String.valueOf(chars);
		}
	};

CodecTest.java

public class CodecTest {

	@Test
	public void testStringCodec() throws IOException {

		final String[] testDatas = new String[2];
		Arrays.setAll(testDatas, i -> createTestString());

		final ByteArrayOutputStream bout = new ByteArrayOutputStream();
		try (final DataOutputStream out = new DataOutputStream(bout)) {
			for (final String str : testDatas) {
				Codec.STRING_CODEC.encode(out, str);
			}
		}
		final byte[] encoded = bout.toByteArray();

		try (final DataInputStream ins = new DataInputStream(new ByteArrayInputStream(encoded))) {
			for (final String expected : testDatas) {
				Assert.assertEquals(expected, Codec.STRING_CODEC.decode(ins));
			}
			Assert.assertTrue(ins.read() < 0);
		}

	}

	private static String createTestString() {
		final Random r = new Random();
		final StringBuilder buf = new StringBuilder();
		while (buf.length() <= 0xffff && buf.toString().getBytes(StandardCharsets.UTF_8).length <= 0xffff) {
			final int cp = r.nextInt(0x20, 0x10FFF);
			if (Character.isDefined(cp)) {
				buf.appendCodePoint(cp);
			}
		}
		return buf.toString();
	}

	@Test
	public void testStringCodec_empty() throws IOException {

		final String testData = "";

		final ByteArrayOutputStream bout = new ByteArrayOutputStream();
		try (final DataOutputStream out = new DataOutputStream(bout)) {
			Codec.STRING_CODEC.encode(out, testData);
		}
		final byte[] encoded = bout.toByteArray();

		try (final DataInputStream ins = new DataInputStream(new ByteArrayInputStream(encoded))) {
			Assert.assertEquals(testData, Codec.STRING_CODEC.decode(ins));
			Assert.assertTrue(ins.read() < 0);
		}
	}
}

yososs avatar Jan 20 '23 14:01 yososs

This issue happens to me long ago (not with this library) and I fixed it just by using this line yourString.replace("\0", ""); This will replace all your NULL (0x00) values in the content. I belive this happens because the way the String is handled by the native code and the NULL termination, So looks like when you copy all content the System thinks the String ends at the first NULL (0x00> value, and that's all you get into the clip board. Try replace("\0", "") and let me know if that helps.

xdsswar avatar Feb 23 '23 00:02 xdsswar