package org.eclipse.swt.internal; | |
/* | |
* (c) Copyright IBM Corp. 2001, 2002. | |
* All Rights Reserved | |
*/ | |
import org.eclipse.swt.SWT; | |
import org.eclipse.swt.graphics.GC; | |
import org.eclipse.swt.internal.win32.GCP_RESULTS; | |
import org.eclipse.swt.internal.win32.OS; | |
import org.eclipse.swt.internal.win32.RECT; | |
import org.eclipse.swt.internal.win32.TCHAR; | |
import java.util.Hashtable; | |
/* | |
* Wraps Win32 API used to bidi enable the StyledText widget. | |
*/ | |
public class BidiUtil { | |
// Keyboard language ids | |
public static final int KEYBOARD_LATIN = 0; | |
public static final int KEYBOARD_HEBREW = 1; | |
public static final int KEYBOARD_ARABIC = 2; | |
// getRenderInfo flag values | |
public static final int CLASSIN = 1; | |
public static final int LINKBEFORE = 2; | |
public static final int LINKAFTER = 4; | |
// variables used for providing a listener mechanism for keyboard language | |
// switching | |
static Hashtable map = new Hashtable (); | |
static Hashtable oldProcMap = new Hashtable (); | |
static Callback callback = new Callback (BidiUtil.class, "windowProc", 4); | |
// GetCharacterPlacement constants | |
static final int GCP_REORDER = 0x0002; | |
static final int GCP_GLYPHSHAPE = 0x0010; | |
static final int GCP_LIGATE = 0x0020; | |
static final int GCP_CLASSIN = 0x00080000; | |
static final byte GCPCLASS_ARABIC = 2; | |
static final byte GCPCLASS_HEBREW = 2; | |
static final byte GCPCLASS_LOCALNUMBER = 4; | |
static final int GCPGLYPH_LINKBEFORE = 0x8000; | |
static final int GCPGLYPH_LINKAFTER = 0x4000; | |
// ExtTextOut constants | |
static final int ETO_GLYPH_INDEX = 0x0010; | |
// Windows primary language identifiers | |
static final int LANG_ARABIC = 0x01; | |
static final int LANG_HEBREW = 0x0d; | |
// ActivateKeyboard constants | |
static final int HKL_NEXT = 1; | |
static final int HKL_PREV = 0; | |
/* | |
* Public character class constants are the same as Windows | |
* platform constants. | |
* Saves conversion of class array in getRenderInfo to arbitrary | |
* constants for now. | |
*/ | |
public static final int CLASS_HEBREW = GCPCLASS_ARABIC; | |
public static final int CLASS_ARABIC = GCPCLASS_HEBREW; | |
public static final int CLASS_LOCALNUMBER = GCPCLASS_LOCALNUMBER; | |
public static final int REORDER = GCP_REORDER; | |
public static final int LIGATE = GCP_LIGATE; | |
public static final int GLYPHSHAPE = GCP_GLYPHSHAPE; | |
/** | |
* Adds a language listener. The listener will get notified when the language of | |
* the keyboard changes (via Alt-Shift on Win platforms). Do this by creating a | |
* window proc for the Control so that the window messages for the Control can be | |
* monitored. | |
* <p> | |
* | |
* @param int the handle of the Control that is listening for keyboard language | |
* changes | |
* @param runnable the code that should be executed when a keyboard language change | |
* occurs | |
*/ | |
public static void addLanguageListener (int hwnd, Runnable runnable) { | |
map.put (new Integer (hwnd), runnable); | |
int oldProc = OS.GetWindowLong (hwnd, OS.GWL_WNDPROC); | |
oldProcMap.put (new Integer(hwnd), new Integer(oldProc)); | |
OS.SetWindowLong (hwnd, OS.GWL_WNDPROC, callback.getAddress ()); | |
} | |
/** | |
* Wraps the ExtTextOut function. | |
* <p> | |
* | |
* @param gc the gc to use for rendering | |
* @param renderBuffer the glyphs to render as an array of characters | |
* @param renderDx the width of each glyph in renderBuffer | |
* @param x x position to start rendering | |
* @param y y position to start rendering | |
*/ | |
public static void drawGlyphs(GC gc, char[] renderBuffer, int[] renderDx, int x, int y) { | |
RECT rect = null; | |
OS.ExtTextOutW(gc.handle, x, y, ETO_GLYPH_INDEX, rect, renderBuffer, renderBuffer.length, renderDx); | |
} | |
/** | |
* Return ordering and rendering information for the given text. Wraps the GetFontLanguageInfo | |
* and GetCharacterPlacement functions. | |
* <p> | |
* | |
* @param gc the GC to use for measuring of this line, input parameter | |
* @param text text that bidi data should be calculated for, input parameter | |
* @param order an array of integers representing the visual position of each character in | |
* the text array, output parameter | |
* @param classBuffer an array of integers representing the type (e.g., ARABIC, HEBREW, | |
* LOCALNUMBER) of each character in the text array, input/output parameter | |
* @param dx an array of integers representing the pixel width of each glyph in the returned | |
* glyph buffer, output paramteter | |
* @param flags an integer representing rendering flag information, input parameter | |
* @param offsets text segments that should be measured and reordered separately, input | |
* parameter. See org.eclipse.swt.custom.BidiSegmentEvent for details. | |
* @return buffer with the glyphs that should be rendered for the given text | |
*/ | |
public static char[] getRenderInfo(GC gc, String text, int[] order, byte[] classBuffer, int[] dx, int flags, int [] offsets) { | |
int fontLanguageInfo = OS.GetFontLanguageInfo(gc.handle); | |
int hHeap = OS.GetProcessHeap(); | |
int[] lpCs = new int[8]; | |
int cs = OS.GetTextCharset(gc.handle); | |
OS.TranslateCharsetInfo(cs, lpCs, OS.TCI_SRCCHARSET); | |
TCHAR textBuffer = new TCHAR(lpCs[1], text, false); | |
int byteCount = textBuffer.length(); | |
boolean linkBefore = (flags & LINKBEFORE) == LINKBEFORE; | |
boolean linkAfter = (flags & LINKAFTER) == LINKAFTER; | |
GCP_RESULTS result = new GCP_RESULTS(); | |
result.lStructSize = GCP_RESULTS.sizeof; | |
result.nGlyphs = byteCount; | |
int lpOrder = result.lpOrder = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount * 4); | |
int lpDx = result.lpDx = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount * 4); | |
int lpClass = result.lpClass = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount); | |
int lpGlyphs = result.lpGlyphs = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount * 2); | |
// set required dwFlags | |
int dwFlags = 0; | |
int glyphFlags = 0; | |
if (((fontLanguageInfo & GCP_REORDER) == GCP_REORDER)) { | |
dwFlags |= GCP_REORDER; | |
} | |
if ((fontLanguageInfo & GCP_LIGATE) == GCP_LIGATE) { | |
dwFlags |= GCP_LIGATE; | |
glyphFlags |= 0; | |
} | |
if ((fontLanguageInfo & GCP_GLYPHSHAPE) == GCP_GLYPHSHAPE) { | |
dwFlags |= GCP_GLYPHSHAPE; | |
if (linkBefore) { | |
glyphFlags |= GCPGLYPH_LINKBEFORE; | |
} | |
if (linkAfter) { | |
glyphFlags |= GCPGLYPH_LINKAFTER; | |
} | |
} | |
byte[] lpGlyphs2; | |
if (linkBefore || linkAfter) { | |
lpGlyphs2 = new byte[2]; | |
lpGlyphs2[0]=(byte)glyphFlags; | |
lpGlyphs2[1]=(byte)(glyphFlags >> 8); | |
} | |
else { | |
lpGlyphs2 = new byte[] {(byte) glyphFlags}; | |
} | |
OS.MoveMemory(result.lpGlyphs, lpGlyphs2, lpGlyphs2.length); | |
if ((flags & CLASSIN) == CLASSIN) { | |
// set classification values for the substring | |
dwFlags |= GCP_CLASSIN; | |
OS.MoveMemory(result.lpClass, classBuffer, classBuffer.length); | |
} | |
char[] glyphBuffer = new char[result.nGlyphs]; | |
int glyphCount = 0; | |
for (int i=0; i<offsets.length-1; i++) { | |
int offset = offsets [i]; | |
int length = offsets [i+1] - offsets [i]; | |
// The number of glyphs expected is <= length (segment length); | |
// the actual number returned may be less in case of Arabic ligatures. | |
result.nGlyphs = length; | |
TCHAR textBuffer2 = new TCHAR(lpCs[1], text.substring(offset, offset + length), false); | |
OS.GetCharacterPlacement(gc.handle, textBuffer2, textBuffer2.length(), 0, result, dwFlags); | |
if (dx != null) { | |
int [] dx2 = new int [result.nGlyphs]; | |
OS.MoveMemory(dx2, result.lpDx, dx2.length * 4); | |
System.arraycopy (dx2, 0, dx, glyphCount, dx2.length); | |
} | |
if (order != null) { | |
int [] order2 = new int [length]; | |
OS.MoveMemory(order2, result.lpOrder, order2.length * 4); | |
for (int j=0; j<length; j++) { | |
order2 [j] += glyphCount; | |
} | |
System.arraycopy (order2, 0, order, offset, length); | |
} | |
if (classBuffer != null) { | |
byte [] classBuffer2 = new byte [length]; | |
OS.MoveMemory(classBuffer2, result.lpClass, classBuffer2.length); | |
System.arraycopy (classBuffer2, 0, classBuffer, offset, length); | |
} | |
char[] glyphBuffer2 = new char[result.nGlyphs]; | |
OS.MoveMemory(glyphBuffer2, result.lpGlyphs, glyphBuffer2.length * 2); | |
System.arraycopy (glyphBuffer2, 0, glyphBuffer, glyphCount, glyphBuffer2.length); | |
glyphCount += glyphBuffer2.length; | |
// We concatenate successive results of calls to GCP. | |
// For Arabic, it is the only good method since the number of output | |
// glyphs might be less than the number of input characters. | |
// This assumes that the whole line is built by successive adjacent | |
// segments without overlapping. | |
result.lpOrder += length * 4; | |
result.lpDx += length * 4; | |
result.lpClass += length; | |
result.lpGlyphs += glyphBuffer2.length * 2; | |
} | |
/* Free the memory that was allocated. */ | |
OS.HeapFree(hHeap, 0, lpGlyphs); | |
OS.HeapFree(hHeap, 0, lpClass); | |
OS.HeapFree(hHeap, 0, lpDx); | |
OS.HeapFree(hHeap, 0, lpOrder); | |
return glyphBuffer; | |
} | |
/** | |
* Return bidi ordering information for the given text. Does not return rendering | |
* information (e.g., glyphs, glyph distances). Use this method when you only need | |
* ordering information. Doing so will improve performance. Wraps the | |
* GetFontLanguageInfo and GetCharacterPlacement functions. | |
* <p> | |
* | |
* @param gc the GC to use for measuring of this line, input parameter | |
* @param text text that bidi data should be calculated for, input parameter | |
* @param order an array of integers representing the visual position of each character in | |
* the text array, output parameter | |
* @param classBuffer an array of integers representing the type (e.g., ARABIC, HEBREW, | |
* LOCALNUMBER) of each character in the text array, input/output parameter | |
* @param flags an integer representing rendering flag information, input parameter | |
* @param offsets text segments that should be measured and reordered separately, input | |
* parameter. See org.eclipse.swt.custom.BidiSegmentEvent for details. | |
*/ | |
public static void getOrderInfo(GC gc, String text, int[] order, byte[] classBuffer, int flags, int [] offsets) { | |
int fontLanguageInfo = OS.GetFontLanguageInfo(gc.handle); | |
int hHeap = OS.GetProcessHeap(); | |
int[] lpCs = new int[8]; | |
int cs = OS.GetTextCharset(gc.handle); | |
OS.TranslateCharsetInfo(cs, lpCs, OS.TCI_SRCCHARSET); | |
TCHAR textBuffer = new TCHAR(lpCs[1], text, false); | |
int byteCount = textBuffer.length(); | |
GCP_RESULTS result = new GCP_RESULTS(); | |
result.lStructSize = GCP_RESULTS.sizeof; | |
result.nGlyphs = byteCount; | |
int lpOrder = result.lpOrder = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount * 4); | |
int lpClass = result.lpClass = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount); | |
// set required dwFlags, these values will affect how the text gets rendered and | |
// ordered | |
int dwFlags = 0; | |
if (((fontLanguageInfo & GCP_REORDER) == GCP_REORDER)) { | |
dwFlags |= GCP_REORDER; | |
} | |
if ((fontLanguageInfo & GCP_LIGATE) == GCP_LIGATE) { | |
dwFlags |= GCP_LIGATE; | |
} | |
if ((fontLanguageInfo & GCP_GLYPHSHAPE) == GCP_GLYPHSHAPE) { | |
dwFlags |= GCP_GLYPHSHAPE; | |
} | |
if ((flags & CLASSIN) == CLASSIN) { | |
// set classification values for the substring, classification values | |
// can be specified on input | |
dwFlags |= GCP_CLASSIN; | |
OS.MoveMemory(result.lpClass, classBuffer, classBuffer.length); | |
} | |
int glyphCount = 0; | |
for (int i=0; i<offsets.length-1; i++) { | |
int offset = offsets [i]; | |
int length = offsets [i+1] - offsets [i]; | |
// The number of glyphs expected is <= length (segment length); | |
// the actual number returned may be less in case of Arabic ligatures. | |
result.nGlyphs = length; | |
TCHAR textBuffer2 = new TCHAR(lpCs[1], text.substring(offset, offset + length), false); | |
OS.GetCharacterPlacement(gc.handle, textBuffer2, textBuffer2.length(), 0, result, dwFlags); | |
if (order != null) { | |
int [] order2 = new int [length]; | |
OS.MoveMemory(order2, result.lpOrder, order2.length * 4); | |
for (int j=0; j<length; j++) { | |
order2 [j] += glyphCount; | |
} | |
System.arraycopy (order2, 0, order, offset, length); | |
} | |
if (classBuffer != null) { | |
byte [] classBuffer2 = new byte [length]; | |
OS.MoveMemory(classBuffer2, result.lpClass, classBuffer2.length); | |
System.arraycopy (classBuffer2, 0, classBuffer, offset, length); | |
} | |
glyphCount += result.nGlyphs; | |
// We concatenate successive results of calls to GCP. | |
// For Arabic, it is the only good method since the number of output | |
// glyphs might be less than the number of input characters. | |
// This assumes that the whole line is built by successive adjacent | |
// segments without overlapping. | |
result.lpOrder += length * 4; | |
result.lpClass += length; | |
} | |
/* Free the memory that was allocated. */ | |
OS.HeapFree(hHeap, 0, lpClass); | |
OS.HeapFree(hHeap, 0, lpOrder); | |
} | |
/** | |
* Return bidi attribute information for the font in the specified gc. | |
* <p> | |
* | |
* @param gc the gc to query | |
* @return bitwise OR of the REORDER, LIGATE and GLYPHSHAPE flags | |
* defined by this class. | |
*/ | |
public static int getFontBidiAttributes(GC gc) { | |
int fontStyle = 0; | |
int fontLanguageInfo = OS.GetFontLanguageInfo(gc.handle); | |
if (((fontLanguageInfo & GCP_REORDER) != 0)) { | |
fontStyle |= REORDER; | |
} | |
if (((fontLanguageInfo & GCP_LIGATE) != 0)) { | |
fontStyle |= LIGATE; | |
} | |
if (((fontLanguageInfo & GCP_GLYPHSHAPE) != 0)) { | |
fontStyle |= GLYPHSHAPE; | |
} | |
return fontStyle; | |
} | |
/** | |
* Return the active keyboard language. | |
* <p> | |
* | |
* @return an integer representing the active keyboard language (KEYBOARD_HEBREW, | |
* KEYBOARD_ARABIC, KEYBOARD_LATIN) | |
*/ | |
public static int getKeyboardLanguage() { | |
int layout = OS.GetKeyboardLayout(0); | |
// only interested in low 2 bytes, which is the primary | |
// language identifier | |
layout = layout & 0x000000FF; | |
if (layout == LANG_HEBREW) return KEYBOARD_HEBREW; | |
if (layout == LANG_ARABIC) return KEYBOARD_ARABIC; | |
// return LATIN for all non-bidi languages | |
return KEYBOARD_LATIN; | |
} | |
/** | |
* Return the languages that are installed for the keyboard. | |
* <p> | |
* | |
* @return integer array with an entry for each installed language | |
*/ | |
static int[] getKeyboardLanguageList() { | |
int maxSize = 10; | |
int[] tempList = new int[maxSize]; | |
int size = OS.GetKeyboardLayoutList(maxSize, tempList); | |
int[] list = new int[size]; | |
System.arraycopy(tempList, 0, list, 0, size); | |
return list; | |
} | |
/** | |
* Return whether or not the platform supports a bidi language. Determine this | |
* by looking at the languages that are installed for the keyboard. | |
* <p> | |
* | |
* @return true if bidi is supported, false otherwise. Always | |
* false on Windows CE. | |
*/ | |
public static boolean isBidiPlatform() { | |
if (OS.IsWinCE) return false; | |
int[] languages = getKeyboardLanguageList(); | |
for (int i=0; i<languages.length; i++) { | |
int language = languages[i] & 0x000000FF;; | |
if ((language == LANG_ARABIC) || (language == LANG_HEBREW)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Removes the specified language listener. | |
* <p> | |
* | |
* @param hwnd the handle of the Control that is listening for keyboard language changes | |
*/ | |
public static void removeLanguageListener (int hwnd) { | |
map.remove (new Integer (hwnd)); | |
Integer proc = (Integer)oldProcMap.remove (new Integer (hwnd)); | |
OS.SetWindowLong (hwnd, OS.GWL_WNDPROC, proc.intValue()); | |
} | |
/** | |
* Switch the keyboard language to the specified language. | |
* <p> | |
* | |
* @param language integer representing language. One of | |
* KEYBOARD_HEBREW, KEYBOARD_ARABIC, KEYBOARD_LATIN. | |
*/ | |
public static void setKeyboardLanguage(int language) { | |
// don't switch the keyboard if it doesn't need to be | |
if (language == getKeyboardLanguage()) return; | |
boolean isBidiLang = (language == KEYBOARD_HEBREW) || (language == KEYBOARD_ARABIC); | |
// get the corresponding WIN language id for the | |
// language | |
if (isBidiLang) { | |
int langId; | |
if (language == KEYBOARD_HEBREW) langId = LANG_HEBREW; | |
else langId = LANG_ARABIC; | |
// get the list of active languages | |
int[] list = getKeyboardLanguageList(); | |
// set to first language of the given type | |
for (int i=0; i<list.length; i++) { | |
int id = list[i] & 0x000000FF; | |
if (id == langId) { | |
OS.ActivateKeyboardLayout(list[i], 0); | |
return; | |
} | |
} | |
} else { | |
// set to the first "Latin" language (anything not | |
// hebrew or arabic) | |
int[] list = getKeyboardLanguageList(); | |
for (int i=0; i<list.length; i++) { | |
int id = list[i] & 0x000000FF; | |
if ((id != LANG_HEBREW) && (id != LANG_ARABIC)) { | |
OS.ActivateKeyboardLayout(list[i], 0); | |
return; | |
} | |
} | |
} | |
} | |
/** | |
* Window proc to intercept keyboard language switch event (WS_INPUTLANGCHANGE). | |
* Run the Control's registered runnable when the keyboard language is switched. | |
* | |
* @param hwnd handle of the control that is listening for the keyboard language | |
* change event | |
* @param msg window message | |
*/ | |
static int windowProc (int hwnd, int msg, int wParam, int lParam) { | |
switch (msg) { | |
case 0x51 /*OS.WM_INPUTLANGCHANGE*/: | |
Runnable runnable = (Runnable) map.get (new Integer (hwnd)); | |
if (runnable != null) runnable.run (); | |
break; | |
} | |
Integer oldProc = (Integer)oldProcMap.get(new Integer(hwnd)); | |
return OS.CallWindowProc (oldProc.intValue(), hwnd, msg, wParam, lParam); | |
} | |
} |