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 15471e1891745fc9ff007b3ca304ab79af7d2764 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/42022 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit 03c82c3dd1268835064cc1a97f9a4c8d7ed4cbba) Reviewed-on: https://gerrit.libreoffice.org/42063 diff --git a/common/Session.cpp b/common/Session.cpp index a1cded5f..c17089e9 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 63872c08..a39d651b 100644 --- a/common/Session.hpp +++ b/common/Session.hpp @@ -155,6 +155,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 7ad0cdd0..bc2ddd31 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."); @@ -398,7 +399,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 7faef0ed..9a01901e 100644 --- a/kit/ChildSession.hpp +++ b/kit/ChildSession.hpp @@ -36,7 +36,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. @@ -139,6 +140,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 loKitCallback(const int type, const std::string& payload); diff --git a/kit/Kit.cpp b/kit/Kit.cpp index 0358e7f9..bdb3ae79 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -426,6 +426,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. @@ -462,6 +575,7 @@ public: _haveDocPassword(false), _isDocPasswordProtected(false), _docPasswordType(PasswordType::ToView), + _docWatermark(), _stop(false), _isLoading(0) { @@ -653,6 +767,12 @@ public: return; } + int pixelWidth = tile.getWidth(); + int pixelHeight = tile.getHeight(); + + if (_docWatermark) + _docWatermark->blending(pixmap.data(), 0, 0, pixelWidth, pixelHeight, pixelWidth, pixelHeight, mode); + if (!_pngCache.encodeBufferToPNG(pixmap.data(), tile.getWidth(), tile.getHeight(), output, mode, hash)) { //FIXME: Return error. @@ -749,6 +869,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)) { @@ -924,7 +1053,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); @@ -953,7 +1083,7 @@ private: try { - if (!load(session, uri, userName, docPassword, renderOpts, haveDocPassword, lang)) + if (!load(session, uri, userName, docPassword, renderOpts, haveDocPassword, lang, watermarkText)) { return false; } @@ -1145,7 +1275,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(); @@ -1210,6 +1341,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 { @@ -1553,6 +1687,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 825ebb4e..a0c83368 100644 --- a/test/WhiteBoxTests.cpp +++ b/test/WhiteBoxTests.cpp @@ -321,7 +321,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 7c22b04d..033a963e 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -290,6 +290,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 0002d615..573e21d4 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 568187d9..9d6bff46 100644 --- a/wsd/DocumentBroker.cpp +++ b/wsd/DocumentBroker.cpp @@ -395,6 +395,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) @@ -403,6 +404,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())) @@ -466,6 +468,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 6eef7e7f..afe69a87 100644 --- a/wsd/Storage.cpp +++ b/wsd/Storage.cpp @@ -484,6 +484,7 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const st std::string userId; std::string userName; std::string userExtraInfo; + std::string watermarkText; bool canWrite = false; bool enableOwnerTermination = false; std::string postMessageOrigin; @@ -510,6 +511,7 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const st 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); @@ -552,7 +554,7 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const st _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})); } /// uri format: http://server/<...>/wopi*/files/<id>/content diff --git a/wsd/Storage.hpp b/wsd/Storage.hpp index 405a8c30..a3fbe637 100644 --- a/wsd/Storage.hpp +++ b/wsd/Storage.hpp @@ -185,6 +185,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, @@ -198,6 +199,7 @@ public: const std::chrono::duration<double> callDuration) : _userid(userid), _username(username), + _watermarkText(watermarkText), _userCanWrite(userCanWrite), _postMessageOrigin(postMessageOrigin), _hidePrintOption(hidePrintOption), @@ -219,6 +221,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 ecc09a36..f6ad3985 100644 --- a/wsd/reference.txt +++ b/wsd/reference.txt @@ -55,5 +55,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