[Libreoffice-commits] online.git: Branch 'distro/collabora/milestone-5' - 27 commits - loleaflet/build loleaflet/debug loleaflet/dist loleaflet/README loleaflet/reference.html loleaflet/src loolwsd/bundled loolwsd/LOKitClient.cpp loolwsd/LOOLSession.cpp loolwsd/LOOLSession.hpp loolwsd/LOOLWSD.cpp loolwsd/LOOLWSD.hpp loolwsd/protocol.txt

Mihai Varga mihai.varga at collabora.com
Mon Oct 19 01:35:31 PDT 2015


 loleaflet/README                                             |   44 
 loleaflet/build/deps.js                                      |    5 
 loleaflet/debug/document/document_simple_example.html        |    4 
 loleaflet/dist/leaflet.css                                   |    5 
 loleaflet/reference.html                                     | 7983 +++++------
 loleaflet/src/control/Control.Dialog.js                      |   13 
 loleaflet/src/control/Control.PartsPreview.js                |    4 
 loleaflet/src/control/Parts.js                               |   76 
 loleaflet/src/control/Search.js                              |   12 
 loleaflet/src/control/Toolbar.js                             |   14 
 loleaflet/src/core/Socket.js                                 |   12 
 loleaflet/src/layer/tile/CalcTileLayer.js                    |    5 
 loleaflet/src/layer/tile/GridLayer.js                        |   13 
 loleaflet/src/layer/tile/ImpressTileLayer.js                 |   27 
 loleaflet/src/layer/tile/TileLayer.js                        |  112 
 loleaflet/src/layer/tile/WriterTileLayer.js                  |   42 
 loleaflet/src/map/Map.js                                     |    1 
 loleaflet/src/map/handler/Map.Keyboard.js                    |    3 
 loleaflet/src/map/handler/Map.Print.js                       |   62 
 loolwsd/LOKitClient.cpp                                      |    1 
 loolwsd/LOOLSession.cpp                                      |   61 
 loolwsd/LOOLSession.hpp                                      |    9 
 loolwsd/LOOLWSD.cpp                                          |   92 
 loolwsd/LOOLWSD.hpp                                          |    2 
 loolwsd/bundled/include/LibreOfficeKit/LibreOfficeKitEnums.h |   20 
 loolwsd/protocol.txt                                         |   10 
 26 files changed, 4513 insertions(+), 4119 deletions(-)

New commits:
commit 5d5f43adc815e11dcc65813b8769c5841520eb94
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Mon Oct 19 11:31:20 2015 +0300

    loleaflet: documented the print event

diff --git a/loleaflet/README b/loleaflet/README
index 3158cf2..c7baa1a 100644
--- a/loleaflet/README
+++ b/loleaflet/README
@@ -238,6 +238,10 @@ CommandValues:
             + e.commandName = '.uno:StyleAplly', etc
             + e.commandValues = a JSON mapping of all possible values for the command
 
+Print:
+    - events
+        map.on('print', function (e) {}) where
+            + e.url = file download url
 Testing
 -------
     - to simulate an editing session and to get the tile loading times
diff --git a/loleaflet/reference.html b/loleaflet/reference.html
index 9c4a2e2..07574de 100644
--- a/loleaflet/reference.html
+++ b/loleaflet/reference.html
@@ -282,7 +282,12 @@ var map = L.map('map', {
 		<td><code><span class="literal">false</span></code></td>
 		<td>Whether the document is read-only.</td>
 	</tr>
-
+	<tr>
+		<td><code><b>print</b></code></td>
+		<td><code>Boolean</code></td>
+		<td><code><span class="literal">false</span></code></td>
+		<td>Whether the print handler is active (for Chrome).</td>
+	</tr>
 	<tr>
 		<td><code><b>dragging</b></code></td>
 		<td><code>Boolean</code></td>
@@ -1700,6 +1705,21 @@ var map = L.map('map', {
 	</tr>
 </table>
 
+<h3 id="print-event">PrintEvent</h3>
+
+<table data-id='events'>
+	<tr>
+		<th class="width100">property</th>
+		<th>type</th>
+		<th>description</th>
+	</tr>
+	<tr>
+		<td><code><b>url</b></code></td>
+        <td><code>String</code></td>
+		<td>An url for the PDF exported document.</td>
+	</tr>
+</table>
+
 <h3 id="partpagerectangles-event">PartPageRectangles</h3>
 
 <table data-id='events'>
commit 77243e2a3e4bb1a07d3f65cd54ee8e742602e23e
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Mon Oct 19 11:24:30 2015 +0300

    loleaflet: allow the disabling of the print handler

diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index b14c0c0..9c722fb 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -278,7 +278,7 @@ L.TileLayer = L.GridLayer.extend({
 
 		if (command.id !== '-1') {
 			var isFirefox = typeof InstallTrigger !== 'undefined' || navigator.userAgent.search('Firefox') >= 0;
-			if (isFirefox) {
+			if (isFirefox || this._map.options.print === false) {
 				// the print dialog doesn't work well on firefox
 				this._map.fire('print', {url: url});
 			}
commit 173613af77a6d2a9b928f3350d7051abe2e71e56
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Mon Oct 19 10:44:54 2015 +0300

    loleaflet: searchAll method

diff --git a/loleaflet/README b/loleaflet/README
index 24123a7..3158cf2 100644
--- a/loleaflet/README
+++ b/loleaflet/README
@@ -62,10 +62,12 @@ API & events
 Search:
     - API:
         map.search(text, [backward])
+        map.searchAll(text, [backward])
     - events:
         map.on('search', function (e) {}) (currently only fired when no search result is found) where:
             + e.originalPhrase = the phrase that has been searched for
             + e.count = number of results
+            + e.results = [SearchResult], where SearchResult = {part: part, rectangles: [Bounds]}
 
 Zoom:
     - API:
diff --git a/loleaflet/reference.html b/loleaflet/reference.html
index 2d79223..9c4a2e2 100644
--- a/loleaflet/reference.html
+++ b/loleaflet/reference.html
@@ -1405,6 +1405,14 @@ var map = L.map('map', {
         Or backwards if specified.</td>
 	</tr>
 	<tr>
+		<td><code><b>searchAll</b>(
+			<nobr><String> <i>phrase</i>,</nobr>
+			<nobr><Boolean> <i>backward?</i> )</nobr>
+		</code></td>
+		<td><code>undefined</code></td>
+		<td>Searches for all occurences of the given phrase.</td>
+	</tr>
+	<tr>
 		<td><code><b>setPermission</b>(
 			<nobr><<a href="#documentpermission-values">DocumentPermissionValues</a>> <i>documenPermission</i>)</nobr>
 		</code></td>
@@ -1667,8 +1675,28 @@ var map = L.map('map', {
 	</tr>
 	<tr>
 		<td><code><b>results</b></code></td>
-		<td><code><a href="#bounds">Bounds[]</a></code></td>
-		<td>An array of bounds representing the selections of the search results in the document.</td>
+		<td><code><a href="#search-result">SearchResult[]</a></code></td>
+		<td>An array representing the selections of the search results in the document.</td>
+	</tr>
+</table>
+
+<h3 id="search-result">SearchResult</h3>
+
+<table data-id='values'>
+	<tr>
+		<th class="width100">property</th>
+		<th class="width100">type</th>
+		<th>description</th>
+	</tr>
+	<tr>
+		<td><code><b>part</b></code></td>
+		<td><code>Number</code></td>
+        <td>The part in which the selection lies.</td>
+	</tr>
+	<tr>
+		<td><code><b>rectangles</b></code></td>
+        <td><code><a href="#bounds">Bounds[]</a></code></td>
+        <td>Selection bounds in pixels.</td>
 	</tr>
 </table>
 
diff --git a/loleaflet/src/control/Search.js b/loleaflet/src/control/Search.js
index e9413e4..e08d1c4 100644
--- a/loleaflet/src/control/Search.js
+++ b/loleaflet/src/control/Search.js
@@ -1,8 +1,11 @@
 L.Map.include({
-	search: function (text, backward) {
+	search: function (text, backward, all) {
 		if (backward === undefined) {
 			backward = false;
 		}
+		if (all === undefined) {
+			all = 0;
+		}
 
 		var searchCmd = {
 			'SearchItem.SearchString': {
@@ -29,9 +32,16 @@ L.Map.include({
 		searchCmd['SearchItem.SearchStartPointY'] = {};
 		searchCmd['SearchItem.SearchStartPointY'].type = 'long';
 		searchCmd['SearchItem.SearchStartPointY'].value = topLeftTwips.y;
+		searchCmd['SearchItem.Command'] = {};
+		searchCmd['SearchItem.Command'].type = 'long';
+		searchCmd['SearchItem.Command'].value = all;
 		L.Socket.sendMessage('uno .uno:ExecuteSearch ' + JSON.stringify(searchCmd));
 	},
 
+	searchAll: function (text, backward) {
+		this.search(text, backward, 1);
+	},
+
 	resetSelection: function () {
 		L.Socket.sendMessage('resetselection');
 	}
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index 28d3e4e..b14c0c0 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -370,7 +370,10 @@ L.TileLayer = L.GridLayer.extend({
 		var count = obj.searchResultSelection.length;
 		var results = [];
 		for (var i = 0; i < obj.searchResultSelection.length; i++) {
-			results.push(this._twipsRectangleToPixelBounds(obj.searchResultSelection[i]));
+			results.push({
+				part: obj.searchResultSelection[i].part,
+				rectangles: this._twipsRectanglesToPixelBounds(obj.searchResultSelection[i].rectangles)
+			});
 		}
 		this._map.fire('search', {originalPhrase: originalPhrase, count: count, results: results});
 	},
commit 85fb1740ab7704ea65fac16ab3cd322ffd147339
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Mon Oct 19 10:29:53 2015 +0300

    loleaflet: [more] rectangles to pixel bounds method

diff --git a/loleaflet/src/layer/tile/GridLayer.js b/loleaflet/src/layer/tile/GridLayer.js
index 0e1fded..b11a51a 100644
--- a/loleaflet/src/layer/tile/GridLayer.js
+++ b/loleaflet/src/layer/tile/GridLayer.js
@@ -773,6 +773,19 @@ L.GridLayer = L.Layer.extend({
 				this._twipsToPixels(bottomRightTwips));
 	},
 
+	_twipsRectanglesToPixelBounds: function (strRectangles) {
+		// used when we have more rectangles
+		strRectangles = strRectangles.split(';');
+		var boundsList = [];
+		for (var i = 0; i < strRectangles.length; i++) {
+			var bounds = this._twipsRectangleToPixelBounds(strRectangles[i]);
+			if (bounds) {
+				boundsList.push(bounds);
+			}
+		}
+		return boundsList;
+	},
+
 	_noTilesToLoad: function () {
 		for (var key in this._tiles) {
 			if (!this._tiles[key].loaded) { return false; }
commit 289eb3c67318f71c89ba18475b316c13b7211b00
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Fri Oct 16 19:45:57 2015 +0300

    loolwsd: allow the specification of the mime type

diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index e198203..4b2e9c8 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -220,8 +220,8 @@ public:
 
         if(!(request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0))
         {
-            StringTokenizer tokens(request.getURI(), "/");
-            if (tokens.count() == 2 && tokens[1] == "convert-to")
+            StringTokenizer tokens(request.getURI(), "/?");
+            if (tokens.count() >= 2 && tokens[1] == "convert-to")
             {
                 std::vector<char> buffer;
                 ConvertToPartHandler handler(buffer);
@@ -239,7 +239,7 @@ public:
                 response.setContentLength(0);
                 response.send();
             }
-            else if (tokens.count() == 4)
+            else if (tokens.count() >= 4)
             {
                 // The user might request a file to download
                 std::string dirPath = LOOLWSD::childRoot + "/" + tokens[1] + LOOLSession::jailDocumentURL + "/" + tokens[2];
@@ -249,7 +249,11 @@ public:
                 if (file.exists())
                 {
                     response.set("Access-Control-Allow-Origin", "*");
-                    response.sendFile(filePath, "application/octet-stream");
+                    Poco::Net::HTMLForm form(request);
+                    std::string mimeType = "application/octet-stream";
+                    if (form.has("mime_type"))
+                        mimeType = form.get("mime_type");
+                    response.sendFile(filePath, mimeType);
                     File dir(dirPath);
                     dir.remove(true);
                 }
commit d89fc44554fa2f97743f836b4293362d3b4396c5
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Fri Oct 16 19:27:13 2015 +0300

    drop 'part' from downloadas protocol as it is no longer needed
    
    Conflicts:
    	loleaflet/src/layer/tile/TileLayer.js
    	loleaflet/src/map/handler/Map.Keyboard.js
    	loleaflet/src/map/handler/Map.Mouse.js

diff --git a/loleaflet/src/control/Toolbar.js b/loleaflet/src/control/Toolbar.js
index 0e276f7..c319926 100644
--- a/loleaflet/src/control/Toolbar.js
+++ b/loleaflet/src/control/Toolbar.js
@@ -41,7 +41,6 @@ L.Map.include({
 		L.Socket.sendMessage('downloadas ' +
 			'name=' + name + ' ' +
 			'id=-1 ' + // not a special download
-			'part=-1 ' + // we don't want to export just a single part
 			'format=' + format + ' ' +
 			'options=' + options);
 	},
diff --git a/loleaflet/src/map/handler/Map.Keyboard.js b/loleaflet/src/map/handler/Map.Keyboard.js
index 37820b8..c674240 100644
--- a/loleaflet/src/map/handler/Map.Keyboard.js
+++ b/loleaflet/src/map/handler/Map.Keyboard.js
@@ -284,7 +284,7 @@ L.Map.Keyboard = L.Handler.extend({
 				L.Socket.sendMessage('uno .uno:LeftPara');
 				break;
 			case 80: // p
-				L.Socket.sendMessage('downloadas name=print.pdf id=print part=-1 format=pdf options=');
+				L.Socket.sendMessage('downloadas name=print.pdf id=print format=pdf options=');
 				break;
 			case 82: // r
 				L.Socket.sendMessage('uno .uno:RightPara');
diff --git a/loolwsd/LOOLSession.cpp b/loolwsd/LOOLSession.cpp
index 53a0873..c731bd9 100644
--- a/loolwsd/LOOLSession.cpp
+++ b/loolwsd/LOOLSession.cpp
@@ -999,22 +999,20 @@ void ChildProcessSession::sendTile(const char *buffer, int length, StringTokeniz
 bool ChildProcessSession::downloadAs(const char *buffer, int length, StringTokenizer& tokens)
 {
     std::string name, id, format, filterOptions;
-    int part;
 
-    if (tokens.count() < 6 ||
+    if (tokens.count() < 5 ||
         !getTokenString(tokens[1], "name", name) ||
-        !getTokenString(tokens[2], "id", id),
-        !getTokenInteger(tokens[3], "part", part))
+        !getTokenString(tokens[2], "id", id))
     {
         sendTextFrame("error: cmd=downloadas kind=syntax");
         return false;
     }
 
-    getTokenString(tokens[4], "format", format);
+    getTokenString(tokens[3], "format", format);
 
-    if (getTokenString(tokens[5], "options", filterOptions)) {
-        if (tokens.count() > 6) {
-            filterOptions += Poco::cat(std::string(" "), tokens.begin() + 6, tokens.end());
+    if (getTokenString(tokens[4], "options", filterOptions)) {
+        if (tokens.count() > 5) {
+            filterOptions += Poco::cat(std::string(" "), tokens.begin() + 5, tokens.end());
         }
     }
 
@@ -1032,10 +1030,6 @@ bool ChildProcessSession::downloadAs(const char *buffer, int length, StringToken
     } while (file->exists());
     delete file;
 
-    if (part != -1) {
-        // we need this to export a slide to svg
-        _loKitDocument->pClass->setPart(_loKitDocument, part);
-    }
     _loKitDocument->pClass->saveAs(_loKitDocument, url.c_str(),
             format.size() == 0 ? NULL :format.c_str(),
             filterOptions.size() == 0 ? NULL : filterOptions.c_str());
diff --git a/loolwsd/protocol.txt b/loolwsd/protocol.txt
index dbe89fb..bc7c453 100644
--- a/loolwsd/protocol.txt
+++ b/loolwsd/protocol.txt
@@ -17,11 +17,10 @@ canceltiles
     dropped and will not be handled. There is no guarantee of exactly
     which tile: messages might still be sent back to the client.
 
-downloadas downloadas name=<fileName> id=<id> part=<part> format=<document format> options=<SkipImages, etc>
+downloadas downloadas name=<fileName> id=<id> format=<document format> options=<SkipImages, etc>
 
     Exports the current document to the desired format and returns a download URL
     The id identifies the request on the client.
-    The part parameter is used to export a specific part, for example when we need to export a slide to svg
 
 gettextselection mimetype=<mimeType>
 
commit f67086f2ef98288b494a7568b12a33223cf168b9
Author: Miklos Vajna <vmiklos at collabora.co.uk>
Date:   Fri Oct 16 17:38:24 2015 +0200

    loolwsd: convert-to handler skeleton

diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index 6b13c8b..e198203 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -67,6 +67,7 @@ DEALINGS IN THE SOFTWARE.
 
 #include <Poco/Exception.h>
 #include <Poco/File.h>
+#include <Poco/Net/HTMLForm.h>
 #include <Poco/Net/HTTPClientSession.h>
 #include <Poco/Net/HTTPRequest.h>
 #include <Poco/Net/HTTPRequestHandler.h>
@@ -76,7 +77,9 @@ DEALINGS IN THE SOFTWARE.
 #include <Poco/Net/HTTPServerParams.h>
 #include <Poco/Net/HTTPServerRequest.h>
 #include <Poco/Net/HTTPServerResponse.h>
+#include <Poco/Net/MessageHeader.h>
 #include <Poco/Net/NetException.h>
+#include <Poco/Net/PartHandler.h>
 #include <Poco/Net/ServerSocket.h>
 #include <Poco/Net/SocketAddress.h>
 #include <Poco/Net/WebSocket.h>
@@ -176,6 +179,24 @@ private:
     tsqueue<std::string>& _queue;
 };
 
+/// Handles the filename part of the convert-to POST request payload.
+class ConvertToPartHandler : public Poco::Net::PartHandler
+{
+    std::vector<char>& _buffer;
+public:
+    ConvertToPartHandler(std::vector<char>& buffer)
+        : _buffer(buffer)
+    {
+    }
+
+    virtual void handlePart(const Poco::Net::MessageHeader& /*header*/, std::istream& stream) override
+    {
+        char c;
+        while (stream.get(c))
+            _buffer.push_back(c);
+    }
+};
+
 class RequestHandler: public HTTPRequestHandler
     /// Handle a WebSocket connection or a simple HTTP request.
 {
@@ -199,10 +220,28 @@ public:
 
         if(!(request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0))
         {
-            // The user might request a file to download
             StringTokenizer tokens(request.getURI(), "/");
-            if (tokens.count() == 4)
+            if (tokens.count() == 2 && tokens[1] == "convert-to")
+            {
+                std::vector<char> buffer;
+                ConvertToPartHandler handler(buffer);
+                Poco::Net::HTMLForm form(request, request.stream(), handler);
+                std::string format;
+                if (form.has("format"))
+                    format = form.get("format");
+
+                if (!format.empty() && !buffer.empty())
+                {
+                    // TODO implement actual conversion
+                }
+
+                response.setStatus(HTTPResponse::HTTP_OK);
+                response.setContentLength(0);
+                response.send();
+            }
+            else if (tokens.count() == 4)
             {
+                // The user might request a file to download
                 std::string dirPath = LOOLWSD::childRoot + "/" + tokens[1] + LOOLSession::jailDocumentURL + "/" + tokens[2];
                 std::string filePath = dirPath + "/" + tokens[3];
                 std::cout << Util::logPrefix() << "HTTP request for: " << filePath << std::endl;
commit d2d7be14125aedff2f2aeeef907a1e7e26d3157c
Author: Miklos Vajna <vmiklos at collabora.co.uk>
Date:   Fri Oct 16 15:47:39 2015 +0200

    loolwsd: invert this condition, so it's possible to add other cases

diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index 3cc510c..6b13c8b 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -201,26 +201,29 @@ public:
         {
             // The user might request a file to download
             StringTokenizer tokens(request.getURI(), "/");
-            if (tokens.count() != 4)
+            if (tokens.count() == 4)
             {
-                response.setStatus(HTTPResponse::HTTP_BAD_REQUEST);
-                response.setContentLength(0);
-                response.send();
-            }
-            std::string dirPath = LOOLWSD::childRoot + "/" + tokens[1] + LOOLSession::jailDocumentURL + "/" + tokens[2];
-            std::string filePath = dirPath + "/" + tokens[3];
-            std::cout << Util::logPrefix() << "HTTP request for: " << filePath << std::endl;
-            File file(filePath);
-            if (file.exists())
-            {
-                response.set("Access-Control-Allow-Origin", "*");
-                response.sendFile(filePath, "application/octet-stream");
-                File dir(dirPath);
-                dir.remove(true);
+                std::string dirPath = LOOLWSD::childRoot + "/" + tokens[1] + LOOLSession::jailDocumentURL + "/" + tokens[2];
+                std::string filePath = dirPath + "/" + tokens[3];
+                std::cout << Util::logPrefix() << "HTTP request for: " << filePath << std::endl;
+                File file(filePath);
+                if (file.exists())
+                {
+                    response.set("Access-Control-Allow-Origin", "*");
+                    response.sendFile(filePath, "application/octet-stream");
+                    File dir(dirPath);
+                    dir.remove(true);
+                }
+                else
+                {
+                    response.setStatus(HTTPResponse::HTTP_NOT_FOUND);
+                    response.setContentLength(0);
+                    response.send();
+                }
             }
             else
             {
-                response.setStatus(HTTPResponse::HTTP_NOT_FOUND);
+                response.setStatus(HTTPResponse::HTTP_BAD_REQUEST);
                 response.setContentLength(0);
                 response.send();
             }
commit e49d3c022309e151b916d3605f9916fa2b6bab2b
Author: Miklos Vajna <vmiklos at collabora.co.uk>
Date:   Fri Oct 16 15:23:49 2015 +0200

    loolwsd: allow frame size of 200K
    
    I got:
    
    WebSocketException: Insufficient buffer for payload size 113579
    
    So double the allocated size: that seems to help.

diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index ac36945..3cc510c 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -262,7 +262,7 @@ public:
                 ws->setReceiveTimeout(0);
                 do
                 {
-                    char buffer[100000];
+                    char buffer[200000];
                     n = ws->receiveFrame(buffer, sizeof(buffer), flags);
 
                     if (n > 0 && (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE)
@@ -316,7 +316,7 @@ public:
             }
             catch (WebSocketException& exc)
             {
-                Application::instance().logger().error(Util::logPrefix() + "WebSocketException: " + exc.message());
+                Application::instance().logger().error(Util::logPrefix() + "RequestHandler::handleRequest(), WebSocketException: " + exc.message());
                 switch (exc.code())
                 {
                 case WebSocket::WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION:
@@ -384,7 +384,7 @@ public:
         {
             do
             {
-                char buffer[100000];
+                char buffer[200000];
                 n = _ws.receiveFrame(buffer, sizeof(buffer), flags);
 
                 if (n > 0 && (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE)
@@ -399,7 +399,7 @@ public:
         }
         catch (WebSocketException& exc)
         {
-            Application::instance().logger().error(Util::logPrefix() + "WebSocketException: " + exc.message());
+            Application::instance().logger().error(Util::logPrefix() + "TestOutput::run(), WebSocketException: " + exc.message());
             _ws.close();
         }
     }
commit 974b465bbf093361aae0c886ba9d591eb4b5ca5d
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Thu Oct 15 20:27:46 2015 +0300

    loleaflet: don't scroll based on the selection, but based on the cursor

diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index 6d2b73e..28d3e4e 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -417,16 +417,6 @@ L.TileLayer = L.GridLayer.extend({
 				selectionCenter = selectionCenter.add(topLeftTwips);
 				selectionCenter = selectionCenter.add(offset.divideBy(2));
 			}
-			// average of all rectangles' centers
-			selectionCenter = selectionCenter.divideBy(strTwips.length / 4);
-			selectionCenter = this._twipsToLatLng(selectionCenter);
-			if (!this._map.getBounds().contains(selectionCenter)) {
-				var center = this._map.project(selectionCenter);
-				center = center.subtract(this._map.getSize().divideBy(2));
-				center.x = Math.round(center.x < 0 ? 0 : center.x);
-				center.y = Math.round(center.y < 0 ? 0 : center.y);
-				this._map.fire('scrollto', {x: center.x, y: center.y});
-			}
 
 			var polygons = L.PolyUtil.rectanglesToPolygons(rectangles, this);
 			for (i = 0; i < polygons.length; i++) {
commit 8a38c790bd65885a9f3f3f68997d105314a54ac3
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Thu Oct 15 19:17:18 2015 +0300

    loleaflet: map.getPageSizes - returns the size of each page
    
    In twips and pixels

diff --git a/loleaflet/README b/loleaflet/README
index f961c79..24123a7 100644
--- a/loleaflet/README
+++ b/loleaflet/README
@@ -203,6 +203,8 @@ Writer pages:
             + options = {autoUpdate: true} - automatically updates the previews
         map.removePreviewUpdate(id)
             + id = the preview's id
+        map.getPageSizes()
+            + returns {twips: [Bounds], pixels: [Bounds]}
 
     - events
         map.on('pagenumberchanged', function (e) {}) where:
diff --git a/loleaflet/reference.html b/loleaflet/reference.html
index 59a5605..2d79223 100644
--- a/loleaflet/reference.html
+++ b/loleaflet/reference.html
@@ -1422,6 +1422,12 @@ var map = L.map('map', {
 		<td>Returns the document type.</td>
 	</tr>
 	<tr>
+        <td><code><b>getPageSizes</b>()</code></td>
+        <td><code><nobr>{twips: <a href="#bounds">[Bounds]</a>,<br>
+                pixels: <a href="#bounds">[Bounds]</a>}</nobr></code></td>
+		<td>Returns an object describing the size of each page in twips and pixels.</td>
+	</tr>
+	<tr>
         <td><code><b>scroll</b>(
 			<nobr><Number><i>x</i>,</nobr>
 			<nobr><Number><i>y</i>,</nobr>
diff --git a/loleaflet/src/control/Parts.js b/loleaflet/src/control/Parts.js
index f101995..88585bf 100644
--- a/loleaflet/src/control/Parts.js
+++ b/loleaflet/src/control/Parts.js
@@ -164,11 +164,10 @@ L.Map.include({
 		return this._docLayer._docPixelSize;
 	},
 
-	getPageSize: function (page) {
-		if (this._docLayer._partPageRectanglesPixels && this._docLayer._partPageRectanglesPixels.length > page) {
-			return this._docLayer._partPageRectanglesPixels[page];
-		}
-		return null;
+	getPageSizes: function () {
+		return {
+			twips: this._docLayer._partPageRectanglesTwips,
+			pixels: this._docLayer._partPageRectanglesPixels};
 	},
 
 	getDocType: function () {
commit 21a11ea66bfc6795e6becf683c29afa583bfeb43
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Thu Oct 15 18:56:19 2015 +0300

    loleaflet: mention reference.html in README

diff --git a/loleaflet/README b/loleaflet/README
index 6909915..f961c79 100644
--- a/loleaflet/README
+++ b/loleaflet/README
@@ -55,6 +55,10 @@ check the loolwsd console for the debugging output.
 API & events
 ------------
 
+######################################################################
+# See /loleaflet/reference.html for a better formated documentation. #
+######################################################################
+
 Search:
     - API:
         map.search(text, [backward])
commit 59ca121118c1abcee71a6edf7b67b2e86e634b70
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Thu Oct 15 18:54:59 2015 +0300

    loleaflet: mention that some parameters are in twips

diff --git a/loleaflet/reference.html b/loleaflet/reference.html
index 67fd51f..59a5605 100644
--- a/loleaflet/reference.html
+++ b/loleaflet/reference.html
@@ -1486,10 +1486,10 @@ var map = L.map('map', {
 			<Number><i>part</i>,<br>
 			<Number><i>width</i>,<br>
 			<Number><i>height</i>,<br>
-			<Number><i>tilePosX</i>,<br>
-			<Number><i>tilePosY</i>,<br>
-			<Number><i>tileWidth</i>,<br>
-			<Number><i>tileHeight</i>,<br>
+			<Twips><i>tilePosX</i>,<br>
+			<Twips><i>tilePosY</i>,<br>
+			<Twips><i>tileWidth</i>,<br>
+			<Twips><i>tileHeight</i>,<br>
             <nobr><<a href="#getpreview-options">PreviewOptions</a>><i>options?</i>)</nobr>
 		</code></td>
 		<td><code>undefined</code></td>
commit 5590375c4578edcc1bcb28ab87e69dba76e2b6d0
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Thu Oct 15 18:46:59 2015 +0300

    loleaflet: we now have getPreview and getCustomPreview methods
    
    getPreview can be used to request a preview of a page or a part while
    getCustomPreview can be used to get a preview of a user defined section
    of the document

diff --git a/loleaflet/README b/loleaflet/README
index 2011c33..6909915 100644
--- a/loleaflet/README
+++ b/loleaflet/README
@@ -93,13 +93,20 @@ Buttons like Bold, Italic, Strike through etc.
 Parts (like slides in presentation, or sheets in spreadsheets):
     - API:
         map.setPart('next' | 'prev' | partNumber)
-        map.getPartPreview(id, part, maxWidth, maxHeight, [options]) where:
-            + id = the ID of the request so that the response can be identified
-            + maxWidth / maxHeight are the desired dimensions of the preview, a smaller
-              image might be returned in order to keep the original ratio of the document
-            + options = {autoUpdate: true} - automatically updates the previews
         map.getNumberOfParts()
         map.getCurrentPartNumber()
+        map.getPreview(id, index, maxWidth, maxHeight, [options])
+            + id = the ID of the request so that the response can be identified
+            + index = the part / page 's number
+            + maxWidth / maxHeight = max dimensions so that the ratio is preserved
+            + options = {autoUpdate: true} - automatically updates the previews
+        map.getCustomPreview(id, part, width, height, tilePosX, tilePosY, tileWidth, tileHeight, [options])
+            + id = the ID of the request so that the response can be identified
+            + part = the part containing the desired preview
+            + width / height = the preview's size in pixels
+            + tilePosX / tilePosY = the rectangles's starting position in twips
+            + tileWidth / tileHeight = the rectangle's dimension in twips
+            + options = {autoUpdate: true} - automatically updates the previews
         map.removePreviewUpdate(id)
             + id = the preview's id
 
@@ -178,11 +185,17 @@ Writer pages:
         map.goToPage(page)
         map.getNumberOfPages()
         map.getCurrentPageNumber()
-        map.getDocPreview(id, maxWidth, maxHeight, x, y, width, height, [options])
+        map.getPreview(id, index, maxWidth, maxHeight, [options])
+            + id = the ID of the request so that the response can be identified
+            + index = the part / page 's number
+            + maxWidth / maxHeight = max dimensions so that the ratio is preserved
+            + options = {autoUpdate: true} - automatically updates the previews
+        map.getCustomPreview(id, part, width, height, tilePosX, tilePosY, tileWidth, tileHeight, [options])
             + id = the ID of the request so that the response can be identified
-            + maxWidth / maxHeight are the desired dimensions of the preview, a smaller
-              image might be returned in order to keep the original ratio of the document
-            + x/y = starting position, where to get the preview from
+            + part = the part containing the desired preview
+            + width / height = the preview's size in pixels
+            + tilePosX / tilePosY = the rectangles's starting position in twips
+            + tileWidth / tileHeight = the rectangle's dimension in twips
             + options = {autoUpdate: true} - automatically updates the previews
         map.removePreviewUpdate(id)
             + id = the preview's id
diff --git a/loleaflet/src/control/Control.PartsPreview.js b/loleaflet/src/control/Control.PartsPreview.js
index 4112d01..5439b49 100644
--- a/loleaflet/src/control/Control.PartsPreview.js
+++ b/loleaflet/src/control/Control.PartsPreview.js
@@ -46,7 +46,7 @@ L.Control.PartsPreview = L.Control.extend({
 					.on(img, 'click', L.DomEvent.stop)
 					.on(img, 'click', this._setPart, this)
 					.on(img, 'click', this._refocusOnMap, this);
-				this._map.getPartPreview(i, i, 180, 180, {autoUpdate: this.options.autoUpdate});
+				this._map.getPreview(i, i, 180, 180, {autoUpdate: this.options.autoUpdate});
 			}
 			this._previewInitialized = true;
 		}
@@ -61,7 +61,7 @@ L.Control.PartsPreview = L.Control.extend({
 
 	_updatePart: function (e) {
 		if (e.docType === 'presentation') {
-			this._map.getPartPreview(e.part, e.part, 180, 180, {autoUpdate: this.options.autoUpdate});
+			this._map.getPreview(e.part, e.part, 180, 180, {autoUpdate: this.options.autoUpdate});
 		}
 	},
 
diff --git a/loleaflet/src/control/Parts.js b/loleaflet/src/control/Parts.js
index d710d47..f101995 100644
--- a/loleaflet/src/control/Parts.js
+++ b/loleaflet/src/control/Parts.js
@@ -40,64 +40,66 @@ L.Map.include({
 		}
 	},
 
-	getPartPreview: function (id, part, maxWidth, maxHeight, options) {
+	getPreview: function (id, index, maxWidth, maxHeight, options) {
 		if (!this._docPreviews) {
 			this._docPreviews = {};
 		}
 		var autoUpdate = options ? options.autoUpdate : false;
-		this._docPreviews[id] = {id: id, part: part, maxWidth: maxWidth, maxHeight: maxHeight, autoUpdate: autoUpdate};
+		this._docPreviews[id] = {id: id, index: index, maxWidth: maxWidth, maxHeight: maxHeight, autoUpdate: autoUpdate};
 
 		var docLayer = this._docLayer;
-		var docRatio = docLayer._docWidthTwips / docLayer._docHeightTwips;
+		if (docLayer._docType === 'text') {
+			if (index >= docLayer._partPageRectanglesTwips.length) {
+				return;
+			}
+			var part = 0;
+			var tilePosX = docLayer._partPageRectanglesTwips[index].min.x;
+			var tilePosY = docLayer._partPageRectanglesTwips[index].min.y;
+			var tileWidth = docLayer._partPageRectanglesTwips[index].max.x - tilePosX;
+			var tileHeight = docLayer._partPageRectanglesTwips[index].max.y - tilePosY;
+		}
+		else {
+			part = index;
+			tilePosX = 0;
+			tilePosY = 0;
+			tileWidth = docLayer._docWidthTwips;
+			tileHeight = docLayer._docHeightTwips;
+		}
+		var docRatio = tileWidth / tileHeight;
 		var imgRatio = maxWidth / maxHeight;
 		// fit into the given rectangle while maintaining the ratio
 		if (imgRatio > docRatio) {
-			maxWidth = Math.round(docLayer._docWidthTwips * maxHeight / docLayer._docHeightTwips);
+			maxWidth = Math.round(tileWidth * maxHeight / tileHeight);
 		}
 		else {
-			maxHeight = Math.round(docLayer._docHeightTwips * maxWidth / docLayer._docWidthTwips);
+			maxHeight = Math.round(tileHeight * maxWidth / tileWidth);
 		}
 		L.Socket.sendMessage('tile ' +
 							'part=' + part + ' ' +
 							'width=' + maxWidth + ' ' +
 							'height=' + maxHeight + ' ' +
-							'tileposx=0 tileposy=0 ' +
-							'tilewidth=' + docLayer._docWidthTwips + ' ' +
-							'tileheight=' + docLayer._docHeightTwips + ' ' +
+							'tileposx=' + tilePosX + ' ' +
+							'tileposy=' + tilePosY + ' ' +
+							'tilewidth=' + tileWidth + ' ' +
+							'tileheight=' + tileHeight + ' ' +
 							'id=' + id);
 	},
 
-	getDocPreview: function (id, maxWidth, maxHeight, x, y, width, height, options) {
+	getCustomPreview: function (id, part, width, height, tilePosX, tilePosY, tileWidth, tileHeight, options) {
 		if (!this._docPreviews) {
 			this._docPreviews = {};
 		}
 		var autoUpdate = options ? options.autoUpdate : false;
-		this._docPreviews[id] = {id: id, maxWidth: maxWidth, maxHeight: maxHeight, x: x, y: y,
-			width: width, height: height, autoUpdate: autoUpdate};
-
-		var docLayer = this._docLayer;
-		var docRatio = width / height;
-		var imgRatio = maxWidth / maxHeight;
-		// fit into the given rectangle while maintaining the ratio
-		if (imgRatio > docRatio) {
-			maxWidth = Math.round(width * maxHeight / height);
-		}
-		else {
-			maxHeight = Math.round(height * maxWidth / width);
-		}
-		x = Math.round(x / docLayer._tileSize * docLayer._tileWidthTwips);
-		width = Math.round(width / docLayer._tileSize * docLayer._tileWidthTwips);
-		y = Math.round(y / docLayer._tileSize * docLayer._tileHeightTwips);
-		height = Math.round(height / docLayer._tileSize * docLayer._tileHeightTwips);
-
+		this._docPreviews[id] = {id: id, part: part, width: width, height: height, tilePosX: tilePosX,
+			tilePosY: tilePosY, tileWidth: tileWidth, tileHeight: tileHeight, autoUpdate: autoUpdate};
 		L.Socket.sendMessage('tile ' +
-							'part=0 ' +
-							'width=' + maxWidth + ' ' +
-							'height=' + maxHeight + ' ' +
-							'tileposx=' + x + ' ' +
-							'tileposy=' + y + ' ' +
-							'tilewidth=' + width + ' ' +
-							'tileheight=' + height + ' ' +
+							'part=' + part + ' ' +
+							'width=' + width + ' ' +
+							'height=' + height + ' ' +
+							'tileposx=' + tilePosX + ' ' +
+							'tileposy=' + tilePosY + ' ' +
+							'tilewidth=' + tileWidth + ' ' +
+							'tileheight=' + tileHeight + ' ' +
 							'id=' + id);
 	},
 
@@ -162,6 +164,13 @@ L.Map.include({
 		return this._docLayer._docPixelSize;
 	},
 
+	getPageSize: function (page) {
+		if (this._docLayer._partPageRectanglesPixels && this._docLayer._partPageRectanglesPixels.length > page) {
+			return this._docLayer._partPageRectanglesPixels[page];
+		}
+		return null;
+	},
+
 	getDocType: function () {
 		return this._docLayer._docType;
 	}
diff --git a/loleaflet/src/layer/tile/CalcTileLayer.js b/loleaflet/src/layer/tile/CalcTileLayer.js
index df1fe80..4e2963e 100644
--- a/loleaflet/src/layer/tile/CalcTileLayer.js
+++ b/loleaflet/src/layer/tile/CalcTileLayer.js
@@ -84,6 +84,11 @@ L.CalcTileLayer = L.TileLayer.extend({
 				delete this._tileCache[key];
 			}
 		}
+
+		this._previewInvalidations.push(invalidBounds);
+		// 1s after the last invalidation, update the preview
+		clearTimeout(this._previewInvalidator);
+		this._previewInvalidator = setTimeout(L.bind(this._invalidatePreviews, this), this.options.previewInvalidationTimeout);
 	},
 
 	_onSetPartMsg: function (textMsg) {
diff --git a/loleaflet/src/layer/tile/ImpressTileLayer.js b/loleaflet/src/layer/tile/ImpressTileLayer.js
index dfb00f3..0868c43 100644
--- a/loleaflet/src/layer/tile/ImpressTileLayer.js
+++ b/loleaflet/src/layer/tile/ImpressTileLayer.js
@@ -87,9 +87,10 @@ L.ImpressTileLayer = L.TileLayer.extend({
 			this._map.fire('updatepart', {part: command.part, docType: this._docType});
 		}
 
+		this._previewInvalidations.push(invalidBounds);
 		// 1s after the last invalidation, update the preview
 		clearTimeout(this._previewInvalidator);
-		this._previewInvalidator = setTimeout(L.bind(this._invalidatePreview, this), 1000);
+		this._previewInvalidator = setTimeout(L.bind(this._invalidatePreviews, this), this.options.previewInvalidationTimeout);
 	},
 
 	_onSetPartMsg: function (textMsg) {
@@ -128,29 +129,5 @@ L.ImpressTileLayer = L.TileLayer.extend({
 				this._preFetchBorder = null;
 			}
 		}
-	},
-
-	_invalidatePreview: function () {
-		if (this._map._docPreviews) {
-			// invalidate part previews
-			for (var key in this._map._docPreviews) {
-				var preview = this._map._docPreviews[key];
-				if (preview.part === this._selectedPart ||
-					(preview.part === this._prevSelectedPart && this._prevSelectedPartNeedsUpdate)) {
-					// if the current part needs its preview updated OR
-					// the part has been changed and we need to update the previous part preview
-					if (preview.part === this._prevSelectedPart) {
-						this._prevSelectedPartNeedsUpdate = false;
-					}
-					if (preview.autoUpdate) {
-						this._map.getPartPreview(preview.id, preview.part, preview.maxWidth, preview.maxHeight,
-								{autoUpdate: true});
-					}
-					else {
-						this._map.fire('invalidatepreview', {id: preview.id});
-					}
-				}
-			}
-		}
 	}
 });
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index 95de103..6d2b73e 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -25,7 +25,8 @@ L.TileLayer = L.GridLayer.extend({
 		zoomReverse: false,
 		detectRetina: false,
 		crossOrigin: false,
-		preFetchOtherParts: false
+		preFetchOtherParts: false,
+		previewInvalidationTimeout: 1000
 	},
 
 	initialize: function (url, options) {
@@ -85,6 +86,7 @@ L.TileLayer = L.GridLayer.extend({
 		this._emptyTilesCount = 0;
 		this._msgQueue = [];
 		this._toolbarCommandValues = {};
+		this._previewInvalidations = [];
 	},
 
     onAdd: function (map) {
@@ -806,6 +808,74 @@ L.TileLayer = L.GridLayer.extend({
 				twipsRectangles: this._partPageRectanglesTwips
 			});
 		}
+	},
+
+    _invalidatePreviews: function () {
+		if (this._map._docPreviews && this._previewInvalidations.length > 0) {
+			var toInvalidate = {};
+			for (var i = 0; i < this._previewInvalidations.length; i++) {
+				var invalidBounds = this._previewInvalidations[i];
+				for (var key in this._map._docPreviews) {
+					// find preview tiles that need to be updated and add them in a set
+					var preview = this._map._docPreviews[key];
+					if (preview.index >= 0 && this._docType === 'text') {
+						// we have a preview for a page
+						if (this._partPageRectanglesTwips.length > preview.index &&
+								invalidBounds.intersects(this._partPageRectanglesTwips[preview.index])) {
+							toInvalidate[key] = true;
+						}
+					}
+					else if (preview.index >= 0) {
+						// we have a preview for a part
+						if (preview.index === this._selectedPart ||
+								(preview.index === this._prevSelectedPart && this._prevSelectedPartNeedsUpdate)) {
+							// if the current part needs its preview updated OR
+							// the part has been changed and we need to update the previous part preview
+							if (preview.index === this._prevSelectedPart) {
+								this._prevSelectedPartNeedsUpdate = false;
+							}
+							toInvalidate[key] = true;
+						}
+					}
+					else {
+						// we have a custom preview
+						var bounds = new L.Bounds(
+								new L.Point(preview.tilePosX, preview.tilePosY),
+								new L.Point(preview.tilePosX + preview.tileWidth, preview.tilePosY + preview.tileHeight));
+						if ((preview.part === this._selectedPart ||
+								(preview.part === this._prevSelectedPart && this._prevSelectedPartNeedsUpdate)) &&
+								invalidBounds.intersects(bounds)) {
+							// if the current part needs its preview updated OR
+							// the part has been changed and we need to update the previous part preview
+							if (preview.index === this._prevSelectedPart) {
+								this._prevSelectedPartNeedsUpdate = false;
+							}
+							toInvalidate[key] = true;
+						}
+
+					}
+				}
+
+			}
+
+			for (key in toInvalidate) {
+				// update invalid preview tiles
+				preview = this._map._docPreviews[key];
+				if (preview.autoUpdate) {
+					if (preview.index >= 0) {
+						this._map.getPreview(preview.id, preview.index, preview.maxWidth, preview.maxHeight, {autoUpdate: true});
+					}
+					else {
+						this._map.getCustomPreview(preview.id, preview.part, preview.width, preview.height, preview.tilePosX,
+								preview.tilePosY, preview.tileWidth, preview.tileHeight, {autoUpdate: true});
+					}
+				}
+				else {
+					this._map.fire('invalidatepreview', {id: preview.id});
+				}
+			}
+		}
+		this._previewInvalidations = [];
 	}
 });
 
diff --git a/loleaflet/src/layer/tile/WriterTileLayer.js b/loleaflet/src/layer/tile/WriterTileLayer.js
index 5686727..dd04890 100644
--- a/loleaflet/src/layer/tile/WriterTileLayer.js
+++ b/loleaflet/src/layer/tile/WriterTileLayer.js
@@ -79,13 +79,11 @@ L.WriterTileLayer = L.TileLayer.extend({
 				delete this._tileCache[key];
 			}
 		}
-		if (!this._previewInvalidations) {
-			this._previewInvalidations = [];
-		}
+
 		this._previewInvalidations.push(invalidBounds);
 		// 1s after the last invalidation, update the preview
 		clearTimeout(this._previewInvalidator);
-		this._previewInvalidator = setTimeout(L.bind(this._invalidatePreview, this), 1000);
+		this._previewInvalidator = setTimeout(L.bind(this._invalidatePreviews, this), this.options.previewInvalidationTimeout);
 	},
 
 	_onSetPartMsg: function (textMsg) {
@@ -118,41 +116,5 @@ L.WriterTileLayer = L.TileLayer.extend({
 			this._resetPreFetching(true);
 			this._update();
 		}
-	},
-
-	_invalidatePreview: function () {
-		// invalidate writer page previews
-		if (this._map._docPreviews && this._previewInvalidations) {
-			var toInvalidate = {};
-			for (var i = 0; i < this._previewInvalidations.length; i++) {
-				var invalidBounds = this._previewInvalidations[i];
-				var invalidPixBounds = new L.Bounds(
-						invalidBounds.min.divideBy(this.options.tileWidthTwips).multiplyBy(this._tileSize),
-						invalidBounds.max.divideBy(this.options.tileWidthTwips).multiplyBy(this._tileSize));
-
-				for (var key in this._map._docPreviews) {
-					// find preview tiles that need to be updated and add them in a set
-					var preview = this._map._docPreviews[key];
-					var bounds = new L.Bounds(new L.Point(preview.x, preview.y),
-											  new L.Point(preview.x + preview.width, preview.y + preview.height));
-					if (invalidPixBounds.intersects(bounds)) {
-						toInvalidate[key] = true;
-					}
-				}
-
-				for (key in toInvalidate) {
-					// update invalid preview tiles
-					preview = this._map._docPreviews[key];
-					if (preview.autoUpdate) {
-						this._map.getDocPreview(preview.id, preview.maxWidth, preview.maxHeight,
-								preview.x, preview.y, preview.width, preview.height, {autoUpdate: true});
-					}
-					else {
-						this._map.fire('invalidatepreview', {id: preview.id});
-					}
-				}
-			}
-		}
-		this._previewInvalidations = [];
 	}
 });
commit 1169c2ae396576b5e6fffe8a2532303c1b9dc15f
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Tue Oct 13 20:56:42 2015 +0300

    added a part parameter to the 'downloadas' command
    
    We need it to export a specific slide to svg

diff --git a/loleaflet/src/control/Toolbar.js b/loleaflet/src/control/Toolbar.js
index 0beaf57..0e276f7 100644
--- a/loleaflet/src/control/Toolbar.js
+++ b/loleaflet/src/control/Toolbar.js
@@ -40,7 +40,8 @@ L.Map.include({
 		}
 		L.Socket.sendMessage('downloadas ' +
 			'name=' + name + ' ' +
-			'id=0 ' +
+			'id=-1 ' + // not a special download
+			'part=-1 ' + // we don't want to export just a single part
 			'format=' + format + ' ' +
 			'options=' + options);
 	},
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index c52daef..95de103 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -274,7 +274,7 @@ L.TileLayer = L.GridLayer.extend({
 		var url = window.location.protocol + '//' + parser.hostname + ':' + command.port + '/' +
 			command.jail + '/' + command.dir + '/' + command.name;
 
-		if (command.id !== '0') {
+		if (command.id !== '-1') {
 			var isFirefox = typeof InstallTrigger !== 'undefined' || navigator.userAgent.search('Firefox') >= 0;
 			if (isFirefox) {
 				// the print dialog doesn't work well on firefox
diff --git a/loleaflet/src/map/handler/Map.Keyboard.js b/loleaflet/src/map/handler/Map.Keyboard.js
index c674240..37820b8 100644
--- a/loleaflet/src/map/handler/Map.Keyboard.js
+++ b/loleaflet/src/map/handler/Map.Keyboard.js
@@ -284,7 +284,7 @@ L.Map.Keyboard = L.Handler.extend({
 				L.Socket.sendMessage('uno .uno:LeftPara');
 				break;
 			case 80: // p
-				L.Socket.sendMessage('downloadas name=print.pdf id=print format=pdf options=');
+				L.Socket.sendMessage('downloadas name=print.pdf id=print part=-1 format=pdf options=');
 				break;
 			case 82: // r
 				L.Socket.sendMessage('uno .uno:RightPara');
diff --git a/loolwsd/LOOLSession.cpp b/loolwsd/LOOLSession.cpp
index cf7e3a3..53a0873 100644
--- a/loolwsd/LOOLSession.cpp
+++ b/loolwsd/LOOLSession.cpp
@@ -999,20 +999,22 @@ void ChildProcessSession::sendTile(const char *buffer, int length, StringTokeniz
 bool ChildProcessSession::downloadAs(const char *buffer, int length, StringTokenizer& tokens)
 {
     std::string name, id, format, filterOptions;
+    int part;
 
-    if (tokens.count() < 5 ||
+    if (tokens.count() < 6 ||
         !getTokenString(tokens[1], "name", name) ||
-        !getTokenString(tokens[2], "id", id))
+        !getTokenString(tokens[2], "id", id),
+        !getTokenInteger(tokens[3], "part", part))
     {
-        sendTextFrame("error: cmd=saveas kind=syntax");
+        sendTextFrame("error: cmd=downloadas kind=syntax");
         return false;
     }
 
-    getTokenString(tokens[3], "format", format);
+    getTokenString(tokens[4], "format", format);
 
-    if (getTokenString(tokens[4], "options", filterOptions)) {
-        if (tokens.count() > 5) {
-            filterOptions += Poco::cat(std::string(" "), tokens.begin() + 4, tokens.end());
+    if (getTokenString(tokens[5], "options", filterOptions)) {
+        if (tokens.count() > 6) {
+            filterOptions += Poco::cat(std::string(" "), tokens.begin() + 6, tokens.end());
         }
     }
 
@@ -1030,6 +1032,10 @@ bool ChildProcessSession::downloadAs(const char *buffer, int length, StringToken
     } while (file->exists());
     delete file;
 
+    if (part != -1) {
+        // we need this to export a slide to svg
+        _loKitDocument->pClass->setPart(_loKitDocument, part);
+    }
     _loKitDocument->pClass->saveAs(_loKitDocument, url.c_str(),
             format.size() == 0 ? NULL :format.c_str(),
             filterOptions.size() == 0 ? NULL : filterOptions.c_str());
diff --git a/loolwsd/protocol.txt b/loolwsd/protocol.txt
index d16d38c..dbe89fb 100644
--- a/loolwsd/protocol.txt
+++ b/loolwsd/protocol.txt
@@ -17,9 +17,11 @@ canceltiles
     dropped and will not be handled. There is no guarantee of exactly
     which tile: messages might still be sent back to the client.
 
-downloadas downloadas name=<fileName> id=<id> format=<document format> options=<SkipImages, etc>
+downloadas downloadas name=<fileName> id=<id> part=<part> format=<document format> options=<SkipImages, etc>
 
     Exports the current document to the desired format and returns a download URL
+    The id identifies the request on the client.
+    The part parameter is used to export a specific part, for example when we need to export a slide to svg
 
 gettextselection mimetype=<mimeType>
 
commit eff9ab1a9dcf0f97e5ff37820bebb76077a50a6b
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Tue Oct 13 19:26:16 2015 +0300

    loleaflet: print handler tdf#94607
    
    In a browser != Firefox we use a hidden iframe to load the exported pdf
    document and call print on that iframe.
    For Firefox where the PDFjs script has some permission problems, we
    can't do that so we just notify the user that they can download a pdf
    version of the document

diff --git a/loleaflet/build/deps.js b/loleaflet/build/deps.js
index dae1e80..7913747 100644
--- a/loleaflet/build/deps.js
+++ b/loleaflet/build/deps.js
@@ -225,6 +225,11 @@ var deps = {
 		desc: 'Handles mouse interaction with the document.'
 	},
 
+	Print: {
+		src: ['map/handler/Map.Print.js'],
+		desc: 'Handles the print action (ctrl + P).'
+	},
+
 	MarkerDrag: {
 		src: ['layer/marker/Marker.Drag.js'],
 		deps: ['Marker'],
diff --git a/loleaflet/src/control/Control.Dialog.js b/loleaflet/src/control/Control.Dialog.js
index 31f9654..cb84d53 100644
--- a/loleaflet/src/control/Control.Dialog.js
+++ b/loleaflet/src/control/Control.Dialog.js
@@ -5,6 +5,7 @@
 L.Control.Dialog = L.Control.extend({
 	onAdd: function (map) {
 		map.on('error', this._onError, this);
+		map.on('print', this._onPrint, this);
 		return document.createElement('div');
 	},
 
@@ -17,6 +18,18 @@ L.Control.Dialog = L.Control.extend({
 						' parsing the \'' + e.cmd + '\' command.';
 			vex.dialog.alert(msg);
 		}
+	},
+
+	_onPrint: function (e) {
+		var url = e.url;
+		vex.dialog.confirm({
+			message: 'Download PDF export?',
+			callback: L.bind(function (value) {
+				if (value) {
+					this._map._fileDownloader.src = url;
+				}
+			}, this)
+		});
 	}
 });
 
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index e5c2ba3..c52daef 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -273,7 +273,20 @@ L.TileLayer = L.GridLayer.extend({
 		parser.href = this._map.options.server;
 		var url = window.location.protocol + '//' + parser.hostname + ':' + command.port + '/' +
 			command.jail + '/' + command.dir + '/' + command.name;
-		this._map._fileDownloader.src = url;
+
+		if (command.id !== '0') {
+			var isFirefox = typeof InstallTrigger !== 'undefined' || navigator.userAgent.search('Firefox') >= 0;
+			if (isFirefox) {
+				// the print dialog doesn't work well on firefox
+				this._map.fire('print', {url: url});
+			}
+			else {
+				this._map.fire('filedownloadready', {url: url, name: name, id: command.id});
+			}
+		}
+		else {
+			this._map._fileDownloader.src = url;
+		}
 	},
 
 	_onErrorMsg: function (textMsg) {
diff --git a/loleaflet/src/map/handler/Map.Keyboard.js b/loleaflet/src/map/handler/Map.Keyboard.js
index 93490e4..c674240 100644
--- a/loleaflet/src/map/handler/Map.Keyboard.js
+++ b/loleaflet/src/map/handler/Map.Keyboard.js
@@ -283,6 +283,9 @@ L.Map.Keyboard = L.Handler.extend({
 			case 76: // l
 				L.Socket.sendMessage('uno .uno:LeftPara');
 				break;
+			case 80: // p
+				L.Socket.sendMessage('downloadas name=print.pdf id=print format=pdf options=');
+				break;
 			case 82: // r
 				L.Socket.sendMessage('uno .uno:RightPara');
 				break;
diff --git a/loleaflet/src/map/handler/Map.Print.js b/loleaflet/src/map/handler/Map.Print.js
new file mode 100644
index 0000000..1a92c8b
--- /dev/null
+++ b/loleaflet/src/map/handler/Map.Print.js
@@ -0,0 +1,62 @@
+/*
+ * L.Map.Print is handling the print action
+ */
+
+L.Map.mergeOptions({
+	print: true
+});
+
+L.Map.Print = L.Handler.extend({
+
+	initialize: function (map) {
+		this._map = map;
+	},
+
+	addHooks: function () {
+		this._map.on('filedownloadready', this._onFileReady, this);
+	},
+
+	removeHooks: function () {
+		this._map.off('filedownloadready', this._onFileReady, this);
+	},
+
+	_onFileReady: function (e) {
+		// we need to load the pdf document and pass it to the iframe as an
+		// object URL, because else we might have cross origin security problems
+		var xmlHttp = new XMLHttpRequest();
+		xmlHttp.onreadystatechange = L.bind(function () {
+			if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
+				this._onInitPrint(xmlHttp);
+			}
+		}, this);
+		xmlHttp.open('GET', e.url, true);
+		xmlHttp.responseType = 'blob';
+		xmlHttp.send();
+	},
+
+	_onInitPrint: function (e) {
+		var blob = new Blob([e.response], {type: 'application/pdf'});
+		var url = URL.createObjectURL(blob);
+		this._printIframe = L.DomUtil.create('iframe', '', document.body);
+		this._printIframe.onload = L.bind(this._onIframeLoaded, this);
+		L.DomUtil.setStyle(this._printIframe, 'visibility', 'hidden');
+		L.DomUtil.setStyle(this._printIframe, 'position', 'fixed');
+		L.DomUtil.setStyle(this._printIframe, 'right', '0');
+		L.DomUtil.setStyle(this._printIframe, 'bottom', '0');
+		this._printIframe.src = url;
+	},
+
+	_onIframeLoaded: function () {
+		this._printIframe.contentWindow.focus(); // Required for IE
+		this._printIframe.contentWindow.print();
+		// couldn't find another way to remove it
+		setTimeout(L.bind(this._closePrintDialog, this), 1000);
+	},
+
+	_closePrintDialog: function () {
+		L.DomUtil.remove(this._printIframe);
+		this._map.focus();
+	}
+});
+
+L.Map.addInitHook('addHandler', 'print', L.Map.Print);
commit 31d94d144567f79e5a311c8a22c8bffaf4af98df
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Mon Oct 12 20:33:29 2015 +0300

    loolwsd: allow cross origin requests

diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index 1c891f4..ac36945 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -213,6 +213,7 @@ public:
             File file(filePath);
             if (file.exists())
             {
+                response.set("Access-Control-Allow-Origin", "*");
                 response.sendFile(filePath, "application/octet-stream");
                 File dir(dirPath);
                 dir.remove(true);
commit 98a0fd6f9ada3ab2d544751d489ddd73496bbecc
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Fri Oct 9 17:20:18 2015 +0300

    add an ID parameter to the downloadas command
    
    We need it to know wheter the user wants to download the file or just
    print it, etc

diff --git a/loleaflet/src/control/Toolbar.js b/loleaflet/src/control/Toolbar.js
index 243b24a..0beaf57 100644
--- a/loleaflet/src/control/Toolbar.js
+++ b/loleaflet/src/control/Toolbar.js
@@ -40,6 +40,7 @@ L.Map.include({
 		}
 		L.Socket.sendMessage('downloadas ' +
 			'name=' + name + ' ' +
+			'id=0 ' +
 			'format=' + format + ' ' +
 			'options=' + options);
 	},
diff --git a/loolwsd/LOOLSession.cpp b/loolwsd/LOOLSession.cpp
index 1eafd4f..cf7e3a3 100644
--- a/loolwsd/LOOLSession.cpp
+++ b/loolwsd/LOOLSession.cpp
@@ -998,19 +998,20 @@ void ChildProcessSession::sendTile(const char *buffer, int length, StringTokeniz
 
 bool ChildProcessSession::downloadAs(const char *buffer, int length, StringTokenizer& tokens)
 {
-    std::string name, format, filterOptions;
+    std::string name, id, format, filterOptions;
 
-    if (tokens.count() < 4 ||
-        !getTokenString(tokens[1], "name", name))
+    if (tokens.count() < 5 ||
+        !getTokenString(tokens[1], "name", name) ||
+        !getTokenString(tokens[2], "id", id))
     {
         sendTextFrame("error: cmd=saveas kind=syntax");
         return false;
     }
 
-    getTokenString(tokens[2], "format", format);
+    getTokenString(tokens[3], "format", format);
 
-    if (getTokenString(tokens[3], "options", filterOptions)) {
-        if (tokens.count() > 4) {
+    if (getTokenString(tokens[4], "options", filterOptions)) {
+        if (tokens.count() > 5) {
             filterOptions += Poco::cat(std::string(" "), tokens.begin() + 4, tokens.end());
         }
     }
@@ -1034,7 +1035,7 @@ bool ChildProcessSession::downloadAs(const char *buffer, int length, StringToken
             filterOptions.size() == 0 ? NULL : filterOptions.c_str());
 
     sendTextFrame("downloadas: jail=" + _childId + " dir=" + tmpDir + " name=" + name +
-            " port=" + std::to_string(LOOLWSD::portNumber));
+            " port=" + std::to_string(LOOLWSD::portNumber) + " id=" + id);
     return true;
 }
 
diff --git a/loolwsd/protocol.txt b/loolwsd/protocol.txt
index 3c8369f..d16d38c 100644
--- a/loolwsd/protocol.txt
+++ b/loolwsd/protocol.txt
@@ -17,7 +17,7 @@ canceltiles
     dropped and will not be handled. There is no guarantee of exactly
     which tile: messages might still be sent back to the client.
 
-downloadas downloadas name=<fileName> format=<document format> options=<SkipImages, etc>
+downloadas downloadas name=<fileName> id=<id> format=<document format> options=<SkipImages, etc>
 
     Exports the current document to the desired format and returns a download URL
 
commit 07b4426758c2ac8b549e8de82b14cbdc538506d7
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Fri Oct 9 17:00:37 2015 +0300

    loleaflet: use the window's protocl for downloading

diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index 1054e73..e5c2ba3 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -271,7 +271,7 @@ L.TileLayer = L.GridLayer.extend({
 		var command = L.Socket.parseServerCmd(textMsg);
 		var parser = document.createElement('a');
 		parser.href = this._map.options.server;
-		var url = 'http://' + parser.hostname + ':' + command.port + '/' +
+		var url = window.location.protocol + '//' + parser.hostname + ':' + command.port + '/' +
 			command.jail + '/' + command.dir + '/' + command.name;
 		this._map._fileDownloader.src = url;
 	},
commit d13882ad4c3ab16c243dae97fd62d539747585cc
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Fri Oct 9 16:12:07 2015 +0300

    tdf#94607 downloadAs command handler
    
    We use a hidden iframe to download the document. Once we have the url
    for it, we set iframr.src = url
    Also added map.downloadAs(name, format, options) method

diff --git a/loleaflet/README b/loleaflet/README
index 8cc2739..2011c33 100644
--- a/loleaflet/README
+++ b/loleaflet/README
@@ -133,6 +133,7 @@ Statusindicator (when the document is loading):
 Save:
     - API:
         map.saveAs(url, [format, options])
+        map.downloadAs(name, [format, options])
 
 Scroll (the following are measured in pixels):
     - API:
diff --git a/loleaflet/reference.html b/loleaflet/reference.html
index 8895452..67fd51f 100644
--- a/loleaflet/reference.html
+++ b/loleaflet/reference.html
@@ -1561,6 +1561,24 @@ var map = L.map('map', {
 		<td><code>undefined</code></td>
 		<td>Toggles the state for the given UNO command.</td>
 	</tr>
+	<tr>
+		<td><code><b>saveAs</b>(
+			<nobr><String><i>url</i>,</nobr>
+            <nobr><String><i>format?</i>,</nobr>
+			<nobr><String><i>options?</i>)</nobr>
+		</code></td>
+		<td><code>undefined</code></td>
+        <td>Save the document as "format" at the given url by applying the filter options.</td>
+	</tr>
+	<tr>
+		<td><code><b>downloadAs</b>(
+			<nobr><String><i>name</i>,</nobr>
+			<nobr><String><i>format?</i>,</nobr>
+            <nobr><String><i>options?</i>)</nobr>
+		</code></td>
+		<td><code>undefined</code></td>
+        <td>Download the document as "format" with the name "name" by applying the filter options.</td>
+	</tr>
 </table>
 
 <h2 id="loleaflet-page">Page oriented</h2>
diff --git a/loleaflet/src/control/Toolbar.js b/loleaflet/src/control/Toolbar.js
index b8b316d..243b24a 100644
--- a/loleaflet/src/control/Toolbar.js
+++ b/loleaflet/src/control/Toolbar.js
@@ -31,6 +31,19 @@ L.Map.include({
 		return this._docLayer._toolbarCommandValues[command];
 	},
 
+	downloadAs: function (name, format, options) {
+		if (format === undefined || format === null) {
+			format = '';
+		}
+		if (options === undefined || options === null) {
+			options = '';
+		}
+		L.Socket.sendMessage('downloadas ' +
+			'name=' + name + ' ' +
+			'format=' + format + ' ' +
+			'options=' + options);
+	},
+
 	saveAs: function (url, format, options) {
 		if (format === undefined || format === null) {
 			format = '';
diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index 99a8c19..947adf5 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -196,6 +196,18 @@ L.Socket = {
 			else if (tokens[i].substring(0, 5) === 'kind=') {
 				command.errorKind = tokens[i].substring(5);
 			}
+			else if (tokens[i].substring(0, 5) === 'jail=') {
+				command.jail = tokens[i].substring(5);
+			}
+			else if (tokens[i].substring(0, 4) === 'dir=') {
+				command.dir = tokens[i].substring(4);
+			}
+			else if (tokens[i].substring(0, 5) === 'name=') {
+				command.name = tokens[i].substring(5);
+			}
+			else if (tokens[i].substring(0, 5) === 'port=') {
+				command.port = tokens[i].substring(5);
+			}
 		}
 		if (command.tileWidth && command.tileHeight && this._map._docLayer) {
 			var scale = command.tileWidth / this._map._docLayer.options.tileWidthTwips;
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index 6be61de..1054e73 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -185,6 +185,9 @@ L.TileLayer = L.GridLayer.extend({
 		else if (textMsg.startsWith('cursorvisible:')) {
 			this._onCursorVisibleMsg(textMsg);
 		}
+		else if (textMsg.startsWith('downloadas:')) {
+			this._onDownloadAsMsg(textMsg);
+		}
 		else if (textMsg.startsWith('error:')) {
 			this._onErrorMsg(textMsg);
 		}
@@ -264,6 +267,15 @@ L.TileLayer = L.GridLayer.extend({
 		this._onUpdateCursor();
 	},
 
+	_onDownloadAsMsg: function (textMsg) {
+		var command = L.Socket.parseServerCmd(textMsg);
+		var parser = document.createElement('a');
+		parser.href = this._map.options.server;
+		var url = 'http://' + parser.hostname + ':' + command.port + '/' +
+			command.jail + '/' + command.dir + '/' + command.name;
+		this._map._fileDownloader.src = url;
+	},
+
 	_onErrorMsg: function (textMsg) {
 		var command = L.Socket.parseServerCmd(textMsg);
 		this._map.fire('error', {cmd: command.errorCmd, kind: command.errorKind});
diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js
index c1db931..df230b9 100644
--- a/loleaflet/src/map/Map.js
+++ b/loleaflet/src/map/Map.js
@@ -458,6 +458,7 @@ L.Map = L.Evented.extend({
 		var textAreaContainer = L.DomUtil.create('div', 'clipboard-container', container.parentElement);
 		this._textArea = L.DomUtil.create('textarea', 'clipboard', textAreaContainer);
 		this._resizeDetector = L.DomUtil.create('iframe', 'resize-detector', container);
+		this._fileDownloader = L.DomUtil.create('iframe', 'resize-detector', container);
 
 		container._leaflet = true;
 	},
commit 2141c1f70d3c4545f707a924ccfb5391dce4678b
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Fri Oct 9 15:55:49 2015 +0300

    tdf#94607 downloadAs command that generates an URL for the doc
    
    When requested, the document is exported under
    /jail_path/CHILD_ID/user/thedocument/RANDOMDIR/filename
    and CHILD_ID, RANDOMDIR and the filename are communicated to the client.
    
    When the client requests
    http://server:port/CHILD_ID/RANDOMDIR/filename, the exported document
    is served and then RANDOMDIR is removed

diff --git a/loolwsd/LOOLSession.cpp b/loolwsd/LOOLSession.cpp
index 1741faa..1eafd4f 100644
--- a/loolwsd/LOOLSession.cpp
+++ b/loolwsd/LOOLSession.cpp
@@ -290,11 +290,12 @@ bool MasterProcessSession::handleInput(const char *buffer, int length)
     }
     else if (tokens[0] != "canceltiles" &&
              tokens[0] != "commandvalues" &&
-             tokens[0] != "partpagerectangles" &&
+             tokens[0] != "downloadas" &&
              tokens[0] != "gettextselection" &&
              tokens[0] != "invalidatetiles" &&
              tokens[0] != "key" &&
              tokens[0] != "mouse" &&
+             tokens[0] != "partpagerectangles" &&
              tokens[0] != "requestloksession" &&
              tokens[0] != "resetselection" &&
              tokens[0] != "saveas" &&
@@ -632,10 +633,11 @@ void MasterProcessSession::forwardToPeer(const char *buffer, int length)
     peer->sendBinaryFrame(buffer, length);
 }
 
-ChildProcessSession::ChildProcessSession(std::shared_ptr<WebSocket> ws, LibreOfficeKit *loKit) :
+ChildProcessSession::ChildProcessSession(std::shared_ptr<WebSocket> ws, LibreOfficeKit *loKit, std::string childId) :
     LOOLSession(ws, Kind::ToMaster),
     _loKitDocument(NULL),
     _loKit(loKit),
+    _childId(childId),
     _clientPart(0)
 {
     std::cout << Util::logPrefix() << "ChildProcessSession ctor this=" << this << " ws=" << _ws.get() << std::endl;
@@ -699,7 +701,8 @@ bool ChildProcessSession::handleInput(const char *buffer, int length)
         // All other commands are such that they always require a LibreOfficeKitDocument session,
         // i.e. need to be handled in a child process.
 
-        assert(tokens[0] == "gettextselection" ||
+        assert(tokens[0] == "downloadas" ||
+               tokens[0] == "gettextselection" ||
                tokens[0] == "key" ||
                tokens[0] == "mouse" ||
                tokens[0] == "uno" ||
@@ -712,7 +715,11 @@ bool ChildProcessSession::handleInput(const char *buffer, int length)
         {
             _loKitDocument->pClass->setPart(_loKitDocument, _clientPart);
         }
-        if (tokens[0] == "gettextselection")
+        if (tokens[0] == "downloadas")
+        {
+            return downloadAs(buffer, length, tokens);
+        }
+        else if (tokens[0] == "gettextselection")
         {
             return getTextSelection(buffer, length, tokens);
         }
@@ -989,6 +996,48 @@ void ChildProcessSession::sendTile(const char *buffer, int length, StringTokeniz
     sendBinaryFrame(output.data(), output.size());
 }
 
+bool ChildProcessSession::downloadAs(const char *buffer, int length, StringTokenizer& tokens)
+{
+    std::string name, format, filterOptions;
+
+    if (tokens.count() < 4 ||
+        !getTokenString(tokens[1], "name", name))
+    {
+        sendTextFrame("error: cmd=saveas kind=syntax");
+        return false;
+    }
+
+    getTokenString(tokens[2], "format", format);
+
+    if (getTokenString(tokens[3], "options", filterOptions)) {
+        if (tokens.count() > 4) {
+            filterOptions += Poco::cat(std::string(" "), tokens.begin() + 4, tokens.end());
+        }
+    }
+
+    std::string tmpDir, url;
+    File *file = NULL;
+    do
+    {
+        if (file != NULL)
+        {
+            delete file;
+        }
+        tmpDir = std::to_string((((Poco::UInt64)LOOLWSD::_rng.next()) << 32) | LOOLWSD::_rng.next() | 1);
+        url = jailDocumentURL + "/" + tmpDir + "/" + name;
+        file = new File(url);
+    } while (file->exists());
+    delete file;
+
+    _loKitDocument->pClass->saveAs(_loKitDocument, url.c_str(),
+            format.size() == 0 ? NULL :format.c_str(),
+            filterOptions.size() == 0 ? NULL : filterOptions.c_str());
+
+    sendTextFrame("downloadas: jail=" + _childId + " dir=" + tmpDir + " name=" + name +
+            " port=" + std::to_string(LOOLWSD::portNumber));
+    return true;
+}
+
 bool ChildProcessSession::getTextSelection(const char *buffer, int length, StringTokenizer& tokens)
 {
     std::string mimeType;
diff --git a/loolwsd/LOOLSession.hpp b/loolwsd/LOOLSession.hpp
index 13970c6..70210ae 100644
--- a/loolwsd/LOOLSession.hpp
+++ b/loolwsd/LOOLSession.hpp
@@ -51,12 +51,12 @@ public:
 
     virtual bool handleInput(const char *buffer, int length) = 0;
 
+    static const std::string jailDocumentURL;
+
 protected:
     LOOLSession(std::shared_ptr<Poco::Net::WebSocket> ws, Kind kind);
     virtual ~LOOLSession();
 
-    static const std::string jailDocumentURL;
-
     const Kind _kind;
 
     std::string _kindString;
@@ -156,7 +156,7 @@ private:
 class ChildProcessSession final : public LOOLSession
 {
 public:
-    ChildProcessSession(std::shared_ptr<Poco::Net::WebSocket> ws, LibreOfficeKit *loKit);
+    ChildProcessSession(std::shared_ptr<Poco::Net::WebSocket> ws, LibreOfficeKit *loKit, std::string _childId);
     virtual ~ChildProcessSession();
 
     virtual bool handleInput(const char *buffer, int length) override;
@@ -175,6 +175,7 @@ public:
 
     virtual void sendTile(const char *buffer, int length, Poco::StringTokenizer& tokens);
 
+    bool downloadAs(const char *buffer, int length, Poco::StringTokenizer& tokens);
     bool getTextSelection(const char *buffer, int length, Poco::StringTokenizer& tokens);
     bool keyEvent(const char *buffer, int length, Poco::StringTokenizer& tokens);
     bool mouseEvent(const char *buffer, int length, Poco::StringTokenizer& tokens);
@@ -186,9 +187,9 @@ public:
     bool setClientPart(const char *buffer, int length, Poco::StringTokenizer& tokens);
     bool setPage(const char *buffer, int length, Poco::StringTokenizer& tokens);
 
-    std::string _jail;
     std::string _loSubPath;
     LibreOfficeKit *_loKit;
+    std::string _childId;
 
  private:
     int _clientPart;
diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index 8ef2c7e..1c891f4 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -176,11 +176,11 @@ private:
     tsqueue<std::string>& _queue;
 };
 
-class WebSocketRequestHandler: public HTTPRequestHandler
-    /// Handle a WebSocket connection.
+class RequestHandler: public HTTPRequestHandler
+    /// Handle a WebSocket connection or a simple HTTP request.
 {
 public:
-    WebSocketRequestHandler()
+    RequestHandler()
     {
     }
 
@@ -199,9 +199,30 @@ public:
 
         if(!(request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0))
         {
-            response.setStatusAndReason(HTTPResponse::HTTP_BAD_REQUEST);
-            response.setContentLength(0);
-            response.send();
+            // The user might request a file to download
+            StringTokenizer tokens(request.getURI(), "/");
+            if (tokens.count() != 4)
+            {
+                response.setStatus(HTTPResponse::HTTP_BAD_REQUEST);
+                response.setContentLength(0);
+                response.send();
+            }
+            std::string dirPath = LOOLWSD::childRoot + "/" + tokens[1] + LOOLSession::jailDocumentURL + "/" + tokens[2];
+            std::string filePath = dirPath + "/" + tokens[3];
+            std::cout << Util::logPrefix() << "HTTP request for: " << filePath << std::endl;
+            File file(filePath);
+            if (file.exists())
+            {
+                response.sendFile(filePath, "application/octet-stream");
+                File dir(dirPath);
+                dir.remove(true);
+            }
+            else
+            {
+                response.setStatus(HTTPResponse::HTTP_NOT_FOUND);
+                response.setContentLength(0);
+                response.send();
+            }
             return;
         }
 
@@ -341,7 +362,7 @@ public:
         }
 
         Application::instance().logger().information(line);
-        return new WebSocketRequestHandler();
+        return new RequestHandler();
     }
 };
 
@@ -778,7 +799,7 @@ void LOOLWSD::componentMain()
         HTTPResponse response;
         std::shared_ptr<WebSocket> ws(new WebSocket(cs, request, response));
 
-        std::shared_ptr<ChildProcessSession> session(new ChildProcessSession(ws, loKit));
+        std::shared_ptr<ChildProcessSession> session(new ChildProcessSession(ws, loKit, std::to_string(_childId)));
 
         ws->setReceiveTimeout(0);
 
diff --git a/loolwsd/LOOLWSD.hpp b/loolwsd/LOOLWSD.hpp
index aa419c6..964115f 100644
--- a/loolwsd/LOOLWSD.hpp
+++ b/loolwsd/LOOLWSD.hpp
@@ -41,6 +41,7 @@ public:
     static std::string jail;
     static Poco::SharedMemory _sharedForkChild;
     static Poco::NamedMutex _namedMutexLOOL;
+    static Poco::Random _rng;
 
     static const int DEFAULT_CLIENT_PORT_NUMBER = 9980;
     static const int MASTER_PORT_NUMBER = 9981;
@@ -70,7 +71,6 @@ private:
     Poco::UInt64 _childId;
     static int _numPreSpawnedChildren;
     static std::mutex _rngMutex;
-    static Poco::Random _rng;
 
 #if ENABLE_DEBUG
 public:
diff --git a/loolwsd/protocol.txt b/loolwsd/protocol.txt
index 88cc062..3c8369f 100644
--- a/loolwsd/protocol.txt
+++ b/loolwsd/protocol.txt
@@ -17,6 +17,10 @@ canceltiles
     dropped and will not be handled. There is no guarantee of exactly
     which tile: messages might still be sent back to the client.
 
+downloadas downloadas name=<fileName> format=<document format> options=<SkipImages, etc>
+
+    Exports the current document to the desired format and returns a download URL
+
 gettextselection mimetype=<mimeType>
 
     Request selection's content
@@ -82,6 +86,11 @@ partpagerectangles
 server -> client
 ================
 
+downloadas: jail=<jail directory> dir=<a tmp dir> name=<name> port=<port>
+
+    The client should then request http://server:port/jail/dir/name in order to download
+    the document
+
 error: cmd=<command> kind=<kind>
 <freeErrorText>
 
commit 30b5fa93249db6b3d6f639231675a6284231ec07
Author: Mihai Varga <mihai.varga at collabora.com>
Date:   Thu Oct 15 18:46:03 2015 +0300

    loleaflet: updated and reorganized the html documentation

diff --git a/loleaflet/reference.html b/loleaflet/reference.html
index 057c2d5..8895452 100644
--- a/loleaflet/reference.html
+++ b/loleaflet/reference.html
@@ -55,12 +55,14 @@
 		</ul>
 	</div>
 	<div class="toc-col">
-		<h4>LoLeaflet API</h4>
+		<h4 style="color:red;">LoLeaflet API</h4>
 		<ul>
 			<li><a href="#loleaflet-general">General</a></li>
 			<li><a href="#loleaflet-toolbar">Toolbar</a></li>
 			<li><a href="#loleaflet-page">Page oriented</a></li>
 			<li><a href="#loleaflet-part">Part oriented</a></li>
+			<li><a href="#loleaflet-events">Events</a></li>
+			<li><a href="#loleaflet-object-values">Object values</a></li>
 		</ul>
 		<h4>UI Layers</h4>
 		<ul>
@@ -147,7 +149,6 @@
 		<h4>Misc</h4>
 		<ul>
 			<li><a href="#event-objects">Event objects</a></li>
-			<li><a href="#object-values">Object values</a></li>
 			<li><a href="#global">global switches</a></li>
 			<li><a href="#noconflict">noConflict</a></li>
 			<li><a href="#version">version</a></li>
@@ -1466,6 +1467,44 @@ var map = L.map('map', {
 		<td><code><nobr><a href="#point">Point</a></nobr></code></td>
 		<td>Returns the scroll offset relative to the beginning of the document.</td>
 	</tr>
+	<tr>
+        <td><code><b>getPreview</b>(
+            <Object><i>id</i>,<br>
+			<Number><i>index</i>,<br>
+			<Number><i>maxWidth</i>,<br>
+			<Number><i>maxHeight</i>,<br>
+            <nobr><<a href="#getpreview-options">PreviewOptions</a>><i>options?</i>)</nobr>
+		</code></td>
+		<td><code>undefined</code></td>
+		<td>Triggers the creation of a preview with the given id, of maximum maxWidth X maxHeight size, of the
+            page / part with number 'index', keeping the original ration. By passing an
+            optional parameter {autoUpdate: true}, the preview will be automatically invalidated.</td>
+	</tr>
+	<tr>
+		<td><code><b>getCustomPreview</b>(
+            <Object><i>id</i>,<br>
+			<Number><i>part</i>,<br>
+			<Number><i>width</i>,<br>
+			<Number><i>height</i>,<br>
+			<Number><i>tilePosX</i>,<br>
+			<Number><i>tilePosY</i>,<br>
+			<Number><i>tileWidth</i>,<br>
+			<Number><i>tileHeight</i>,<br>
+            <nobr><<a href="#getpreview-options">PreviewOptions</a>><i>options?</i>)</nobr>
+		</code></td>
+		<td><code>undefined</code></td>
+		<td>Triggers the creation of a preview with the given id, of width X height size, of the
+            [(tilePosX,tilePosY), (tilePosX + tileWidth, tilePosY + tileHeight)] section of the document. By passing an
+            optional parameter {autoUpdate: true}, the preview will be automatically invalidated.</td>
+	</tr>
+	<tr>
+		<td><code><b>removePreviewUpdate</b>(
+			<nobr><Object><i>id</i>)</nobr>
+        </code></td>
+		<td><code>undfined</code></td>
+		<td>Cancels the automatic update for the preview defined by 'id'.</td>
+	</tr>
+
 </table>
 
 <h3 id="scroll-options">ScrollOptions</h3>
@@ -1483,6 +1522,21 @@ var map = L.map('map', {
 	</tr>
 </table>
 
+<h3 id="getpreview-options">PreviewOptions</h3>
+
+<table data-id='values'>
+	<tr>
+		<th class="width100">property</th>
+		<th class="width100">type</th>
+		<th>description</th>
+	</tr>
+	<tr>
+		<td><code><b>autoUpdate</b></code></td>
+		<td><code>Boolean</code></td>
+        <td>Whether a new preview is generated automatically when it becomes invalid.</td>
+	</tr>
+</table>
+
 <h2 id="loleaflet-toolbar">Toolbar</h2>
 
 <p>Toolbar methods.</p>
@@ -1536,29 +1590,6 @@ var map = L.map('map', {
 		<td><code>undfined</code></td>
 		<td>Scrolls to the beginning of the given page.</td>
 	</tr>
-	<tr>
-		<td><code><b>getDocPreview</b>(
-            <Object><i>id</i>,<br>
-			<Number><i>maxWidth</i>,<br>
-			<Number><i>maxHeight</i>,<br>
-			<Number><i>x</i>,<br>
-			<Number><i>y</i>,<br>
-			<Number><i>width</i>,<br>
-			<Number><i>height</i>,<br>
-            <nobr><<a href="#getpreview-options">PreviewOptions</a>><i>options?</i>)</nobr>
-		</code></td>
-		<td><code>undefined</code></td>
-		<td>Triggers the creation of a preview with the given id, of maximum maxWidth X maxHeight size, of the
-            [(x,y), (x + width, y + height)] section of the document keeping the original ration. By passing an
-            optional parameter {autoUpdate: true}, the preview will be automatically invalidated.</td>
-	</tr>
-	<tr>
-		<td><code><b>removePreviewUpdate</b>(
-			<nobr><Object><i>id</i>)</nobr>
-        </code></td>
-		<td><code>undfined</code></td>
-		<td>Cancels the automatic update for the preview defined by 'id'.</td>
-	</tr>
 </table>
 
 <h2 id="loleaflet-part">Part oriented</h2>
@@ -1588,1298 +1619,1087 @@ var map = L.map('map', {
 		<td><code>undfined</code></td>
 		<td>Select a specific part.</td>
 	</tr>
-	<tr>
-        <td><code><b>getPartPreview</b>(
-            <Object><i>id</i>,<br>
-			<Number><i>part</i>,<br>
-			<Number><i>maxWidth</i>,<br>
-			<Number><i>maxHeight</i>,<br>
-            <nobr><<a href="#getpreview-options">PreviewOptions</a>><i>options?</i>)</nobr>
-		</code></td>
-		<td><code>undefined</code></td>
-		<td>Triggers the creation of a preview with the given id, of maximum maxWidth X maxHeight size, of the
-            specified part keeping the original ration. By passing an
-            optional parameter {autoUpdate: true}, the preview will be automatically invalidated.</td>
-	</tr>
-	<tr>
-		<td><code><b>removePreviewUpdate</b>(
-			<nobr><Object><i>id</i>)</nobr>
-        </code></td>
-		<td><code>undfined</code></td>
-		<td>Cancels the automatic update for the preview defined by 'id'.</td>
-	</tr>
 </table>
 
-<h3 id="getpreview-options">PreviewOptions</h3>
+<h2 id="loleaflet-events">Events</h2>
 
-<table data-id='values'>
+<h3 id="search-event">SearchEvent</h3>
+
+<table data-id='events'>
 	<tr>
 		<th class="width100">property</th>
-		<th class="width100">type</th>
+		<th>type</th>
 		<th>description</th>
 	</tr>
 	<tr>
-		<td><code><b>autoUpdate</b></code></td>
-		<td><code>Boolean</code></td>
-        <td>Whether a new preview is generated automatically when it becomes invalid.</td>
+		<td><code><b>originalPhrase</b></code></td>
+		<td><code>String</code></td>
+		<td>The phrase that has been searched for</td>
+	</tr>
+	<tr>
+		<td><code><b>count</b></code></td>
+		<td><code>Number</code></td>
+		<td>Number of search results</td>
+	</tr>
+	<tr>
+		<td><code><b>results</b></code></td>
+		<td><code><a href="#bounds">Bounds[]</a></code></td>
+		<td>An array of bounds representing the selections of the search results in the document.</td>
 	</tr>
 </table>
 
-<h2 id="marker">Marker</h2>
-
-<p>Used to put markers on the map. Extends <a href="#layer">Layer</a>.</p>
-
-<pre><code class="javascript">L.marker([50.5, 30.5]).addTo(map);</code></pre>
-
-<h3>Creation</h3>
+<h3 id="partpagerectangles-event">PartPageRectangles</h3>
 
-<table data-id='marker'>
+<table data-id='events'>
 	<tr>
-		<th class="width200">Factory</th>
-
-		<th>Description</th>
+		<th class="width100">property</th>
+		<th>type</th>
+		<th>description</th>
 	</tr>
 	<tr>
-		<td><code><b>L.marker</b>(
-			<nobr><<a href="#latlng">LatLng</a>> <i>latlng</i>,</nobr>
-			<nobr><<a href="#marker-options">Marker options</a>> <i>options?</i> )</nobr>
-		</code></td>
-
-		<td>Instantiates a Marker object given a geographical point and optionally an options object.</td>
+		<td><code><b>pixelRectangles</b></code></td>
+        <td><code><a href="#bounds">Bounds[]</a></code></td>
+		<td>An array of bounds representing each page's dimension in pixels on the current zoom level.</td>
+	</tr>
+	<tr>
+		<td><code><b>twipsRectangles</b></code></td>
+		<td><code><a href="#bounds">Bounds[]</a></code></td>
+		<td>An array of bounds representing each page's dimension in twips.</td>
 	</tr>
 </table>
 
-<h3 id="marker-options">Options</h3>
+<h3 id="permission-event">PermissionEvent</h3>
 
-<table data-id='marker'>
+<table data-id='events'>
 	<tr>
-		<th>Option</th>
-		<th>Type</th>
-		<th>Default</th>
-		<th>Description</th>
+		<th class="width100">property</th>
+		<th>type</th>
+		<th>description</th>
 	</tr>
 	<tr>
-		<td><code><b>icon</b></code></td>
-		<td><code><a href="#icon">L.Icon</a></code></td>
-		<td>*</td>
-		<td>Icon class to use for rendering the marker. See <a href="#icon">Icon documentation</a> for details on how to customize the marker icon. Set to <code>new L.Icon.Default()</code> by default.</td>
+		<td><code><b>perm</b></code></td>
+		<td><code><a href="#documentpermission-values">DocumentPermission</a></code></td>
+		<td>Document permission.</td>
 	</tr>
+</table>
+
+<h3 id="commandstatechanged-event">CommandStateChangedEvent</h3>
+
+<table data-id='events'>
 	<tr>
-		<td><code><b>interactive</b></code></td>
-		<td><code>Boolean</code></td>
-		<td><code><span class="literal">true</span></code></td>
-		<td>If <code><span class="literal">false</span></code>, the marker will not emit mouse events and will act as a part of the underlying map.</td>
+		<th class="width100">property</th>
+		<th>type</th>
+		<th>description</th>
 	</tr>
 	<tr>
-		<td><code><b>draggable</b></code></td>
-		<td><code>Boolean</code></td>
-		<td><code><span class="literal">false</span></code></td>
-		<td>Whether the marker is draggable with mouse/touch or not.</td>
+		<td><code><b>commandName</b></code></td>
+		<td><code><a href="#commandstatechanged-values">CommandStateChangedValues</a></code></td>
+		<td>UNO command.</td>
 	</tr>
 	<tr>
-		<td><code><b>keyboard</b></code></td>
-		<td><code>Boolean</code></td>
-		<td><code><span class="literal">true</span></code></td>
-		<td>Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.</td>
+		<td><code><b>state</b></code></td>
+		<td><code><a href="#commandstate-values">CommandStateValues</a></code></td>
+		<td>UNO command state.</td>
 	</tr>
+</table>
+
+<h3 id="updateparts-event">UpdatePartsEvent</h3>
+
+<table data-id='events'>
 	<tr>
-		<td><code><b>title</b></code></td>
-		<td><code>String</code></td>
-		<td><code><span class="string">''</span></code></td>
-		<td>Text for the browser tooltip that appear on marker hover (no tooltip by default).</td>
+		<th class="width100">property</th>
+		<th>type</th>
+		<th>description</th>
 	</tr>
 	<tr>
-		<td><code><b>alt</b></code></td>
-		<td><code>String</code></td>
-		<td><code><span class="string">''</span></code></td>
-		<td>Text for the <code>alt</code> attribute of the icon image (useful for accessibility).</td>
-	</tr>
-	<tr id="marker-zindexoffset">
-		<td><code><b>zIndexOffset</b></code></td>
+		<td><code><b>selectedPart</b></code></td>
 		<td><code>Number</code></td>
-		<td><code><span class="number">0</span></code></td>
-		<td>By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like <code>1000</code> (or high negative value, respectively).</td>
+		<td>The currently selected part.</td>
 	</tr>
 	<tr>
-		<td><code><b>opacity</b></code></td>
+		<td><code><b>parts</b></code></td>
 		<td><code>Number</code></td>
-		<td><code><span class="number">1.0</span></code></td>
-		<td>The opacity of the marker.</td>
+		<td>The number of parts in the document.</td>
 	</tr>
 	<tr>
-		<td><code><b>riseOnHover</b></code></td>
-		<td><code>Boolean</code></td>
-		<td><code><span class="literal">false</span></code></td>
-		<td>If <code><span class="literal">true</span></code>, the marker will get on top of others when you hover the mouse over it.</td>
+		<td><code><b>docType</b></code></td>
+		<td><code><a href="#documenttype-values">DocumentTypeValues</a></code></td>
+		<td>The document type.</td>
 	</tr>
 	<tr>
-		<td><code><b>riseOffset</b></code></td>
-		<td><code>Number</code></td>
-		<td><code><span class="number">250</span></code></td>
-		<td>The z-index offset used for the <code>riseOnHover</code> feature.</td>
+		<td><code><b>partNames</b></code></td>
+		<td><code>String[]</code></td>
+		<td>If present, an array containing slides' / spreadsheets' names.</td>
 	</tr>
+</table>
+
+<h3 id="invalidatepreview-event">InvalidatePreviewEvent</h3>
+<p>Loleaflet specific events.</p>
+
+<table data-id='events'>
 	<tr>
-		<td><code><b>pane</b></code></td>
-		<td><code>String</code></td>
-		<td><code><span class="string">'markerPane'</span></code></td>
-		<td><a href="#map-panes">Map pane</a> where the markers icon will be added.</td>
+		<th class="width100">property</th>
+		<th>type</th>
+		<th>description</th>
 	</tr>
 	<tr>
-		<td><code><b>shadowPane</b></code></td>
-		<td><code>String</code></td>
-		<td><code><span class="string">'shadowPane'</span></code></td>
-		<td><a href="#map-panes">Map pane</a> where the markers shadow will be added.</td>
+		<td><code><b>id</b></code></td>
+		<td><code>Object</code></td>
+		<td>Preview's id that needs to be invalidated.</td>
 	</tr>
 </table>
 
-<h3>Events</h3>
-
-<p>You can subscribe to the following events using <a href="#events">these methods</a>.</p>
+<h3 id="tilepreview-event">TilePreviewEvent</h3>
 
-<table data-id='marker'>
+<table data-id='events'>
 	<tr>
-		<th class="width100">Event</th>
-		<th class="width100">Data</th>
-		<th>Description</th>
+		<th class="width100">property</th>
+		<th>type</th>
+		<th>description</th>
 	</tr>
 	<tr>
-		<td><code><b>click</b></code></td>
-		<td><code><a href="#mouse-event">MouseEvent</a></code></td>
-		<td>Fired when the user clicks (or taps) the marker.</td>
+		<td><code><b>tile</b></code></td>
+		<td><code>Image</code></td>
+		<td>The actual preview.</td>
 	</tr>
 	<tr>
-		<td><code><b>dblclick</b></code></td>
-		<td><code><a href="#mouse-event">MouseEvent</a></code></td>
-		<td>Fired when the user double-clicks (or double-taps) the marker.</td>
+		<td><code><b>id</b></code></td>
+		<td><code>Object</code></td>
+		<td>Preview id.</td>
 	</tr>
 	<tr>
-		<td><code><b>mousedown</b></code></td>
-		<td><code><a href="#mouse-event">MouseEvent</a></code></td>
-		<td>Fired when the user pushes the mouse button on the marker.</td>
+		<td><code><b>width</b></code></td>
+		<td><code>Number</code></td>
+		<td>Image width.</td>
 	</tr>
 	<tr>
-		<td><code><b>mouseover</b></code></td>
-		<td><code><a href="#mouse-event">MouseEvent</a></code></td>
-		<td>Fired when the mouse enters the marker.</td>
+		<td><code><b>height</b></code></td>
+		<td><code>Number</code></td>
+		<td>Image height.</td>
 	</tr>
 	<tr>
-		<td><code><b>mouseout</b></code></td>
-		<td><code><a href="#mouse-event">MouseEvent</a></code></td>
-		<td>Fired when the mouse leaves the marker.</td>
+		<td><code><b>docType</b></code></td>
+		<td><code><a href="#documenttype-values">DocumentTypeValues</a></code></td>
+		<td>The document type.</td>
 	</tr>
 	<tr>
-		<td><code><b>contextmenu</b></code></td>
-		<td><code><a href="#mouse-event">MouseEvent</a></code>
-		<td>Fired when the user right-clicks on the marker.</td>
+		<td><code><b>part</b></code></td>
+		<td><code>Number</code></td>
+		<td>If the preview is for a whole part.</td>
 	</tr>
+</table>
+
+<h3 id="statusindicator-event">StatusIndicatorEvent</h3>
+
+<table data-id='events'>
 	<tr>
-		<td><code><b>dragstart</b></code></td>
-		<td><code><a href="#event">Event</a></code></td>
-		<td>Fired when the user starts dragging the marker.</td>
+		<th class="width100">property</th>
+		<th>type</th>
+		<th>description</th>
 	</tr>
 	<tr>
-		<td><code><b>drag</b></code></td>
-		<td><code><a href="#event">Event</a></code></td>
-		<td>Fired repeatedly while the user drags the marker.</td>
+		<td><code><b>statusType</b></code></td>
+		<td><code><a href="#statusindicator-values">StatusIndicatorValues</a></code></td>
+		<td>Status type.</td>
 	</tr>
 	<tr>
-		<td><code><b>dragend</b></code></td>
-		<td><code><a href="#dragend-event">DragEndEvent</a></code></td>
-		<td>Fired when the user stops dragging the marker.</td>
+		<td><code><b>value</b></code></td>
+		<td><code>Number</code></td>
+		<td>If present, a number for 0 to 100 represending the loading status.<td>
 	</tr>
+</table>
+
+<h3 id="docsize-event">DocumentSizeEvent</h3>
+
+<table data-id='events'>
 	<tr>
-		<td><code><b>move</b></code></td>
-		<td><code><a href="#event">Event</a></code></td>
-		<td>Fired when the marker is moved via setLatLng. Old and new coordinates are included in event arguments as <code>oldLatLng, latlng</code>.</td>
+		<th class="width100">property</th>
+		<th>type</th>
+		<th>description</th>
 	</tr>
 	<tr>
-		<td><code><b>add</b></code></td>
-		<td><code><a href="#event">Event</a></code></td>
-		<td>Fired when the marker is added to the map.</td>
+		<td><code><b>x</b></code></td>
+		<td><code>Number</code></td>
+		<td>Document width in pixels.</td>
 	</tr>
 	<tr>
-		<td><code><b>remove</b></code></td>
-		<td><code><a href="#event">Event</a></code></td>
-		<td>Fired when the marker is removed from the map.</td>
+		<td><code><b>y</b></code></td>
+		<td><code>Number</code></td>
+		<td>Document height in pixels.</td>
+	</tr>
+</table>
+
+<h3 id="updatescrolloffset-event">UpdateScrollOffsetEvent</h3>
+
+<table data-id='events'>
+	<tr>
+		<th class="width100">property</th>
+		<th>type</th>
+		<th>description</th>
 	</tr>
 	<tr>
-		<td><code><b>popupopen</b></code></td>
-		<td><code><a href="#popup-event">PopupEvent</a></code></td>
-		<td>Fired when a popup bound to the marker is open.</td>
+		<td><code><b>x</b></code></td>
+		<td><code>Number</code></td>
+		<td>Difference in pixels between the document's left border and view's left border.</td>
 	</tr>
 	<tr>
-		<td><code><b>popupclose</b></code></td>
-		<td><code><a href="#popup-event">PopupEvent</a></code></td>
-		<td>Fired when a popup bound to the marker is closed.</td>
+		<td><code><b>y</b></code></td>
+		<td><code>Number</code></td>
+		<td>Difference in pixels between the document's top border and view's top border.</td>
 	</tr>
 </table>
 
-<h3>Methods</h3>
-
-<p>In addition to <a href="#layers">shared layer methods</a> like <code>addTo()</code> and <code>remove()</code> and <a href="#popups">popup methods</a> like <code>bindPopup()</code> you can also use the following methods:</p>
+<h3 id="scrollto-event">ScrollToEvent</h3>
 
-<table data-id='marker'>
+<table data-id='events'>
 	<tr>
-		<th class="width250">Method</th>
-		<th>Returns</th>
-		<th>Description</th>
+		<th class="width100">property</th>
+		<th>type</th>
+		<th>description</th>
 	</tr>
 	<tr>
-		<td><code><b>getLatLng</b>()</code></td>
-		<td><code><a href="#latlng">LatLng</a></code></td>
-		<td>Returns the current geographical position of the marker.</td>
+		<td><code><b>x</b></code></td>
+		<td><code>Number</code></td>
+		<td>View's left border position in pixels.</td>
 	</tr>
 	<tr>
-		<td><code><b>setLatLng</b>(
-			<nobr><<a href="#latlng">LatLng</a>> <i>latlng</i> )</nobr>
-		</code></td>
-
-		<td><code><span class="keyword">this</span></code></td>
-		<td>Changes the marker position to the given point.</td>
+		<td><code><b>y</b></code></td>
+		<td><code>Number</code></td>
+		<td>View's top border position in pixels.</td>
 	</tr>
-	<tr>
-		<td><code><b>setIcon</b>(
-			<nobr><<a href="#icon">Icon</a>> <i>icon</i> )</nobr>
-		</code></td>
+</table>
 
-		<td><code><span class="keyword">this</span></code></td>
-		<td>Changes the marker icon.</td>
-	</tr>
-	<tr>
-		<td><code><b>setZIndexOffset</b>(
-			<nobr><Number> <i>offset</i> )</nobr>
-		</code></td>
+<h3 id="scrollby-event">ScrollByEvent</h3>
 
-		<td><code><span class="keyword">this</span></code></td>
-		<td>Changes the <a href="#marker-zindexoffset">zIndex offset</a> of the marker.</td>
-	</tr>
+<table data-id='events'>
 	<tr>
-		<td><code><b>setOpacity</b>(
-			<nobr><Number> <i>opacity</i> )</nobr>
-		</code></td>
-		<td><code><span class="keyword">this</span></code></td>
-		<td>Changes the opacity of the marker.</td>
+		<th class="width100">property</th>
+		<th>type</th>
+		<th>description</th>
 	</tr>
 	<tr>
-		<td><code><b>update</b>()</nobr>
-		</code></td>
-
-		<td><code><span class="keyword">this</span></code></td>
-		<td>Updates the marker position, useful if coordinates of its <code>latLng</code> object were changed directly.</td>
+		<td><code><b>x</b></code></td>
+		<td><code>Number</code></td>
+		<td>Scroll right by x pixels, or left if negative.</td>
 	</tr>
-	<tr id="marker-togeojson">
-		<td><code><b>toGeoJSON</b>()</code></td>
-		<td><code>Object</code></td>
-		<td>Returns a <a href="http://en.wikipedia.org/wiki/GeoJSON">GeoJSON</a> representation of the marker (GeoJSON Point Feature).</td>
+	<tr>
+		<td><code><b>y</b></code></td>
+		<td><code>Number</code></td>
+		<td>Scroll down by y pixels, or up if negative.</td>
 	</tr>
 </table>
 
-<h3 id="marker-interaction-handlers">Interaction handlers</h3>
-
-<p>Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see <a href="#handler">IHandler</a> methods). Example:</p>
-
-<pre><code class="javascript">marker.dragging.disable();</code></pre>
+<h3 id="pagenumberchanged-event">PageNumberChangedEvent</h3>
 
-<table data-id='marker'>
+<table data-id='events'>
 	<tr>
-		<th class="width100">Property</th>
-		<th class="width100">Type</th>
-		<th>Description</th>
+		<th class="width100">property</th>
+		<th>type</th>
+		<th>description</th>
 	</tr>
 	<tr>
-		<td>dragging</td>
-		<td><a href="#handler"><code>IHandler</code></a></td>
-		<td>Marker dragging handler (by both mouse and touch).</td>
+		<td><code><b>currentPage</b></code></td>
+		<td><code>Number</code></td>
+		<td>The current page in the document.</td>
 	</tr>
-</table>
-
-
-
-<h2 id="popup">Popup</h2>
-
-<p>Used to open popups in certain places of the map. Use <a href="#map-openpopup">Map#openPopup</a> to open popups while making sure that only one popup is open at one time (recommended for usability), or use <a href="#map-addlayer">Map#addLayer</a> to open as many as you want.</p>
-
-<h3>Usage example</h3>
-<p>If you want to just bind a popup to marker click and then open it, it's really easy:</p>
-<pre><code class="javascript">marker.bindPopup(popupContent).openPopup();</code></pre>
-<p>Path overlays like polylines also have a <code>bindPopup</code> method. Here's a more complicated way to open a popup on a map:</p>
-
-<pre><code class="javascript">var popup = L.popup()
-	.setLatLng(latlng)
-	.setContent('<p>Hello world!<br />This is a nice popup.</p>')
-	.openOn(map);</code></pre>
-
-<h3>Creation</h3>
-
-<table data-id='popup'>
 	<tr>
-		<th>Factory</th>
-
-		<th>Description</th>
+		<td><code><b>pages</b></code></td>
+		<td><code>Number</code></td>
+		<td>The number of pages.</td>
 	</tr>
 	<tr>
-		<td><code><b>L.popup</b>(
-			<nobr><<a href="#popup-options">Popup options</a>> <i>options?</i>,</nobr>
-			<nobr><<a href="#layer">Layer</a>> <i>source?</i> )</nobr>
-		</code></td>
-
-
-		<td>Instantiates a Popup object given an optional options object that describes its appearance and location and an optional source object that is used to tag the popup with a reference to the Layer to which it refers.</td>
+		<td><code><b>docType</b></code></td>
+		<td><code><a href="#documenttype-values">DocumentTypeValues</a></code></td>
+		<td>The document type.</td>
 	</tr>
 </table>
 
-<h3 id="popup-options">Options</h3>
+<h3 id="error-event">ErrorEvent</h3>
 
-<table data-id='popup'>
+<table data-id='events'>
 	<tr>
-		<th>Option</th>
-		<th>Type</th>
-		<th>Default</th>
-		<th>Description</th>
+		<th class="width100">property</th>
+		<th>type</th>
+		<th>description</th>
 	</tr>
 	<tr>
-		<td><code><b>maxWidth</b></code></td>
-		<td><code>Number</code></td>
-		<td><code><span class="number">300</span></code></td>
-		<td>Max width of the popup.</td>
+		<td><code><b>msg</b></code></td>
+		<td><code>String</code></td>
+		<td>If present, the error message.</td>
 	</tr>
 	<tr>
-		<td><code><b>minWidth</b></code></td>
-		<td><code>Number</code></td>
-		<td><code><span class="number">50</span></code></td>
-		<td>Min width of the popup.</td>
+		<td><code><b>cmd</b></code></td>
+		<td><code>String</code></td>
+		<td>If present, the server command that caused the error.</td>
 	</tr>
 	<tr>
-		<td><code><b>maxHeight</b></code></td>
-		<td><code>Number</code></td>
-		<td><code><span class="literal">null</span></code></td>
-		<td>If set, creates a scrollable container of the given height inside a popup if its content exceeds it.</td>
-	</tr>
-	<tr>
-		<td><code><b>autoPan</b></code></td>
-		<td><code>Boolean</code></td>
-		<td><code><span class="literal">true</span></code></td>
-		<td>Set it to <code><span class="literal">false</span></code> if you don't want the map to do panning animation to fit the opened popup.</td>
+		<td><code><b>kind</b></code></td>
+		<td><code>String</code></td>
+		<td>If present, the kind of error associated with the command.</td>
 	</tr>
+</table>
+
+<h3 id="updatetoolbarcommandvalues-event">UpdateToolbarCommandValuesEvent</h3>
+
+<table data-id='events'>
 	<tr>
-		<td><code><b>keepInView</b></code></td>
-		<td><code>Boolean</code></td>
-		<td><code><span class="literal">false</span></code></td>
-		<td>Set it to <code><span class="literal">true</span></code> if you want to prevent users from panning the popup off of the screen while it is open.</td>
+		<th class="width100">property</th>
+		<th>type</th>
+		<th>description</th>
 	</tr>
 	<tr>
-		<td><code><b>closeButton</b></code></td>
-		<td><code>Boolean</code></td>
-		<td><code><span class="literal">true</span></code></td>
-		<td>Controls the presense of a close button in the popup.</td>
+		<td><code><b>commandName</b></code></td>
+		<td><code><a href="#toolbarcommand-values">ToolbarCommandValues</a></code></td>
+		<td>UNO command.</td>
 	</tr>
 	<tr>
-		<td><code><b>offset</b></code></td>
-		<td><code><a href="#point">Point</a></code></td>
-		<td><code><nobr>Point(<span class="number">0</span>, <span class="number">6</span>)</nobr>
-		</code></td>
-		<td>The offset of the popup position. Useful to control the anchor of the popup when opening it on some overlays.</td>
+		<td><code><b>commandValues</b></code></td>
+		<td><code>Object</code></td>
+		<td>JSON mapping of the possible values.</td>
 	</tr>
+</table>
+
+<h2 id="loleaflet-object-values">Object values</h2>
+

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list