From f9069144e5e140dc76815c9c2d794339d17faa43 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 13 Feb 2024 11:53:40 +0400 Subject: [PATCH] Support albums layout. --- Telegram/Resources/iv_html/page.css | 110 ++++++-- Telegram/Resources/iv_html/page.js | 30 ++ Telegram/SourceFiles/iv/iv_controller.cpp | 7 + Telegram/SourceFiles/iv/iv_controller.h | 1 + Telegram/SourceFiles/iv/iv_instance.cpp | 35 +++ Telegram/SourceFiles/iv/iv_prepare.cpp | 320 ++++++++++++++++++---- 6 files changed, 431 insertions(+), 72 deletions(-) diff --git a/Telegram/Resources/iv_html/page.css b/Telegram/Resources/iv_html/page.css index 41a9709ca..58ac7a6d1 100644 --- a/Telegram/Resources/iv_html/page.css +++ b/Telegram/Resources/iv_html/page.css @@ -499,7 +499,7 @@ img, video, iframe { max-width: 100%; - max-height: 400px; + max-height: 480px; vertical-align: top; } video { @@ -703,24 +703,37 @@ figure.slideshow > figure figcaption a:hover { opacity: 1; } +figure.collage-wrap { + margin: 0px 12px; +} +figure.collage-wrap figcaption { + padding: 6px 6px 0px; +} figure.collage { - margin: -2px 16px; - text-align: left; + overflow: hidden; + border-radius: 6px; } -figure.collage > figure { - display: inline-block; - vertical-align: top; - width: calc(25% - 4px); - margin: 2px; - box-sizing: border-box; +figure.collage .photo-wrap, +figure.collage .video-wrap { + position: absolute; } -figure.collage > figure > i { - background: no-repeat center; +figure.collage .photo-wrap .photo { background-size: cover; - display: inline-block; - vertical-align: top; - width: 100%; - padding-top: 100%; +} +figure.collage .video-wrap video { + object-fit: cover; + position: absolute; + top: 50%; + left: 50%; + width: auto; + height: auto; + min-width: 100%; + min-height: 100%; + transform: translate(-50%, -50%); +} +figure.collage .video-wrap .video-small, +video[autoplay] { + pointer-events: none; } figure.table-wrap { @@ -983,13 +996,74 @@ section.channel > a > h4 { display: block; margin: 0 auto; } -.photo-wrap { +.photo-wrap, +.video-wrap { width: 100%; - background-size: 100%; margin: 0 auto; + position: relative; + overflow: hidden; +} +.photo-bg, +.video-bg { + background-size: cover; + background-position: center; + background-repeat: no-repeat; + position: absolute; + filter: blur(16px); + width: 100%; + height: 100%; +} +.video-bg, +video { + position: absolute; + top: 0px; } .photo { - background-size: 100%; + position: relative; + background-size: contain; + background-position: center; + background-repeat: no-repeat; +} +.photo, +video { + opacity: 0; + transition: opacity 300ms ease-in-out; +} +.photo.loaded, +video.loaded { + opacity: 1; +} +.video-play-outer { + position: relative; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} +.video-play { + position: relative; + width: 48px; + height: 0; + padding-top: 48px; + max-width: 48px; + max-height: 48px; + background-color: rgba(0, 0, 0, 0.34); + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; +} +.video-play::before { + content: ''; + position: absolute; + margin: -48px -4px 0px 0px; + width: 0; + height: 0; + border-style: solid; + border-width: 10px 0 10px 16px; + border-color: transparent transparent transparent white; } .toast { diff --git a/Telegram/Resources/iv_html/page.js b/Telegram/Resources/iv_html/page.js index c38bd1119..181a11498 100644 --- a/Telegram/Resources/iv_html/page.js +++ b/Telegram/Resources/iv_html/page.js @@ -226,6 +226,36 @@ var IV = { IV.stopRipples(e.currentTarget); }); } + const photos = document.getElementsByClassName('photo'); + for (let i = 0; i < photos.length; ++i) { + const photo = photos[i]; + if (photo.classList.contains('loaded')) { + continue; + } + + const url = photo.style.backgroundImage; + if (!url || url.length < 7) { + continue; + } + var img = new Image(); + img.onload = function () { + photo.classList.add('loaded'); + } + img.src = url.substr(5, url.length - 7); + if (img.complete) { + img.onload(); + } + } + const videos = document.getElementsByTagName('video'); + for (let i = 0; i < videos.length; ++i) { + const video = videos[i]; + if (video.classList.contains('loaded')) { + continue; + } + video.addEventListener('canplay', function () { + video.classList.add('loaded'); + }); + } IV.notify({ event: 'ready' }); }, showTooltip: function (text) { diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp index 8dae7db07..dca23189c 100644 --- a/Telegram/SourceFiles/iv/iv_controller.cpp +++ b/Telegram/SourceFiles/iv/iv_controller.cpp @@ -489,6 +489,7 @@ void Controller::processLink(const QString &url, const QString &context) { const auto channelPrefix = u"channel"_q; const auto joinPrefix = u"join_link"_q; const auto webpagePrefix = u"webpage"_q; + const auto viewerPrefix = u"viewer"_q; if (context.startsWith(channelPrefix)) { _events.fire({ .type = Event::Type::OpenChannel, @@ -505,6 +506,12 @@ void Controller::processLink(const QString &url, const QString &context) { .url = url, .context = context.mid(webpagePrefix.size()), }); + } else if (context.startsWith(viewerPrefix)) { + _events.fire({ + .type = Event::Type::OpenMedia, + .url = url, + .context = context.mid(viewerPrefix.size()), + }); } else if (context.isEmpty()) { _events.fire({ .type = Event::Type::OpenLink, .url = url }); } diff --git a/Telegram/SourceFiles/iv/iv_controller.h b/Telegram/SourceFiles/iv/iv_controller.h index 9ab5e7e05..f0b77276e 100644 --- a/Telegram/SourceFiles/iv/iv_controller.h +++ b/Telegram/SourceFiles/iv/iv_controller.h @@ -43,6 +43,7 @@ public: OpenPage, OpenLink, OpenLinkExternal, + OpenMedia, }; Type type = Type::Close; QString url; diff --git a/Telegram/SourceFiles/iv/iv_instance.cpp b/Telegram/SourceFiles/iv/iv_instance.cpp index 68e255925..b4539da6e 100644 --- a/Telegram/SourceFiles/iv/iv_instance.cpp +++ b/Telegram/SourceFiles/iv/iv_instance.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "main/session/session_show.h" #include "media/streaming/media_streaming_loader.h" +#include "media/view/media_view_open_common.h" #include "storage/file_download.h" #include "storage/storage_domain.h" #include "ui/boxes/confirm_box.h" @@ -796,6 +797,40 @@ void Instance::show( case Type::OpenLinkExternal: QDesktopServices::openUrl(event.url); break; + case Type::OpenMedia: + if (const auto window = Core::App().activeWindow()) { + const auto current = window->sessionController(); + const auto controller = (current + && ¤t->session() == _shownSession) + ? current + : nullptr; + const auto item = (HistoryItem*)nullptr; + const auto topicRootId = MsgId(0); + if (event.context.startsWith("-photo")) { + const auto id = event.context.mid(6).toULongLong(); + const auto photo = _shownSession->data().photo(id); + if (!photo->isNull()) { + window->openInMediaView({ + controller, + photo, + item, + topicRootId + }); + } + } else if (event.context.startsWith("-video")) { + const auto id = event.context.mid(6).toULongLong(); + const auto video = _shownSession->data().document(id); + if (!video->isNull()) { + window->openInMediaView({ + controller, + video, + item, + topicRootId + }); + } + } + } + break; case Type::OpenPage: case Type::OpenLink: _shownSession->api().request(MTPmessages_GetWebPage( diff --git a/Telegram/SourceFiles/iv/iv_prepare.cpp b/Telegram/SourceFiles/iv/iv_prepare.cpp index 4a46a7ab4..ebfbadd23 100644 --- a/Telegram/SourceFiles/iv/iv_prepare.cpp +++ b/Telegram/SourceFiles/iv/iv_prepare.cpp @@ -12,7 +12,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "iv/iv_data.h" #include "lang/lang_keys.h" #include "ui/image/image_prepare.h" +#include "ui/grouped_layout.h" #include "styles/palette.h" +#include "styles/style_chat.h" #include @@ -34,6 +36,19 @@ struct Photo { struct Document { uint64 id = 0; + int width = 0; + int height = 0; + QByteArray minithumbnail; +}; + +template >> +[[nodiscard]] QByteArray Number(T value) { + return QByteArray::number(value); +} + +template >> +[[nodiscard]] QByteArray Percent(T value) { + return Number(base::SafeRound(value * 10000.) / 100.); }; class Parser final { @@ -50,6 +65,11 @@ private: template [[nodiscard]] QByteArray list(const MTPVector &data); + [[nodiscard]] QByteArray collage( + const QVector &list, + const std::vector &dimensions, + int offset = 0); + [[nodiscard]] QByteArray block(const MTPDpageBlockUnsupported &data); [[nodiscard]] QByteArray block(const MTPDpageBlockTitle &data); [[nodiscard]] QByteArray block(const MTPDpageBlockSubtitle &data); @@ -64,8 +84,14 @@ private: [[nodiscard]] QByteArray block(const MTPDpageBlockList &data); [[nodiscard]] QByteArray block(const MTPDpageBlockBlockquote &data); [[nodiscard]] QByteArray block(const MTPDpageBlockPullquote &data); - [[nodiscard]] QByteArray block(const MTPDpageBlockPhoto &data); - [[nodiscard]] QByteArray block(const MTPDpageBlockVideo &data); + [[nodiscard]] QByteArray block( + const MTPDpageBlockPhoto &data, + const Ui::GroupMediaLayout &layout = {}, + QSize outer = {}); + [[nodiscard]] QByteArray block( + const MTPDpageBlockVideo &data, + const Ui::GroupMediaLayout &layout = {}, + QSize outer = {}); [[nodiscard]] QByteArray block(const MTPDpageBlockCover &data); [[nodiscard]] QByteArray block(const MTPDpageBlockEmbed &data); [[nodiscard]] QByteArray block(const MTPDpageBlockEmbedPost &data); @@ -123,6 +149,9 @@ private: int zoom); [[nodiscard]] QByteArray resource(QByteArray id); + [[nodiscard]] std::vector computeCollageDimensions( + const QVector &items); + const Options _options; base::flat_set _resources; @@ -209,6 +238,60 @@ QByteArray Parser::list(const MTPVector &data) { return result.join(QByteArray()); } +QByteArray Parser::collage( + const QVector &list, + const std::vector &dimensions, + int offset) { + Expects(list.size() == dimensions.size()); + + constexpr auto kPerCollage = 10; + const auto last = (offset + kPerCollage >= int(dimensions.size())); + + auto result = QByteArray(); + auto slice = ((offset > 0) || (dimensions.size() > kPerCollage)) + ? (dimensions + | ranges::views::drop(offset) + | ranges::views::take(kPerCollage) + | ranges::to_vector) + : dimensions; + const auto layout = Ui::LayoutMediaGroup( + slice, + st::historyGroupWidthMax, + st::historyGroupWidthMin, + st::historyGroupSkip); + auto size = QSize(); + for (const auto &part : layout) { + const auto &rect = part.geometry; + size = QSize( + std::max(size.width(), rect.x() + rect.width()), + std::max(size.height(), rect.y() + rect.height())); + } + for (auto i = 0, count = int(layout.size()); i != count; ++i) { + const auto &part = layout[i]; + list[offset + i].match([&](const MTPDpageBlockPhoto &data) { + result += block(data, part, size); + }, [&](const MTPDpageBlockVideo &data) { + result += block(data, part, size); + }, [](const auto &) { + Unexpected("Block type in collage layout."); + }); + } + const auto aspectHeight = size.height() / float64(size.width()); + const auto aspectSkip = st::historyGroupSkip / float64(size.width()); + auto wrapped = tag("figure", { + { "class", "collage" }, + { + "style", + ("padding-top: " + Percent(aspectHeight) + "%; " + + "margin-bottom: " + Percent(last ? 0 : aspectSkip) + "%;") + }, + }, result); + if (offset + kPerCollage < int(dimensions.size())) { + wrapped += collage(list, dimensions, offset + kPerCollage); + } + return wrapped; +} + QByteArray Parser::block(const MTPDpageBlockUnsupported &data) { return "Unsupported."_q; } @@ -289,40 +372,54 @@ QByteArray Parser::block(const MTPDpageBlockPullquote &data) { rich(data.vtext()) + cite); } -QByteArray Parser::block(const MTPDpageBlockPhoto &data) { +QByteArray Parser::block( + const MTPDpageBlockPhoto &data, + const Ui::GroupMediaLayout &layout, + QSize outer) { + const auto collage = !layout.geometry.isEmpty(); const auto photo = photoById(data.vphoto_id().v); if (!photo.id) { return "Photo not found."; } const auto src = photoFullUrl(photo); auto wrapStyle = QByteArray(); - if (photo.width) { - wrapStyle += "max-width:" + QByteArray::number(photo.width) + "px"; + if (collage) { + const auto wcoef = 1. / outer.width(); + const auto hcoef = 1. / outer.height(); + wrapStyle += "left: " + Percent(layout.geometry.x() * wcoef) + "%; " + + "top: " + Percent(layout.geometry.y() * hcoef) + "%; " + + "width: " + Percent(layout.geometry.width() * wcoef) + "%; " + + "height: " + Percent(layout.geometry.height() * hcoef) + "%"; + } else if (photo.width) { + wrapStyle += "max-width:" + Number(photo.width) + "px"; } + const auto dimension = collage + ? (layout.geometry.height() / float64(layout.geometry.width())) + : (photo.width && photo.height) + ? (photo.height / float64(photo.width)) + : (3 / 4.); + const auto paddingTop = collage + ? Percent(dimension) + "%" + : "calc(min(480px, " + Percent(dimension) + "%))"; + const auto style = "background-image:url('" + src + "');" + "padding-top: " + paddingTop + ";"; + auto inner = tag("div", { + { "class", "photo" }, + { "style", style } }); const auto minithumb = Images::ExpandInlineBytes(photo.minithumbnail); if (!minithumb.isEmpty()) { const auto image = Images::Read({ .content = minithumb }); - wrapStyle += ";background-image:url('data:image/jpeg;base64," - + minithumb.toBase64() - + "');"; + inner = tag("div", { + { "class", "photo-bg" }, + { "style", "background-image:url('data:image/jpeg;base64," + + minithumb.toBase64() + + "');" }, + }) + inner; } - const auto dimension = (photo.width && photo.height) - ? (photo.height / float64(photo.width)) - : (3 / 4.); - const auto paddingTopPercent = int(base::SafeRound(dimension * 100)); - const auto style = "background-image:url('" + src + "');" - "padding-top:" + QByteArray::number(paddingTopPercent) + "%"; - const auto inner = tag("div", { - { "class", "photo" }, - { "style", style } }); - auto result = tag( - "div", - { { "class", "photo-wrap" }, { "style", wrapStyle } }, - inner); - if (const auto url = data.vurl()) { - result = tag("a", { { "href", utf(*url) } }, result); - } - auto attributes = Attributes(); + auto attributes = Attributes{ + { "class", "photo-wrap" }, + { "style", wrapStyle } + }; if (_captionAsTitle) { const auto caption = plain(data.vcaption().data().vtext()); const auto credit = plain(data.vcaption().data().vtext()); @@ -332,34 +429,89 @@ QByteArray Parser::block(const MTPDpageBlockPhoto &data) { : (caption + credit); attributes.push_back({ "title", title }); } - } else { + } + auto result = tag("div", attributes, inner); + + const auto href = data.vurl() + ? utf(*data.vurl()) + : photoFullUrl(photo); + const auto id = Number(photo.id); + result = tag("a", { + { "href", href }, + { "data-context", data.vurl() ? QByteArray() : "viewer-photo" + id }, + }, result); + if (!_captionAsTitle) { result += caption(data.vcaption()); } - return tag("figure", attributes, result); + return result; } -QByteArray Parser::block(const MTPDpageBlockVideo &data) { +QByteArray Parser::block( + const MTPDpageBlockVideo &data, + const Ui::GroupMediaLayout &layout, + QSize outer) { + const auto collage = !layout.geometry.isEmpty(); + const auto collageSmall = collage + && (layout.geometry.width() < outer.width()); const auto video = documentById(data.vvideo_id().v); if (!video.id) { return "Video not found."; } const auto src = documentFullUrl(video); auto vattributes = Attributes{}; + if (collageSmall) { + vattributes.push_back({ "class", "video-small" }); + } if (data.is_autoplay()) { vattributes.push_back({ "preload", "auto" }); vattributes.push_back({ "autoplay", std::nullopt }); - } else { + } else if (!collageSmall) { vattributes.push_back({ "controls", std::nullopt }); } if (data.is_loop()) { vattributes.push_back({ "loop", std::nullopt }); } vattributes.push_back({ "muted", std::nullopt }); - auto result = tag( + auto inner = tag( "video", vattributes, tag("source", { { "src", src }, { "type", "video/mp4" } })); - auto attributes = Attributes(); + if (collageSmall) { + inner += tag( + "div", + { { "class", "video-play-outer" } }, + tag("div", { { "class", "video-play" } })); + } + const auto minithumb = Images::ExpandInlineBytes(video.minithumbnail); + if (!minithumb.isEmpty()) { + const auto image = Images::Read({ .content = minithumb }); + inner = tag("div", { + { "class", "video-bg" }, + { "style", "background-image:url('data:image/jpeg;base64," + + minithumb.toBase64() + + "');" }, + }) + inner; + } + auto wrapStyle = QByteArray(); + if (collage) { + const auto wcoef = 1. / outer.width(); + const auto hcoef = 1. / outer.height(); + wrapStyle += "left: " + Percent(layout.geometry.x() * wcoef) + "%; " + + "top: " + Percent(layout.geometry.y() * hcoef) + "%; " + + "width: " + Percent(layout.geometry.width() * wcoef) + "%; " + + "height: " + Percent(layout.geometry.height() * hcoef) + "%; "; + } else { + const auto dimension = (video.width && video.height) + ? (video.height / float64(video.width)) + : (3 / 4.); + wrapStyle += "padding-top: calc(min(480px, " + + Percent(dimension) + + "%));"; + } + auto attributes = Attributes{ + { "class", "video-wrap" }, + { "style", wrapStyle }, + }; if (_captionAsTitle) { const auto caption = plain(data.vcaption().data().vtext()); const auto credit = plain(data.vcaption().data().vtext()); @@ -369,10 +521,20 @@ QByteArray Parser::block(const MTPDpageBlockVideo &data) { : (caption + credit); attributes.push_back({ "title", title }); } - } else { + } + auto result = tag("div", attributes, inner); + if (data.is_autoplay() || collageSmall) { + const auto id = Number(video.id); + const auto href = resource("video" + id); + result = tag("a", { + { "href", href }, + { "data-context", "viewer-video" + id }, + }, result); + } + if (!_captionAsTitle) { result += caption(data.vcaption()); } - return tag("figure", attributes, result); + return result; } QByteArray Parser::block(const MTPDpageBlockCover &data) { @@ -394,13 +556,12 @@ QByteArray Parser::block(const MTPDpageBlockEmbed &data) { eclass = "nowide"; } else if (data.is_full_width() || !data.vw()->v) { width = "100%"; - height = QByteArray::number(data.vh()->v) + "px"; + height = Number(data.vh()->v) + "px"; iframeWidth = "100%"; iframeHeight = height; } else { - const auto percent = data.vh()->v * 100 / data.vw()->v; - width = QByteArray::number(data.vw()->v) + "px"; - height = QByteArray::number(percent) + "%"; + width = Number(data.vw()->v) + "px"; + height = Percent(data.vh()->v / float64(data.vw()->v)) + "%"; } auto attributes = Attributes(); if (autosize) { @@ -468,11 +629,18 @@ QByteArray Parser::block(const MTPDpageBlockEmbedPost &data) { } QByteArray Parser::block(const MTPDpageBlockCollage &data) { - auto result = tag( + const auto &items = data.vitems().v; + const auto dimensions = computeCollageDimensions(items); + if (dimensions.empty()) { + return tag( + "figure", + tag("figure", list(data.vitems())) + caption(data.vcaption())); + } + + return tag( "figure", - { { "class", "collage" } }, - list(data.vitems())); - return tag("figure", result + caption(data.vcaption())); + { { "class", "collage-wrap" } }, + collage(items, dimensions) + caption(data.vcaption())); } QByteArray Parser::block(const MTPDpageBlockSlideshow &data) { @@ -482,7 +650,7 @@ QByteArray Parser::block(const MTPDpageBlockSlideshow &data) { auto attributes = Attributes{ { "type", "radio" }, { "name", "s" }, - { "value", QByteArray::number(i) }, + { "value", Number(i) }, { "onchange", "return IV.slideshowSlide(this);" }, }; if (!i) { @@ -509,7 +677,7 @@ QByteArray Parser::block(const MTPDpageBlockChannel &data) { auto name = QByteArray(); auto username = QByteArray(); auto id = data.vchannel().match([](const auto &data) { - return QByteArray::number(data.vid().v); + return Number(data.vid().v); }); data.vchannel().match([&](const MTPDchannel &data) { if (const auto has = data.vusername()) { @@ -658,7 +826,7 @@ QByteArray Parser::block(const MTPDpageRelatedArticle &data) { } const auto webpageId = data.vwebpage_id().v; const auto context = webpageId - ? ("webpage" + QByteArray::number(webpageId)) + ? ("webpage" + Number(webpageId)) : QByteArray(); return tag("a", { { "class", "related-link" }, @@ -690,10 +858,10 @@ QByteArray Parser::block(const MTPDpageTableCell &data) { } auto attributes = Attributes{ { "style", style } }; if (const auto cs = data.vcolspan()) { - attributes.push_back({ "colspan", QByteArray::number(cs->v) }); + attributes.push_back({ "colspan", Number(cs->v) }); } if (const auto rs = data.vrowspan()) { - attributes.push_back({ "rowspan", QByteArray::number(rs->v) }); + attributes.push_back({ "rowspan", Number(rs->v) }); } return tag(data.is_header() ? "th" : "td", attributes, text); } @@ -783,10 +951,10 @@ QByteArray Parser::rich(const MTPRichText &text) { { "src", documentFullUrl(image) }, }; if (const auto width = data.vw().v) { - attributes.push_back({ "width", QByteArray::number(width) }); + attributes.push_back({ "width", Number(width) }); } if (const auto height = data.vh().v) { - attributes.push_back({ "height", QByteArray::number(height) }); + attributes.push_back({ "height", Number(height) }); } return tag("img", attributes); }, [&](const MTPDtextBold &data) { @@ -802,7 +970,7 @@ QByteArray Parser::rich(const MTPRichText &text) { }, [&](const MTPDtextUrl &data) { const auto webpageId = data.vwebpage_id().v; const auto context = webpageId - ? ("webpage" + QByteArray::number(webpageId)) + ? ("webpage" + Number(webpageId)) : QByteArray(); return tag("a", { { "href", utf(data.vurl()) }, @@ -911,6 +1079,24 @@ Document Parser::parse(const MTPDocument &document) { }; document.match([](const MTPDdocumentEmpty &) { }, [&](const MTPDdocument &data) { + for (const auto &attribute : data.vattributes().v) { + attribute.match([&](const MTPDdocumentAttributeImageSize &data) { + result.width = data.vw().v; + result.height = data.vh().v; + }, [&](const MTPDdocumentAttributeVideo &data) { + result.width = data.vw().v; + result.height = data.vh().v; + }, [](const auto &) {}); + } + if (const auto sizes = data.vthumbs()) { + for (const auto &size : sizes->v) { + size.match([&](const MTPDphotoStrippedSize &data) { + result.minithumbnail = data.vbytes().v; + }, [&](const auto &data) { + }); + } + + } }); return result; } @@ -938,11 +1124,11 @@ Document Parser::documentById(uint64 id) { } QByteArray Parser::photoFullUrl(const Photo &photo) { - return resource("photo/" + QByteArray::number(photo.id)); + return resource("photo/" + Number(photo.id)); } QByteArray Parser::documentFullUrl(const Document &document) { - return resource("document/" + QByteArray::number(document.id)); + return resource("document/" + Number(document.id)); } QByteArray Parser::embedUrl(const QByteArray &html) { @@ -969,9 +1155,9 @@ QByteArray Parser::embedUrl(const QByteArray &html) { QByteArray Parser::mapUrl(const Geo &geo, int width, int height, int zoom) { return resource("map/" + GeoPointId(geo) + "&" - + QByteArray::number(width) + "," - + QByteArray::number(height) + "&" - + QByteArray::number(zoom)); + + Number(width) + "," + + Number(height) + "&" + + Number(zoom)); } QByteArray Parser::resource(QByteArray id) { @@ -982,6 +1168,32 @@ QByteArray Parser::resource(QByteArray id) { return toFolder ? id : ('/' + id); } +std::vector Parser::computeCollageDimensions( + const QVector &items) { + auto result = std::vector(items.size()); + if (items.size() < 2) { + return {}; + } + for (auto i = 0, count = int(items.size()); i != count; ++i) { + auto size = QSize(); + items[i].match([&](const MTPDpageBlockPhoto &data) { + const auto photo = photoById(data.vphoto_id().v); + if (photo.id && photo.width > 0 && photo.height > 0) { + result[i] = QSize(photo.width, photo.height); + } + }, [&](const MTPDpageBlockVideo &data) { + const auto document = documentById(data.vvideo_id().v); + if (document.id && document.width > 0 && document.height > 0) { + result[i] = QSize(document.width, document.height); + } + }, [](const auto &) {}); + if (result[i].isEmpty()) { + return {}; + } + } + return result; +} + } // namespace Prepared Prepare(const Source &source, const Options &options) {