Text encoding improvements.

This commit is contained in:
Ioannis Tsakpinis 2011-08-20 16:38:45 +00:00
parent 6502050630
commit 87c04cc995
6 changed files with 187 additions and 121 deletions

View file

@ -33,26 +33,28 @@ package org.lwjgl;
import java.lang.reflect.Field;
import java.nio.*;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
/**
* [INTERNAL USE ONLY]
* <p/>
* This class provides utility methods for passing buffer addresses to JNI API calls.
* This class provides utility methods for passing buffers to JNI API calls.
*
* @author Spasi
*/
public final class MemoryUtil {
private static final CharsetEncoder textEncoder;
private static final Charset ascii;
private static final Charset utf8;
private static final Charset utf16;
static {
CharsetEncoder encoder = Charset.defaultCharset().newEncoder();
if ( 1.0f < encoder.maxBytesPerChar() )
encoder = Charset.forName("ISO-8859-1").newEncoder();
textEncoder = encoder;
ascii = Charset.forName("ISO-8859-1");
utf8 = Charset.forName("UTF-8");
utf16 = Charset.forName("UTF-16LE");
}
private static final Accessor memUtil;
@ -190,42 +192,7 @@ public final class MemoryUtil {
// --- [ String utilities ] ---
/**
* Returns the specified text as a null-terminated CharBuffer.
*
* @param text the text to encode
*
* @return the encoded text
*/
public static CharBuffer encodeUTF16(final CharSequence text) {
CharBuffer buffer = BufferUtils.createCharBuffer(text.length() + 1);
buffer.append(text).append('\0');
buffer.flip();
return buffer;
}
/**
* Returns the specified text array as a CharBuffer. The CharBuffer is packed
* and each text is null-terminated.
*
* @param text the text array to encode
*
* @return the encoded text
*/
public static CharBuffer encodeUTF16(final CharSequence... text) {
int len = 0;
for ( CharSequence cs : text )
len += cs.length();
final CharBuffer buffer = BufferUtils.createCharBuffer(len + text.length);
for ( CharSequence cs : text )
buffer.append(cs).append('\0');
buffer.flip();
return buffer;
}
/**
* Encodes and null-terminated the specified text and returns a ByteBuffer.
* Returns a ByteBuffer containing the specified text ASCII encoded and null-terminated.
* If text is null, null is returned.
*
* @param text the text to encode
@ -235,16 +202,116 @@ public final class MemoryUtil {
* @see String#getBytes()
*/
public static ByteBuffer encodeASCII(final CharSequence text) {
return encode(text, ascii);
}
/**
* Returns a ByteBuffer containing the specified text UTF-8 encoded and null-terminated.
* If text is null, null is returned.
*
* @param text the text to encode
*
* @return the encoded text or null
*
* @see String#getBytes()
*/
public static ByteBuffer encodeUTF8(final CharSequence text) {
return encode(text, utf8);
}
/**
* Returns a ByteBuffer containing the specified text UTF-16LE encoded and null-terminated.
* If text is null, null is returned.
*
* @param text the text to encode
*
* @return the encoded text
*/
public static ByteBuffer encodeUTF16(final CharSequence text) {
return encode(text, utf16);
}
/**
* Wraps the specified text in a null-terminated CharBuffer and encodes it using the specified Charset.
*
* @param text the text to encode
* @param charset the charset to use for encoding
*
* @return the encoded text
*/
private static ByteBuffer encode(final CharSequence text, final Charset charset) {
if ( text == null )
return null;
final ByteBuffer buffer = BufferUtils.createByteBuffer(text.length() + 1);
return encode(CharBuffer.wrap(new CharSequenceNT(text)), charset);
}
textEncoder.encode(CharBuffer.wrap(text), buffer, true);
buffer.put((byte)0);
buffer.flip();
/**
* A {@link CharsetEncoder#encode(java.nio.CharBuffer)} implementation that uses {@link BufferUtils#createByteBuffer(int)}
* instead of {@link ByteBuffer#allocate(int)}.
*
* @see CharsetEncoder#encode(java.nio.CharBuffer)
*/
private static ByteBuffer encode(final CharBuffer in, final Charset charset) {
final CharsetEncoder encoder = charset.newEncoder(); // encoders are not thread-safe, create a new one on every call
int n = (int)(in.remaining() * encoder.averageBytesPerChar());
ByteBuffer out = BufferUtils.createByteBuffer(n);
if ( n == 0 && in.remaining() == 0 )
return out;
encoder.reset();
while ( true ) {
CoderResult cr = in.hasRemaining() ? encoder.encode(in, out, true) : CoderResult.UNDERFLOW;
if ( cr.isUnderflow() )
cr = encoder.flush(out);
if ( cr.isUnderflow() )
break;
if ( cr.isOverflow() ) {
n = 2 * n + 1; // Ensure progress; n might be 0!
ByteBuffer o = BufferUtils.createByteBuffer(n);
out.flip();
o.put(out);
out = o;
continue;
}
try {
cr.throwException();
} catch (CharacterCodingException e) {
throw new RuntimeException(e);
}
}
out.flip();
return out;
}
/** A null-terminated CharSequence. */
private static class CharSequenceNT implements CharSequence {
final CharSequence source;
CharSequenceNT(CharSequence source) {
this.source = source;
}
public int length() {
return source.length() + 1;
}
public char charAt(final int index) {
return index == source.length() ? '\0' : source.charAt(index);
}
public CharSequence subSequence(final int start, final int end) {
return new CharSequenceNT(source.subSequence(start, Math.min(end, source.length())));
}
return buffer;
}
interface Accessor {
@ -307,4 +374,4 @@ public final class MemoryUtil {
throw new NoSuchFieldException(fieldName + " does not exist in " + type.getSimpleName() + " or any of its superclasses.");
}
}
}

View file

@ -31,7 +31,7 @@
*/
package org.lwjgl;
import java.nio.CharBuffer;
import java.nio.ByteBuffer;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException;
import java.security.AccessController;
@ -99,10 +99,9 @@ final class WindowsSysImplementation extends DefaultSysImplementation {
LWJGLUtil.log(String.format("*** Alert *** %s\n%s\n", title, message));
// Pack both strings in the same buffer
final CharBuffer buffer = MemoryUtil.encodeUTF16(title, message);
final long address = MemoryUtil.getAddress0(buffer);
nAlert(getHwnd(), address, address + (title.length() + 1) * 2);
final ByteBuffer titleText = MemoryUtil.encodeUTF16(title);
final ByteBuffer messageText = MemoryUtil.encodeUTF16(message);
nAlert(getHwnd(), MemoryUtil.getAddress(titleText), MemoryUtil.getAddress(messageText));
}
private static native void nAlert(long parent_hwnd, long title, long message);
private static native void initCommonControls();

View file

@ -53,6 +53,7 @@ import java.lang.reflect.InvocationTargetException;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.LWJGLUtil;
import org.lwjgl.MemoryUtil;
import org.lwjgl.opengl.XRandR.Screen;
import org.lwjgl.opengles.EGL;
@ -138,7 +139,7 @@ final class LinuxDisplay implements DisplayImplementation {
private long current_cursor;
private long blank_cursor;
private boolean mouseInside = true;
private Canvas parent;
private long parent_window;
private boolean xembedded;
@ -148,7 +149,7 @@ final class LinuxDisplay implements DisplayImplementation {
private LinuxKeyboard keyboard;
private LinuxMouse mouse;
private final FocusListener focus_listener = new FocusListener() {
public void focusGained(FocusEvent e) {
synchronized (GlobalLock.lock) {
@ -499,7 +500,7 @@ final class LinuxDisplay implements DisplayImplementation {
private static native void reparentWindow(long display, long window, long parent, int x, int y);
private static native long nGetInputFocus(long display) throws LWJGLException;
private static native void nSetInputFocus(long display, long window, long time);
private static boolean isAncestorXEmbedded(long window) throws LWJGLException {
long xembed_atom = internAtom("_XEMBED_INFO", true);
if (xembed_atom != None) {
@ -728,12 +729,13 @@ final class LinuxDisplay implements DisplayImplementation {
public void setTitle(String title) {
lockAWT();
try {
nSetTitle(getDisplay(), getWindow(), title);
final ByteBuffer titleText = MemoryUtil.encodeUTF8(title);
nSetTitle(getDisplay(), getWindow(), MemoryUtil.getAddress(titleText), titleText.remaining() - 1);
} finally {
unlockAWT();
}
}
private static native void nSetTitle(long display, long window, String title);
private static native void nSetTitle(long display, long window, long title, int len);
public boolean isCloseRequested() {
boolean result = close_requested;
@ -915,19 +917,19 @@ final class LinuxDisplay implements DisplayImplementation {
unlockAWT();
}
}
private void checkInput() {
if (parent == null) return;
if (xembedded) {
long current_focus_window = 0;
try {
current_focus_window = nGetInputFocus(getDisplay());
} catch (LWJGLException e) {
return; // fail silently as it can fail whilst splitting browser tabs
}
if (last_window_focus != current_focus_window || parent_focused != focused) {
if (isParentWindowActive(current_focus_window)) {
if (parent_focused) {
@ -963,49 +965,49 @@ final class LinuxDisplay implements DisplayImplementation {
}
}
}
/**
* This method will check if the parent window is active when running
* in xembed mode. Every xembed embedder window has a focus proxy
* window that recieves all the input. This method will test whether
* the provided window handle is the focus proxy, if so it will get its
* in xembed mode. Every xembed embedder window has a focus proxy
* window that recieves all the input. This method will test whether
* the provided window handle is the focus proxy, if so it will get its
* parent window and then test whether this is an ancestor to our
* current_window. If so then parent window is active.
*
*
* @param window - the window handle to test
*/
private boolean isParentWindowActive(long window) {
try {
// parent window already active as window is current_window
if (window == current_window) return true;
// xembed focus proxy will have no children
if (getChildCount(getDisplay(), window) != 0) return false;
// get parent, will be xembed embedder window and ancestor of current_window
long parent_window = getParentWindow(getDisplay(), window);
// parent must not be None
if (parent_window == None) return false;
// scroll current_window's ancestors to find parent_window
long w = current_window;
while (w != None) {
w = getParentWindow(getDisplay(), w);
if (w == parent_window) {
parent_proxy_focus_window = window; // save focus proxy window
return true;
}
}
}
} catch (LWJGLException e) {
LWJGLUtil.log("Failed to detect if parent window is active: " + e.getMessage());
return true; // on failure assume still active
}
return false; // failed to find an active parent window
}
private void setFocused(boolean got_focus, int focus_detail) {
if (focused == got_focus || focus_detail == NotifyDetailNone || focus_detail == NotifyPointer || focus_detail == NotifyPointerRoot || parent != null)
return;
@ -1356,11 +1358,11 @@ final class LinuxDisplay implements DisplayImplementation {
public boolean isInsideWindow() {
return mouseInside;
}
public void setResizable(boolean resizable) {
}
public boolean wasResized() {
return false;
}
@ -1552,4 +1554,4 @@ final class LinuxDisplay implements DisplayImplementation {
}
}
}
}

View file

@ -438,7 +438,7 @@ final class WindowsDisplay implements DisplayImplementation {
private static native DisplayMode getCurrentDisplayMode() throws LWJGLException;
public void setTitle(String title) {
CharBuffer buffer = MemoryUtil.encodeUTF16(title);
ByteBuffer buffer = MemoryUtil.encodeUTF16(title);
nSetTitle(hwnd, MemoryUtil.getAddress0(buffer));
}
private static native void nSetTitle(long hwnd, long title);