common/Session.cpp | 6 ++ common/Session.hpp | 3 + kit/ChildSession.cpp | 6 +- kit/ChildSession.hpp | 4 + kit/Kit.cpp | 143 +++++++++++++++++++++++++++++++++++++++++++++++-- test/WhiteBoxTests.cpp | 3 - wsd/ClientSession.cpp | 7 ++ wsd/ClientSession.hpp | 1 wsd/DocumentBroker.cpp | 3 + wsd/Storage.cpp | 4 + wsd/Storage.hpp | 4 + wsd/reference.txt | 4 + 12 files changed, 180 insertions(+), 8 deletions(-)
New commits: commit dee39a562c6fd8f1eed3567e7f9e518f1d599bd7 Author: Marco Cecchetti <marco.cecche...@collabora.com> Date: Mon Sep 4 15:40:04 2017 +0200 support for rendering a watermark on each tile Change-Id: I3edccac49a3bcd3d2493d8d7ef3a1ae29307e727 Reviewed-on: https://gerrit.libreoffice.org/41898 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> diff --git a/common/Session.cpp b/common/Session.cpp index 97933c83..c58eb3f0 100644 --- a/common/Session.cpp +++ b/common/Session.cpp @@ -136,6 +136,12 @@ void Session::parseDocOptions(const std::vector<std::string>& tokens, int& part, _lang = tokens[i].substr(strlen("lang=")); ++offset; } + else if (tokens[i].find("watermarkText=") == 0) + { + const std::string watermarkText = tokens[i].substr(strlen("watermarkText=")); + Poco::URI::decode(watermarkText, _watermarkText); + ++offset; + } } if (tokens.size() > offset) diff --git a/common/Session.hpp b/common/Session.hpp index 5a154ee1..99961848 100644 --- a/common/Session.hpp +++ b/common/Session.hpp @@ -157,6 +157,9 @@ protected: /// Extra info per user, mostly mail, avatar, links, etc. std::string _userExtraInfo; + /// In case a watermark has to be rendered on each tile. + std::string _watermarkText; + /// Language for the document based on what the user has in the UI. std::string _lang; }; diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp index 553f6dbf..56075790 100644 --- a/kit/ChildSession.cpp +++ b/kit/ChildSession.cpp @@ -329,7 +329,8 @@ bool ChildSession::loadDocument(const char * /*buffer*/, int /*length*/, const s std::unique_lock<std::recursive_mutex> lock(Mutex); - bool loaded = _docManager.onLoad(getId(), _jailedFilePath, _userName, _docPassword, renderOpts, _haveDocPassword, _lang); + bool loaded = _docManager.onLoad(getId(), _jailedFilePath, _userName, + _docPassword, renderOpts, _haveDocPassword, _lang, _watermarkText); if (!loaded || _viewId < 0) { LOG_ERR("Failed to get LoKitDocument instance."); @@ -400,7 +401,8 @@ bool ChildSession::sendFontRendering(const char* /*buffer*/, int /*length*/, con std::memcpy(output.data(), response.data(), response.size()); Timestamp timestamp; - int width, height; + // renderFont use a default font size (25) when width and height are 0 + int width = 0, height = 0; unsigned char* ptrFont = nullptr; { diff --git a/kit/ChildSession.hpp b/kit/ChildSession.hpp index 446ffca4..c3af96ec 100644 --- a/kit/ChildSession.hpp +++ b/kit/ChildSession.hpp @@ -37,7 +37,8 @@ public: const std::string& docPassword, const std::string& renderOpts, const bool haveDocPassword, - const std::string& lang) = 0; + const std::string& lang, + const std::string& watermarkText) = 0; /// Unload a client session, which unloads the document /// if it is the last and only. @@ -143,6 +144,7 @@ public: const std::string& getViewUserId() const { return _userId; } const std::string& getViewUserName() const { return _userName; } const std::string& getViewUserExtraInfo() const { return _userExtraInfo; } + const std::string& getWatermarkText() const { return _watermarkText; } void updateSpeed(); int getSpeed(); diff --git a/kit/Kit.cpp b/kit/Kit.cpp index a5a2be25..63ebdbe1 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -479,6 +479,119 @@ public: } }; +class Watermark +{ +public: + Watermark(std::shared_ptr<lok::Document> loKitDoc, std::string text) + : _loKitDoc(loKitDoc) + , _text(text) + , _font("Liberation Sans") + , _width(0) + , _height(0) + , _color{64, 64, 64} + , _alphaLevel(0.2) + , _pixmap(nullptr) + { + } + + ~Watermark() + { + if (_pixmap) + std::free(_pixmap); + } + + void blending(unsigned char* tilePixmap, + int offsetX, int offsetY, + int tilesPixmapWidth, int tilesPixmapHeight, + int tileWidth, int tileHeight, + LibreOfficeKitTileMode mode) + { + // set requested watermark size a little bit smaller than tile size + int width = tileWidth * 0.9; + int height = tileHeight * 0.9; + + const unsigned char* pixmap = getPixmap(width, height); + + if (pixmap && tilePixmap) + { + unsigned int pixmapSize = tilesPixmapWidth * tilesPixmapHeight * 4; + int maxX = std::min(tileWidth, _width); + int maxY = std::min(tileHeight, _height); + + // center watermark + offsetX += (tileWidth - maxX) / 2; + offsetY += (tileHeight - maxY) / 2; + + for (int y = 0; y < maxY; ++y) + { + for (int x = 0; x < maxX; ++x) + { + unsigned int i = (y * _width + x) * 4; + unsigned int alpha = pixmap[i + 3]; + if (alpha) + { + for (int h = 0; h < 3; ++h) + { + unsigned int j = ((y + offsetY) * tilesPixmapWidth + (x + offsetX)) * 4 + h; + if (j < pixmapSize) + { + unsigned int color = (mode == LOK_TILEMODE_BGRA) ? _color[2 - h] : _color[h]; + + // original alpha blending for smoothing text edges + color = ((color * alpha) + tilePixmap[j] * (255 - alpha)) / 255; + // blending between document tile and watermark + tilePixmap[j] = color * _alphaLevel + tilePixmap[j] * (1 - _alphaLevel); + } + } + } + } + } + } + } + +private: + const unsigned char* getPixmap(int width, int height) + { + if (_pixmap && width == _width && height == _height) + return _pixmap; + + if (_pixmap) + std::free(_pixmap); + + _width = width; + _height = height; + + if (!_loKitDoc) + { + LOG_ERR("Watermark rendering requested without a valid document."); + return nullptr; + } + + // renderFont returns a buffer based on RGBA mode, where r, g, b + // are always set to 0 (black) and the alpha level is 0 everywhere + // except on the text area; the alpha level take into account of + // performing anti-aliasing over the text edges. + _pixmap = _loKitDoc->renderFont(_font.c_str(), _text.c_str(), &_width, &_height); + + if (!_pixmap) + { + LOG_ERR("Watermark: rendering failed."); + } + + return _pixmap; + } + +private: + std::shared_ptr<lok::Document> _loKitDoc; + std::string _text; + std::string _font; + int _width; + int _height; + unsigned char _color[3]; + double _alphaLevel; + unsigned char* _pixmap; +}; + static FILE* ProcSMapsFile = nullptr; /// A document container. @@ -515,6 +628,7 @@ public: _haveDocPassword(false), _isDocPasswordProtected(false), _docPasswordType(PasswordType::ToView), + _docWatermark(), _stop(false), _isLoading(0), _editorId(-1), @@ -708,6 +822,12 @@ public: // Send back the request with all optional parameters given in the request. std::string response = ADD_DEBUG_RENDERID(tile.serialize("tile:")) + "\n"; + int pixelWidth = tile.getWidth(); + int pixelHeight = tile.getHeight(); + + if (_docWatermark) + _docWatermark->blending(pixmap.data(), 0, 0, pixelWidth, pixelHeight, pixelWidth, pixelHeight, mode); + std::vector<char> output; output.reserve(response.size() + pixmapDataSize); output.resize(response.size()); @@ -812,6 +932,15 @@ public: continue; } + int offsetX = positionX * pixelWidth; + int offsetY = positionY * pixelHeight; + + if (_docWatermark) + _docWatermark->blending(pixmap.data(), offsetX, offsetY, + pixmapWidth, pixmapHeight, + tileCombined.getWidth(), tileCombined.getHeight(), + mode); + if (!_pngCache.encodeSubBufferToPNG(pixmap.data(), positionX * pixelWidth, positionY * pixelHeight, pixelWidth, pixelHeight, pixmapWidth, pixmapHeight, output, mode, hash, wireId, oldWireId)) @@ -984,7 +1113,8 @@ private: const std::string& docPassword, const std::string& renderOpts, const bool haveDocPassword, - const std::string& lang) override + const std::string& lang, + const std::string& watermarkText) override { std::unique_lock<std::mutex> lock(_mutex); @@ -1013,7 +1143,7 @@ private: try { - if (!load(session, uri, userName, docPassword, renderOpts, haveDocPassword, lang)) + if (!load(session, uri, userName, docPassword, renderOpts, haveDocPassword, lang, watermarkText)) { return false; } @@ -1255,7 +1385,8 @@ private: const std::string& docPassword, const std::string& renderOpts, const bool haveDocPassword, - const std::string& lang) + const std::string& lang, + const std::string& watermarkText) { const std::string sessionId = session->getId(); @@ -1320,6 +1451,9 @@ private: // Only save the options on opening the document. // No support for changing them after opening a document. _renderOpts = renderOpts; + + if (!watermarkText.empty()) + _docWatermark.reset(new Watermark(_loKitDocument, watermarkText)); } else { @@ -1665,6 +1799,9 @@ private: // Whether password is required to view the document, or modify it PasswordType _docPasswordType; + // Document watermark + std::unique_ptr<Watermark> _docWatermark; + std::atomic<bool> _stop; mutable std::mutex _mutex; diff --git a/test/WhiteBoxTests.cpp b/test/WhiteBoxTests.cpp index 7c9b13f2..665d0bfe 100644 --- a/test/WhiteBoxTests.cpp +++ b/test/WhiteBoxTests.cpp @@ -336,7 +336,8 @@ public: const std::string& /*docPassword*/, const std::string& /*renderOpts*/, const bool /*haveDocPassword*/, - const std::string& /*lang*/) override + const std::string& /*lang*/, + const std::string& /*watermarkText*/) override { return false; } diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index 7a4f42b0..2e455029 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -316,6 +316,13 @@ bool ClientSession::loadDocument(const char* /*buffer*/, int /*length*/, oss << " lang=" << _lang; } + if (!_watermarkText.empty()) + { + std::string encodedWatermarkText; + Poco::URI::encode(_watermarkText, "", encodedWatermarkText); + oss << " watermarkText=" << encodedWatermarkText; + } + if (!_docOptions.empty()) { oss << " options=" << _docOptions; diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp index fd3a05b8..dd2ea0e3 100644 --- a/wsd/ClientSession.hpp +++ b/wsd/ClientSession.hpp @@ -47,6 +47,7 @@ public: void setUserId(const std::string& userId) { _userId = userId; } void setUserName(const std::string& userName) { _userName = userName; } void setUserExtraInfo(const std::string& userExtraInfo) { _userExtraInfo = userExtraInfo; } + void setWatermarkText(const std::string& watermarkText) { _watermarkText = watermarkText; } void setDocumentOwner(const bool documentOwner) { _isDocumentOwner = documentOwner; } bool isDocumentOwner() const { return _isDocumentOwner; } diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index 11f40004..9154abab 100644 --- a/wsd/DocumentBroker.cpp +++ b/wsd/DocumentBroker.cpp @@ -424,6 +424,7 @@ bool DocumentBroker::load(const std::shared_ptr<ClientSession>& session, const s // Call the storage specific fileinfo functions std::string userid, username; std::string userExtraInfo; + std::string watermarkText; std::chrono::duration<double> getInfoCallDuration(0); WopiStorage* wopiStorage = dynamic_cast<WopiStorage*>(_storage.get()); if (wopiStorage != nullptr) @@ -432,6 +433,7 @@ bool DocumentBroker::load(const std::shared_ptr<ClientSession>& session, const s userid = wopifileinfo->_userid; username = wopifileinfo->_username; userExtraInfo = wopifileinfo->_userExtraInfo; + watermarkText = wopifileinfo->_watermarkText; if (!wopifileinfo->_userCanWrite || LOOLWSD::IsViewFileExtension(wopiStorage->getFileExtension())) @@ -509,6 +511,7 @@ bool DocumentBroker::load(const std::shared_ptr<ClientSession>& session, const s session->setUserId(userid); session->setUserName(username); session->setUserExtraInfo(userExtraInfo); + session->setWatermarkText(watermarkText); // Basic file information was stored by the above getWOPIFileInfo() or getLocalFileInfo() calls const auto fileInfo = _storage->getFileInfo(); diff --git a/wsd/Storage.cpp b/wsd/Storage.cpp index 7cf22e56..c6b4339f 100644 --- a/wsd/Storage.cpp +++ b/wsd/Storage.cpp @@ -540,6 +540,7 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const Au std::string userId; std::string userName; std::string userExtraInfo; + std::string watermarkText; bool canWrite = false; bool enableOwnerTermination = false; std::string postMessageOrigin; @@ -562,6 +563,7 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const Au getWOPIValue(object, "UserId", userId); getWOPIValue(object, "UserFriendlyName", userName); getWOPIValue(object, "UserExtraInfo", userExtraInfo); + getWOPIValue(object, "WatermarkText", watermarkText); getWOPIValue(object, "UserCanWrite", canWrite); getWOPIValue(object, "PostMessageOrigin", postMessageOrigin); getWOPIValue(object, "HidePrintOption", hidePrintOption); @@ -583,7 +585,7 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const Au const Poco::Timestamp modifiedTime = iso8601ToTimestamp(lastModifiedTime); _fileInfo = FileInfo({filename, ownerId, modifiedTime, size}); - return std::unique_ptr<WopiStorage::WOPIFileInfo>(new WOPIFileInfo({userId, userName, userExtraInfo, canWrite, postMessageOrigin, hidePrintOption, hideSaveOption, hideExportOption, enableOwnerTermination, disablePrint, disableExport, disableCopy, disableInactiveMessages, callDuration})); + return std::unique_ptr<WopiStorage::WOPIFileInfo>(new WOPIFileInfo({userId, userName, userExtraInfo, watermarkText, canWrite, postMessageOrigin, hidePrintOption, hideSaveOption, hideExportOption, enableOwnerTermination, disablePrint, disableExport, disableCopy, disableInactiveMessages, callDuration})); } /// PutRelativeFile - uri format: http://server/<...>/wopi*/files/<id>/ diff --git a/wsd/Storage.hpp b/wsd/Storage.hpp index 28532e4a..2c619581 100644 --- a/wsd/Storage.hpp +++ b/wsd/Storage.hpp @@ -198,6 +198,7 @@ public: WOPIFileInfo(const std::string& userid, const std::string& username, const std::string& userExtraInfo, + const std::string& watermarkText, const bool userCanWrite, const std::string& postMessageOrigin, const bool hidePrintOption, @@ -211,6 +212,7 @@ public: const std::chrono::duration<double> callDuration) : _userid(userid), _username(username), + _watermarkText(watermarkText), _userCanWrite(userCanWrite), _postMessageOrigin(postMessageOrigin), _hidePrintOption(hidePrintOption), @@ -232,6 +234,8 @@ public: std::string _username; /// Extra info per user, typically mail and other links, as json. std::string _userExtraInfo; + /// In case a watermark has to be rendered on each tile. + std::string _watermarkText; /// If user accessing the file has write permission bool _userCanWrite; /// WOPI Post message property diff --git a/wsd/reference.txt b/wsd/reference.txt index 92d0e3e2..4d06850f 100644 --- a/wsd/reference.txt +++ b/wsd/reference.txt @@ -73,5 +73,9 @@ EnableOwnerTermination If set to true, it allows the document owner (the one with OwnerId = UserId) to send a 'closedocument' message (see protocol.txt) +WatermarkText + If set to a non-empty string, is used for rendering a watermark-like + text on each tile of the document + Note that it is possible to just hide print,save,export options while still being able to access them from WOPI hosts using PostMessage API (see loleaflet/reference.html) _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits