понедельник, 21 ноября 2011 г.

Вызов javascript функции в Firefox из Windows приложения

Разрабатываются Windows приложение и Web приложение. Они выполняются одновременно. Возникла задача передать данные из первого приложения во второе и выполнить там javascript функцию с переданными данными. Для IE всё настолько тривиально, что здесь даже не стоит обсуждать; сложнее оказалось для Mozilla Firefox.
Я стремился следовать принципу KISS при решении этой задачи. Весьма полезными оказались следующие ресурсы: Предлагаемый способ основан на создании в Firefox невидимого окна для приёма сообщений. Это окно можно будет найти по имени класса и имени окна (я использую "Modus RP Class" и "Modus RP"). Для упрощения примера я буду передавать строку, но можно передать любую структуру. Также для упрощения в Web приложении будет вызываться функция window.alert(). В Delphi 2010 для отправки сообщения я на форму добавил кнопку TButton и обработчик события OnClick:
procedure TForm1.Button1Click(Sender: TObject);
var
  s: String;
  copyData: TCopyDataStruct;
  targetHandle: HWND;
begin
  s := 'Текущее время: ' + DateTimeToStr(Now());
  copyData.lpData := PChar(s);
  copyData.cbData := SizeOf(Char) * (Length(s) + 1);
  targetHandle := FindWindow('Modus RP Class', 'Modus RP');
  if targetHandle <> 0 then
    SendMessage(targetHandle, WM_COPYDATA, Handle, Longint(@copyData));
end;
Теперь привожу исходный код страницы HTML:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>Call JavaScript Function</title>
    <script type="text/javascript" language="javascript">
    
    function getBrowserName() {
        var browserName = "";
        var ua = navigator.userAgent.toLowerCase();
        if ( ua.indexOf("opera") != -1 ) 
            browserName = "opera";
        else if ( ua.indexOf("msie") != -1 ) 
            browserName = "msie";
        else if ( ua.indexOf("safari") != -1 ) 
            browserName = "safari";
        else if ( ua.indexOf("mozilla") != -1 ) 
            if ( ua.indexOf("firefox") != -1 ) 
                browserName = "firefox";
            else 
                browserName = "mozilla";
        return browserName;
    };
    
    if (getBrowserName() == "firefox") {    
        try {
            netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
            Components.utils.import("resource://gre/modules/ctypes.jsm");

            var user32 = ctypes.open("user32.dll");
            var kernel32 = ctypes.open("kernel32.dll");

            const HWND_MESSAGE = -3; 
            const WM_COPYDATA = 74;

            var lpszClassName = ctypes.char.array()("Modus RP Class");    
            var lpszWindowName = ctypes.char.array()("Modus RP");
            
            const COPYDATASTRUCT = ctypes.StructType("COPYDATASTRUCT",
                [ 
                  {dwData: ctypes.uintptr_t},
                  {cbData: ctypes.uint32_t},
                  {lpData: ctypes.voidptr_t}
                ]);    

            var DefWindowProc = user32.declare("DefWindowProcA", ctypes.winapi_abi, ctypes.int,
                ctypes.voidptr_t, ctypes.int32_t, ctypes.int32_t, ctypes.int32_t);
                
            function windowProcJSCallback(hWnd, uMsg, wParam, lParam) {
              if (uMsg == WM_COPYDATA) {
                var CD = COPYDATASTRUCT.ptr(lParam).contents;
                var s = ctypes.cast(CD.lpData, ctypes.jschar.ptr).readString();
                alert(s);
                return ctypes.int(0);
              } else 
                return DefWindowProc(hWnd, uMsg, wParam, lParam);
            }
            
            var WindowProcType = ctypes.FunctionType(ctypes.stdcall_abi, ctypes.int,
                [ctypes.voidptr_t, ctypes.int32_t, ctypes.int32_t, ctypes.int32_t]).ptr;
            var WindowProcPointer = WindowProcType(windowProcJSCallback);
            var WNDCLASS = ctypes.StructType("WNDCLASS",
                [
                  { style: ctypes.uint32_t },
                  { lpfnWndProc: WindowProcType }, 
                  { cbClsExtra: ctypes.int32_t },
                  { cbWndExtra: ctypes.int32_t },
                  { hInstance: ctypes.voidptr_t },
                  { hIcon: ctypes.voidptr_t },
                  { hCursor: ctypes.voidptr_t },
                  { hbrBackground: ctypes.voidptr_t },
                  { lpszMenuName: ctypes.char.ptr },
                  { lpszClassName: ctypes.char.ptr }
                ]);
                
            var GetModuleHandle = kernel32.declare("GetModuleHandleA", ctypes.winapi_abi, 
                ctypes.voidptr_t, ctypes.char.ptr);    
            var hInstance = GetModuleHandle(null);    
                
            var wndclass = WNDCLASS();
            wndclass.hInstance = hInstance;
            wndclass.lpszClassName = lpszClassName;
            wndclass.lpfnWndProc = WindowProcPointer;   

            var RegisterClass = user32.declare("RegisterClassA", ctypes.winapi_abi, 
                ctypes.uint32_t, WNDCLASS.ptr);
            var UnregisterClass = user32.declare("UnregisterClassA", ctypes.winapi_abi, 
                ctypes.bool, ctypes.char.ptr, ctypes.voidptr_t);
            var DestroyWindow = user32.declare("DestroyWindow", ctypes.winapi_abi, 
                ctypes.bool, ctypes.voidptr_t);

            RegisterClass(wndclass.address());

            var CreateWindowEx = user32.declare("CreateWindowExA", ctypes.winapi_abi, 
                ctypes.voidptr_t, ctypes.int, ctypes.char.ptr, ctypes.char.ptr, ctypes.int, 
                ctypes.int, ctypes.int, ctypes.int, ctypes.int, ctypes.voidptr_t, 
                ctypes.voidptr_t, ctypes.voidptr_t, ctypes.voidptr_t);

            var win = CreateWindowEx(0, lpszClassName, lpszWindowName, 0, 0, 0, 0, 0, 
                ctypes.voidptr_t(HWND_MESSAGE), null, hInstance, null);    

            window.onunload = function () {
              if (win && !win.isNull()) {
                  DestroyWindow(win);
                  UnregisterClass(lpszClassName, hInstance);
                  user32.close();
                  kernel32.close();
                  win = null;
              }    
            };
        } catch(ex) {}
    }
    
    </script>
</head>

<body>
    <h1>Call JavaScript Function</h1>
</body>
При нажатии на кнопку в Delphi приложении в окне Firefox выполняется функция alert(). Результат представлен на картинке:
Перед перезагрузкой страницы или закрытием окна невидимое окно уничножается, его класс разрегистрируется, используемые библиотеки закрываются.
Чтобы всё это работало, требуется дать разрешения в Firefox. Для этого нужно открыть в нём окно about:config и изменить свойство signed.applets.codebase_principal_support = true. Затем открыть файл C:/Users/.../AppData/Roaming/Mozilla/Firefox/Profiles/...default/prefs.js и добавить строки
user_pref("capability.principal.codebase.p<n>.granted", "UniversalXPConnect");
user_pref("capability.principal.codebase.p<n>.id", "http://<localhost:8080>");
user_pref("capability.principal.codebase.p<n>.subjectName", "");
где <n> - порядковый номер и <localhost:8080> - адрес Вашего сервера.

вторник, 8 ноября 2011 г.

Создание PDF из карты GeoServer с русским текстом

Я установил printing-plugin в GeoServer из рекомендованной ссылки. Настроил config.yaml
fonts:
  - /usr/share/fonts/PingWin/PWTVerdeTST.ttf
  
        items:
        - !text
          text: 'Заголовок'
          font: PWTVerdeTST
          fontSize: 14
          fontEncoding: Identity-H
Всё работает, как положено. Но мне понадобилось передавать строку текста с клиента. Я заменил
text: 'Заголовок'
на
text: '${mapTitle}'
и вместо русских букв получил нечитаемые символы. При разработке приложения я использую smartgwt. Я нашёл решение этой задачи. В файле config.yaml я убрал
fontEncoding: Identity-H
В приложении я кодирую русский текст из UTF-8 в ANSI.
        // Печать текущего фрагмента карты
        MenuItem printItem = new MenuItem("Печать", skinImageDir + "print.png");
        printItem.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(MenuItemClickEvent event) {
                // Печать текущего фрагмента карты в PDF файл
                Map map = mainTabSet.getMap();
                String geoserverWMS = mainTabSet.getGeoserverWMS(); // http://localhost:8080/geoserver/wms
                String[] urlParts = geoserverWMS.split("(://|:|/)");

                UrlBuilder ub = new UrlBuilder();
                ub.setProtocol(urlParts[0]); //http
                ub.setHost(urlParts[1]); //localhost
                try {
                    ub.setPort(Integer.parseInt(urlParts[2])); //8080
                } catch (NumberFormatException e) {
                    e.printStackTrace(); // UrlBuilder.PORT_UNSPECIFIED
                }
                ub.setPath("/geoserver/pdf/print.pdf");
                java.util.Map params = new java.util.HashMap();
                params.put("units", "\"m\"");
                params.put("srs", "\"" + map.getProjection() + "\"");
                params.put("layout", "\"A4 portrait\"");
                params.put("dpi", "100");
                params.put("mapTitle", "\"Заголовок\"");
                params.put("comment", "\"Комментарий\"");
                params.put("baseURL", "\"" + geoserverWMS + "\"");
                params.put("opacity", "1");
                params.put("singleTile", "true");
                params.put("type", "\"WMS\"");
                params.put("format", "\"image/png\"");
                params.put("center", "["+map.getCenter().lon()+","+map.getCenter().lat()+"]");
                params.put("scale", Long.toString(Math.round(map.getScale())));
                params.put("rotation", "0");

                StringBuilder spec = new StringBuilder();
                spec.append("{");
                spec.append("\"units\":");       spec.append(params.get("units"));
                spec.append(",\"srs\":");        spec.append(params.get("srs"));
                spec.append(",\"layout\":");     spec.append(params.get("layout"));
                spec.append(",\"dpi\":");        spec.append(params.get("dpi"));
                spec.append(",\"mapTitle\":");   spec.append(params.get("mapTitle"));
                spec.append(",\"comment\":");    spec.append(params.get("comment"));
                spec.append(",\"layers\":");
                spec.append("[{\"baseURL\":");   spec.append(params.get("baseURL"));
                spec.append(",\"opacity\":");    spec.append(params.get("opacity"));
                spec.append(",\"singleTile\":"); spec.append(params.get("singleTile"));
                spec.append(",\"type\":");       spec.append(params.get("type"));
                spec.append(",\"layers\":");
                spec.append("[\"egko\"");
                // Получение списка включенных слоёв
                String[] s1 = mainTabSet.getArrayVisibledLayers();
                for (String s : s1)
                    if (s.equals("tp") || s.equals("rp") || s.equals("sp"))
                        spec.append(",\"rptp:main\"");
                    else {
                        spec.append(",\"rptp:");
                        spec.append(s);
                        spec.append("\"");
                    }
                spec.append("]");
                spec.append(",\"format\":");     spec.append(params.get("format"));
                spec.append(",\"styles\":");
                spec.append("[\"\"");
                for (String s : s1)
                    if (s.equals("collector_well"))
                        spec.append(",\"\"");
                    else {
                        spec.append(",\"");
                        if (s.equals("tp") || s.equals("rp") || s.equals("sp"))
                            spec.append("main_");
                        spec.append(s);
                        spec.append("\"");
                    }
                spec.append("]}]");
                spec.append(",\"pages\":");
                spec.append("[{\"center\":");   spec.append(params.get("center"));
                spec.append(",\"scale\":");     spec.append(params.get("scale"));
                spec.append(",\"rotation\":");  spec.append(params.get("rotation"));
                spec.append("}]}");
                String specVal = encodeToAnsi(spec.toString());
                ub.setParameter("spec", specVal);
                String url = URL.decode(ub.buildString());
                Window.open(url, "_top", "");
            }
        });
Здесь русский текст содержится в параметрах mapTitle и comment. Для кодирования русского текста я добавил функцию
    private native static String encodeToAnsi(String str)/*-{
        // UTF-8 to Ansi character encoding
        var hexChars = "0123456789ABCDEF";
        var trans = [];
        for (var i = 1040; i <= 1103; i++)
            trans[i] = "%" + hexChars.charAt((i >> 4) - 53) +
                    hexChars.charAt(i & 0xF); // А-Я,а-я
        trans[1025] = "%A8";      // Ё
        trans[1105] = "%B8";      // ё
        var ret = "";
        for (var i = 0; i < str.length; i++) {
            var code = str.charCodeAt(i);
            if (typeof trans[code] != 'undefined')
                ret += trans[code];
            else
                ret += str.charAt(i);
        }
        return ret;
    }-*/;
Теперь русские буквы правильно отображаются в PDF: