/******************************************************************************* * Copyright (c) 2000, 2016 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.swt.internal; import java.util.*; import org.eclipse.swt.*; import org.eclipse.swt.graphics.*; import org.eclipse.swt.internal.win32.*; import org.eclipse.swt.widgets.*; /* * Wraps Win32 API used to bidi enable widgets. Up to 3.104 was used by * StyledText widget exclusively. 3.105 release introduced the method * #resolveTextDirection, which is used by other widgets as well. */ public class BidiUtil { // Keyboard language ids public static final int KEYBOARD_NON_BIDI = 0; public static final int KEYBOARD_BIDI = 1; // bidi flag static int isBidiPlatform = -1; // 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 Map languageMap = new HashMap<> (); static Map oldProcMap = new HashMap<> (); /* * This code is intentionally commented. In order * to support CLDC, .class cannot be used because * it does not compile on some Java compilers when * they are targeted for CLDC. */ // static Callback callback = new Callback (BidiUtil.class, "windowProc", 4); static final String CLASS_NAME = "org.eclipse.swt.internal.BidiUtil"; //$NON-NLS-1$ static Callback callback; static { try { callback = new Callback (Class.forName (CLASS_NAME), "windowProc", 4); //$NON-NLS-1$ if (callback.getAddress () == 0) SWT.error (SWT.ERROR_NO_MORE_CALLBACKS); } catch (ClassNotFoundException e) {} } // 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 byte GCPCLASS_LATINNUMBER = 5; static final int GCPGLYPH_LINKBEFORE = 0x8000; static final int GCPGLYPH_LINKAFTER = 0x4000; // ExtTextOut constants static final int ETO_CLIPPED = 0x4; static final int ETO_GLYPH_INDEX = 0x0010; // Windows primary language identifiers static final int LANG_ARABIC = 0x01; static final int LANG_HEBREW = 0x0d; static final int LANG_FARSI = 0x29; // code page identifiers static final String CD_PG_HEBREW = "1255"; //$NON-NLS-1$ static final String CD_PG_ARABIC = "1256"; //$NON-NLS-1$ // 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 CLASS_LATINNUMBER = GCPCLASS_LATINNUMBER; 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. *

* * @param hwnd 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 (long hwnd, Runnable runnable) { languageMap.put(new LONG(hwnd), runnable); subclass(hwnd); } public static void addLanguageListener (Control control, Runnable runnable) { addLanguageListener(control.handle, runnable); } /** * Proc used for OS.EnumSystemLanguageGroups call during isBidiPlatform test. */ static long EnumSystemLanguageGroupsProc(long lpLangGrpId, long lpLangGrpIdString, long lpLangGrpName, long options, long lParam) { if ((int)lpLangGrpId == OS.LGRPID_HEBREW) { isBidiPlatform = 1; return 0; } if ((int)lpLangGrpId == OS.LGRPID_ARABIC) { isBidiPlatform = 1; return 0; } return 1; } /** * Wraps the ExtTextOut function. *

* * @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) { int length = renderDx.length; if (OS.GetLayout (gc.handle) != 0) { reverse(renderDx); renderDx[length-1]--; //fixes bug 40006 reverse(renderBuffer); } // render transparently to avoid overlapping segments. fixes bug 40006 int oldBkMode = OS.SetBkMode(gc.handle, OS.TRANSPARENT); OS.ExtTextOut(gc.handle, x, y, ETO_GLYPH_INDEX , null, renderBuffer, renderBuffer.length, renderDx); OS.SetBkMode(gc.handle, oldBkMode); } /** * Return ordering and rendering information for the given text. Wraps the GetFontLanguageInfo * and GetCharacterPlacement functions. *

* * @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 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. * @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); long hHeap = OS.GetProcessHeap(); boolean isRightOriented = OS.GetLayout(gc.handle) != 0; char [] textBuffer = text.toCharArray(); 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; long lpOrder = result.lpOrder = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount * 4); long lpDx = result.lpDx = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount * 4); long lpClass = result.lpClass = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount); long lpGlyphs = result.lpGlyphs = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount * 2); // set required dwFlags int dwFlags = 0; int glyphFlags = 0; // Always reorder. We assume that if we are calling this function we're // on a platform that supports bidi. Fixes 20690. 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 * * @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); long hHeap = OS.GetProcessHeap(); char [] textBuffer = text.toCharArray(); int byteCount = textBuffer.length; boolean isRightOriented = OS.GetLayout(gc.handle) != 0; GCP_RESULTS result = new GCP_RESULTS(); result.lStructSize = GCP_RESULTS.sizeof; result.nGlyphs = byteCount; long lpOrder = result.lpOrder = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, byteCount * 4); long 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; // Always reorder. We assume that if we are calling this function we're // on a platform that supports bidi. Fixes 20690. 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 * * @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 type. *

* * @return an integer representing the active keyboard language (KEYBOARD_BIDI, * KEYBOARD_NON_BIDI) */ public static int getKeyboardLanguage() { long layout = OS.GetKeyboardLayout(0); return isBidiLang(layout) ? KEYBOARD_BIDI : KEYBOARD_NON_BIDI; } /** * Return the languages that are installed for the keyboard. *

* * @return integer array with an entry for each installed language */ static long[] getKeyboardLanguageList() { int maxSize = 10; long[] tempList = new long[maxSize]; int size = OS.GetKeyboardLayoutList(maxSize, tempList); long[] list = new long[size]; System.arraycopy(tempList, 0, list, 0, size); return list; } static boolean isBidiLang(long lang) { int id = OS.PRIMARYLANGID(OS.LOWORD(lang)); return id == LANG_ARABIC || id == LANG_HEBREW || id == LANG_FARSI; } /** * Return whether or not the platform supports a bidi language. Determine this * by looking at the languages that are installed. *

* * @return true if bidi is supported, false otherwise. Always * false on Windows CE. */ public static boolean isBidiPlatform() { if (isBidiPlatform != -1) return isBidiPlatform == 1; // already set isBidiPlatform = 0; // The following test is a workaround for bug report 27629. On WinXP, // both bidi and complex script (e.g., Thai) languages must be installed // at the same time. Since the bidi platform calls do not support // double byte characters, there is no way to run Eclipse using the // complex script languages on XP, so constrain this test to answer true // only if a bidi input language is defined. Doing so will allow complex // script languages to work (e.g., one can install bidi and complex script // languages, but only install the Thai keyboard). if (!isKeyboardBidi()) return false; Callback callback = null; try { callback = new Callback (Class.forName (CLASS_NAME), "EnumSystemLanguageGroupsProc", 5); //$NON-NLS-1$ long lpEnumSystemLanguageGroupsProc = callback.getAddress (); if (lpEnumSystemLanguageGroupsProc == 0) SWT.error(SWT.ERROR_NO_MORE_CALLBACKS); OS.EnumSystemLanguageGroups(lpEnumSystemLanguageGroupsProc, OS.LGRPID_INSTALLED, 0); callback.dispose (); } catch (ClassNotFoundException e) { //callback can only be null at this point } if (isBidiPlatform == 1) return true; // need to look at system code page for NT & 98 platforms since EnumSystemLanguageGroups is // not supported for these platforms String codePage = String.valueOf(OS.GetACP()); if (CD_PG_ARABIC.equals(codePage) || CD_PG_HEBREW.equals(codePage)) { isBidiPlatform = 1; } return isBidiPlatform == 1; } /** * Return whether or not the keyboard supports input of a bidi language. Determine this * by looking at the languages that are installed for the keyboard. *

* * @return true if bidi is supported, false otherwise. */ public static boolean isKeyboardBidi() { long[] list = getKeyboardLanguageList(); for (int i=0; i * * @param hwnd the handle of the Control that is listening for keyboard language changes */ public static void removeLanguageListener (long hwnd) { languageMap.remove(new LONG(hwnd)); unsubclass(hwnd); } public static void removeLanguageListener (Control control) { removeLanguageListener(control.handle); } /** * Determine the base direction for the given text. The direction is derived * from that of the first strong bidirectional character. In case the text * doesn't contain any strong characters, the base direction is to be * derived from a higher-level protocol (e.g. the widget orientation). *

* * @param text * Text base direction should be resolved for. * @return SWT#LEFT_RIGHT or SWT#RIGHT_TO_LEFT if the text contains strong * characters and thus the direction can be resolved, SWT#NONE * otherwise. * @since 3.105 */ public static int resolveTextDirection (String text) { if (text == null) return SWT.NONE; int length = text.length(); if (length == 0) return SWT.NONE; char[] rtlProbe = {' ', ' ', '1'}; /* * "Wide" version of win32 API can also run even on non-Unicode Windows, * hence need for OS.IsUnicode check here. */ char[] ltrProbe = {'\u202b', 'a', ' '}; char[] numberProbe = {'\u05d0', ' ', ' '}; GCP_RESULTS result = new GCP_RESULTS(); result.lStructSize = GCP_RESULTS.sizeof; int nGlyphs = result.nGlyphs = ltrProbe.length; long hHeap = OS.GetProcessHeap(); long lpOrder = result.lpOrder = OS.HeapAlloc(hHeap, OS.HEAP_ZERO_MEMORY, nGlyphs * 4); long hdc = OS.GetDC(0); int[] order = new int[1]; int textDirection = SWT.NONE; for (int i = 0; i < length; i++) { char ch = text.charAt(i); rtlProbe[0] = ch; OS.GetCharacterPlacement(hdc, rtlProbe, rtlProbe.length, 0, result, OS.GCP_REORDER); OS.MoveMemory(order, result.lpOrder, 4); if (order[0] == 2) { textDirection = SWT.RIGHT_TO_LEFT; break; } ltrProbe[2] = ch; OS.GetCharacterPlacement(hdc, ltrProbe, ltrProbe.length, 0, result, OS.GCP_REORDER); OS.MoveMemory(order, result.lpOrder + 4, 4); if (order[0] == 1) { numberProbe[2] = ch; OS.GetCharacterPlacement(hdc, numberProbe, numberProbe.length, 0, result, OS.GCP_REORDER); OS.MoveMemory(order, result.lpOrder, 4); if (order[0] == 0) { textDirection = SWT.LEFT_TO_RIGHT; break; } } } OS.ReleaseDC (0, hdc); OS.HeapFree(hHeap, 0, lpOrder); return textDirection; } /** * Switch the keyboard language to the specified language type. We do * not distinguish between multiple bidi or multiple non-bidi languages, so * set the keyboard to the first language of the given type. *

* * @param language integer representing language. One of * KEYBOARD_BIDI, KEYBOARD_NON_BIDI. */ public static void setKeyboardLanguage(int language) { if (language == getKeyboardLanguage()) return; boolean bidi = language == KEYBOARD_BIDI; long[] list = getKeyboardLanguageList(); for (int i=0; i * * @param hwnd the handle of the Control to change the orientation of * @param orientation one of SWT.RIGHT_TO_LEFT or SWT.LEFT_TO_RIGHT * @return true if the orientation was changed, false if the orientation * could not be changed */ public static boolean setOrientation (long hwnd, int orientation) { int bits = OS.GetWindowLong (hwnd, OS.GWL_EXSTYLE); if ((orientation & SWT.RIGHT_TO_LEFT) != 0) { bits |= OS.WS_EX_LAYOUTRTL; } else { bits &= ~OS.WS_EX_LAYOUTRTL; } OS.SetWindowLong (hwnd, OS.GWL_EXSTYLE, bits); return true; } public static boolean setOrientation (Control control, int orientation) { return setOrientation(control.handle, orientation); } /** * Override the window proc. * * @param hwnd control to override the window proc of */ static void subclass(long hwnd) { LONG key = new LONG(hwnd); if (oldProcMap.get(key) == null) { long oldProc = OS.GetWindowLongPtr(hwnd, OS.GWLP_WNDPROC); oldProcMap.put(key, new LONG(oldProc)); OS.SetWindowLongPtr(hwnd, OS.GWLP_WNDPROC, callback.getAddress()); } } /** * Reverse the character array. Used for right orientation. * * @param charArray character array to reverse */ static void reverse(char[] charArray) { int length = charArray.length; for (int i = 0; i <= (length - 1) / 2; i++) { char tmp = charArray[i]; charArray[i] = charArray[length - 1 - i]; charArray[length - 1 - i] = tmp; } } /** * Reverse the integer array. Used for right orientation. * * @param intArray integer array to reverse */ static void reverse(int[] intArray) { int length = intArray.length; for (int i = 0; i <= (length - 1) / 2; i++) { int tmp = intArray[i]; intArray[i] = intArray[length - 1 - i]; intArray[length - 1 - i] = tmp; } } /** * Adjust the order array so that it is relative to the start of the line. Also reverse the order array if the orientation * is to the right. * * @param orderArray integer array of order values to translate * @param glyphCount number of glyphs that have been processed for the current line * @param isRightOriented flag indicating whether or not current orientation is to the right */ static void translateOrder(int[] orderArray, int glyphCount, boolean isRightOriented) { int maxOrder = 0; int length = orderArray.length; if (isRightOriented) { for (int i=0; i