iv_prepare.cpp 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "iv/iv_prepare.h"
  8. #include "base/openssl_help.h"
  9. #include "base/unixtime.h"
  10. #include "iv/iv_data.h"
  11. #include "lang/lang_keys.h"
  12. #include "ui/image/image_prepare.h"
  13. #include "ui/grouped_layout.h"
  14. #include "styles/palette.h"
  15. #include "styles/style_chat.h"
  16. #include <QtCore/QSize>
  17. namespace Iv {
  18. namespace {
  19. struct Attribute {
  20. QByteArray name;
  21. std::optional<QByteArray> value;
  22. };
  23. using Attributes = std::vector<Attribute>;
  24. struct Photo {
  25. uint64 id = 0;
  26. int width = 0;
  27. int height = 0;
  28. QByteArray minithumbnail;
  29. };
  30. struct Document {
  31. uint64 id = 0;
  32. int width = 0;
  33. int height = 0;
  34. QByteArray minithumbnail;
  35. };
  36. template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
  37. [[nodiscard]] QByteArray Number(T value) {
  38. return QByteArray::number(value);
  39. }
  40. template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
  41. [[nodiscard]] QByteArray Percent(T value) {
  42. return Number(base::SafeRound(value * 10000.) / 100.);
  43. };
  44. [[nodiscard]] QByteArray Escape(QByteArray value) {
  45. auto result = QByteArray();
  46. result.reserve(value.size());
  47. for (const auto &ch : value) {
  48. switch (ch) {
  49. case '&': result.append("&amp;"); break;
  50. case '<': result.append("&lt;"); break;
  51. case '>': result.append("&gt;"); break;
  52. case '"': result.append("&quot;"); break;
  53. case '\'': result.append("&apos;"); break;
  54. default: result.append(ch); break;
  55. }
  56. }
  57. return result;
  58. }
  59. [[nodiscard]] QByteArray Date(TimeId date) {
  60. return Escape(langDateTimeFull(base::unixtime::parse(date)).toUtf8());
  61. }
  62. class Parser final {
  63. public:
  64. Parser(const Source &source, const Options &options);
  65. [[nodiscard]] Prepared result();
  66. private:
  67. void process(const Source &source);
  68. void process(const MTPPhoto &photo);
  69. void process(const MTPDocument &document);
  70. template <typename Inner>
  71. [[nodiscard]] QByteArray list(const MTPVector<Inner> &data);
  72. [[nodiscard]] QByteArray collage(
  73. const QVector<MTPPageBlock> &list,
  74. const std::vector<QSize> &dimensions,
  75. int offset = 0);
  76. [[nodiscard]] QByteArray slideshow(
  77. const QVector<MTPPageBlock> &list,
  78. QSize dimensions);
  79. [[nodiscard]] QByteArray block(const MTPDpageBlockUnsupported &data);
  80. [[nodiscard]] QByteArray block(const MTPDpageBlockTitle &data);
  81. [[nodiscard]] QByteArray block(const MTPDpageBlockSubtitle &data);
  82. [[nodiscard]] QByteArray block(const MTPDpageBlockAuthorDate &data);
  83. [[nodiscard]] QByteArray block(const MTPDpageBlockHeader &data);
  84. [[nodiscard]] QByteArray block(const MTPDpageBlockSubheader &data);
  85. [[nodiscard]] QByteArray block(const MTPDpageBlockParagraph &data);
  86. [[nodiscard]] QByteArray block(const MTPDpageBlockPreformatted &data);
  87. [[nodiscard]] QByteArray block(const MTPDpageBlockFooter &data);
  88. [[nodiscard]] QByteArray block(const MTPDpageBlockDivider &data);
  89. [[nodiscard]] QByteArray block(const MTPDpageBlockAnchor &data);
  90. [[nodiscard]] QByteArray block(const MTPDpageBlockList &data);
  91. [[nodiscard]] QByteArray block(const MTPDpageBlockBlockquote &data);
  92. [[nodiscard]] QByteArray block(const MTPDpageBlockPullquote &data);
  93. [[nodiscard]] QByteArray block(
  94. const MTPDpageBlockPhoto &data,
  95. const Ui::GroupMediaLayout &layout = {},
  96. QSize outer = {});
  97. [[nodiscard]] QByteArray block(
  98. const MTPDpageBlockVideo &data,
  99. const Ui::GroupMediaLayout &layout = {},
  100. QSize outer = {});
  101. [[nodiscard]] QByteArray block(const MTPDpageBlockCover &data);
  102. [[nodiscard]] QByteArray block(const MTPDpageBlockEmbed &data);
  103. [[nodiscard]] QByteArray block(const MTPDpageBlockEmbedPost &data);
  104. [[nodiscard]] QByteArray block(const MTPDpageBlockCollage &data);
  105. [[nodiscard]] QByteArray block(const MTPDpageBlockSlideshow &data);
  106. [[nodiscard]] QByteArray block(const MTPDpageBlockChannel &data);
  107. [[nodiscard]] QByteArray block(const MTPDpageBlockAudio &data);
  108. [[nodiscard]] QByteArray block(const MTPDpageBlockKicker &data);
  109. [[nodiscard]] QByteArray block(const MTPDpageBlockTable &data);
  110. [[nodiscard]] QByteArray block(const MTPDpageBlockOrderedList &data);
  111. [[nodiscard]] QByteArray block(const MTPDpageBlockDetails &data);
  112. [[nodiscard]] QByteArray block(
  113. const MTPDpageBlockRelatedArticles &data);
  114. [[nodiscard]] QByteArray block(const MTPDpageBlockMap &data);
  115. [[nodiscard]] QByteArray block(const MTPDpageRelatedArticle &data);
  116. [[nodiscard]] QByteArray block(const MTPDpageTableRow &data);
  117. [[nodiscard]] QByteArray block(const MTPDpageTableCell &data);
  118. [[nodiscard]] QByteArray block(const MTPDpageListItemText &data);
  119. [[nodiscard]] QByteArray block(const MTPDpageListItemBlocks &data);
  120. [[nodiscard]] QByteArray block(const MTPDpageListOrderedItemText &data);
  121. [[nodiscard]] QByteArray block(
  122. const MTPDpageListOrderedItemBlocks &data);
  123. [[nodiscard]] QByteArray wrap(const QByteArray &content, int views);
  124. [[nodiscard]] QByteArray tag(
  125. const QByteArray &name,
  126. const QByteArray &body = {});
  127. [[nodiscard]] QByteArray tag(
  128. const QByteArray &name,
  129. const Attributes &attributes,
  130. const QByteArray &body = {});
  131. [[nodiscard]] QByteArray utf(const MTPstring &text);
  132. [[nodiscard]] QByteArray utf(const tl::conditional<MTPstring> &text);
  133. [[nodiscard]] QByteArray rich(const MTPRichText &text);
  134. [[nodiscard]] QByteArray caption(const MTPPageCaption &caption);
  135. [[nodiscard]] Photo parse(const MTPPhoto &photo);
  136. [[nodiscard]] Document parse(const MTPDocument &document);
  137. [[nodiscard]] Geo parse(const MTPGeoPoint &geo);
  138. [[nodiscard]] Photo photoById(uint64 id);
  139. [[nodiscard]] Document documentById(uint64 id);
  140. [[nodiscard]] QByteArray photoFullUrl(const Photo &photo);
  141. [[nodiscard]] QByteArray documentFullUrl(const Document &document);
  142. [[nodiscard]] QByteArray embedUrl(const QByteArray &html);
  143. [[nodiscard]] QByteArray mapUrl(
  144. const Geo &geo,
  145. int width,
  146. int height,
  147. int zoom);
  148. [[nodiscard]] QByteArray resource(QByteArray id);
  149. [[nodiscard]] std::vector<QSize> computeCollageDimensions(
  150. const QVector<MTPPageBlock> &items);
  151. [[nodiscard]] QSize computeSlideshowDimensions(
  152. const QVector<MTPPageBlock> &items);
  153. //const Options _options;
  154. const QByteArray _fileOriginPostfix;
  155. base::flat_set<QByteArray> _resources;
  156. Prepared _result;
  157. base::flat_map<uint64, Photo> _photosById;
  158. base::flat_map<uint64, Document> _documentsById;
  159. };
  160. [[nodiscard]] bool IsVoidElement(const QByteArray &name) {
  161. // Thanks https://developer.mozilla.org/en-US/docs/Glossary/Void_element
  162. static const auto voids = base::flat_set<QByteArray>{
  163. "area"_q,
  164. "base"_q,
  165. "br"_q,
  166. "col"_q,
  167. "embed"_q,
  168. "hr"_q,
  169. "img"_q,
  170. "input"_q,
  171. "link"_q,
  172. "meta"_q,
  173. "source"_q,
  174. "track"_q,
  175. "wbr"_q,
  176. };
  177. return voids.contains(name);
  178. }
  179. [[nodiscard]] QByteArray ArrowSvg(bool left) {
  180. const auto rotate = QByteArray(left ? "180" : "0");
  181. return R"(
  182. <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
  183. <path
  184. d="M14.9972363,18 L9.13865768,12.1414214 C9.06055283,12.0633165 9.06055283,11.9366835 9.13865768,11.8585786 L14.9972363,6 L14.9972363,6"
  185. transform="translate(11.997236, 12) scale(-1, -1) rotate()" + rotate + ") translate(-11.997236, -12)" + R"(">
  186. </path>
  187. </svg>)";
  188. }
  189. Parser::Parser(const Source &source, const Options &options)
  190. : /*_options(options)
  191. , */_fileOriginPostfix('/' + Number(source.pageId)) {
  192. process(source);
  193. _result.pageId = source.pageId;
  194. _result.name = source.name;
  195. _result.rtl = source.page.data().is_rtl();
  196. const auto views = std::max(
  197. source.page.data().vviews().value_or_empty(),
  198. source.updatedCachedViews);
  199. const auto content = list(source.page.data().vblocks());
  200. _result.content = wrap(content, views);
  201. }
  202. Prepared Parser::result() {
  203. return _result;
  204. }
  205. void Parser::process(const Source &source) {
  206. const auto &data = source.page.data();
  207. for (const auto &photo : data.vphotos().v) {
  208. process(photo);
  209. }
  210. for (const auto &document : data.vdocuments().v) {
  211. process(document);
  212. }
  213. if (source.webpagePhoto) {
  214. process(*source.webpagePhoto);
  215. }
  216. if (source.webpageDocument) {
  217. process(*source.webpageDocument);
  218. }
  219. }
  220. void Parser::process(const MTPPhoto &photo) {
  221. _photosById.emplace(
  222. photo.match([](const auto &data) { return data.vid().v; }),
  223. parse(photo));
  224. }
  225. void Parser::process(const MTPDocument &document) {
  226. _documentsById.emplace(
  227. document.match([](const auto &data) { return data.vid().v; }),
  228. parse(document));
  229. }
  230. template <typename Inner>
  231. QByteArray Parser::list(const MTPVector<Inner> &data) {
  232. auto result = QByteArrayList();
  233. result.reserve(data.v.size());
  234. for (const auto &item : data.v) {
  235. result.append(item.match([&](const auto &data) {
  236. return block(data);
  237. }));
  238. }
  239. return result.join(QByteArray());
  240. }
  241. QByteArray Parser::collage(
  242. const QVector<MTPPageBlock> &list,
  243. const std::vector<QSize> &dimensions,
  244. int offset) {
  245. Expects(list.size() == dimensions.size());
  246. constexpr auto kPerCollage = 10;
  247. const auto last = (offset + kPerCollage >= int(dimensions.size()));
  248. auto result = QByteArray();
  249. auto slice = ((offset > 0) || (dimensions.size() > kPerCollage))
  250. ? (dimensions
  251. | ranges::views::drop(offset)
  252. | ranges::views::take(kPerCollage)
  253. | ranges::to_vector)
  254. : dimensions;
  255. const auto layout = Ui::LayoutMediaGroup(
  256. slice,
  257. st::historyGroupWidthMax,
  258. st::historyGroupWidthMin,
  259. st::historyGroupSkip);
  260. auto size = QSize();
  261. for (const auto &part : layout) {
  262. const auto &rect = part.geometry;
  263. size = QSize(
  264. std::max(size.width(), rect.x() + rect.width()),
  265. std::max(size.height(), rect.y() + rect.height()));
  266. }
  267. for (auto i = 0, count = int(layout.size()); i != count; ++i) {
  268. const auto &part = layout[i];
  269. list[offset + i].match([&](const MTPDpageBlockPhoto &data) {
  270. result += block(data, part, size);
  271. }, [&](const MTPDpageBlockVideo &data) {
  272. result += block(data, part, size);
  273. }, [](const auto &) {
  274. Unexpected("Block type in collage layout.");
  275. });
  276. }
  277. const auto aspectHeight = size.height() / float64(size.width());
  278. const auto aspectSkip = st::historyGroupSkip / float64(size.width());
  279. auto wrapped = tag("figure", {
  280. { "class", "collage" },
  281. {
  282. "style",
  283. ("padding-top: " + Percent(aspectHeight) + "%; "
  284. + "margin-bottom: " + Percent(last ? 0 : aspectSkip) + "%;")
  285. },
  286. }, result);
  287. if (offset + kPerCollage < int(dimensions.size())) {
  288. wrapped += collage(list, dimensions, offset + kPerCollage);
  289. }
  290. return wrapped;
  291. }
  292. QByteArray Parser::slideshow(
  293. const QVector<MTPPageBlock> &list,
  294. QSize dimensions) {
  295. auto result = QByteArray();
  296. for (auto i = 0, count = int(list.size()); i != count; ++i) {
  297. list[i].match([&](const MTPDpageBlockPhoto &data) {
  298. result += block(data, {}, dimensions);
  299. }, [&](const MTPDpageBlockVideo &data) {
  300. result += block(data, {}, dimensions);
  301. }, [](const auto &) {
  302. Unexpected("Block type in collage layout.");
  303. });
  304. }
  305. auto inputs = QByteArrayList();
  306. for (auto i = 0; i != int(list.size()); ++i) {
  307. auto attributes = Attributes{
  308. { "type", "radio" },
  309. { "name", "s" },
  310. { "value", Number(i) },
  311. { "onchange", "return IV.slideshowSlide(this);" },
  312. };
  313. if (!i) {
  314. attributes.push_back({ "checked", std::nullopt });
  315. }
  316. inputs.append(tag("label", tag("input", attributes, tag("i"))));
  317. }
  318. const auto form = tag(
  319. "form",
  320. { { "class", "slideshow-buttons" } },
  321. tag("fieldset", inputs.join(QByteArray())));
  322. const auto navigation = tag("a", {
  323. { "class", "slideshow-prev" },
  324. { "onclick", "IV.slideshowSlide(this, -1);" },
  325. }, ArrowSvg(true)) + tag("a", {
  326. { "class", "slideshow-next" },
  327. { "onclick", "IV.slideshowSlide(this, 1);" },
  328. }, ArrowSvg(false));
  329. auto wrapStyle = "padding-top: calc(min("
  330. + Percent(dimensions.height() / float64(dimensions.width()))
  331. + "%, 480px));";
  332. result = form + tag("figure", {
  333. { "class", "slideshow" },
  334. }, result) + navigation;
  335. return tag("figure", {
  336. { "class", "slideshow-wrap" },
  337. { "style", wrapStyle },
  338. }, result);
  339. }
  340. QByteArray Parser::block(const MTPDpageBlockUnsupported &data) {
  341. return QByteArray();
  342. }
  343. QByteArray Parser::block(const MTPDpageBlockTitle &data) {
  344. return tag("h1", {
  345. { "class", "title" },
  346. { "dir", "auto" },
  347. }, rich(data.vtext()));
  348. }
  349. QByteArray Parser::block(const MTPDpageBlockSubtitle &data) {
  350. return tag("h2", {
  351. { "class", "subtitle" },
  352. { "dir", "auto" },
  353. }, rich(data.vtext()));
  354. }
  355. QByteArray Parser::block(const MTPDpageBlockAuthorDate &data) {
  356. auto inner = rich(data.vauthor());
  357. if (const auto date = data.vpublished_date().v) {
  358. inner += " \xE2\x80\xA2 " + tag("time", Date(date));
  359. }
  360. return tag("address", { { "dir", "auto" } }, inner);
  361. }
  362. QByteArray Parser::block(const MTPDpageBlockHeader &data) {
  363. return tag("h3", {
  364. { "class", "header" },
  365. { "dir", "auto" },
  366. }, rich(data.vtext()));
  367. }
  368. QByteArray Parser::block(const MTPDpageBlockSubheader &data) {
  369. return tag("h4", {
  370. { "class", "subheader" },
  371. { "dir", "auto" },
  372. }, rich(data.vtext()));
  373. }
  374. QByteArray Parser::block(const MTPDpageBlockParagraph &data) {
  375. return tag("p", { { "dir", "auto" } }, rich(data.vtext()));
  376. }
  377. QByteArray Parser::block(const MTPDpageBlockPreformatted &data) {
  378. auto list = Attributes{ { "dir", "auto" } };
  379. const auto language = utf(data.vlanguage());
  380. if (!language.isEmpty()) {
  381. list.push_back({ "data-language", language });
  382. list.push_back({ "class", "lang-" + language });
  383. _result.hasCode = true;
  384. }
  385. return tag("pre", list, rich(data.vtext()));
  386. }
  387. QByteArray Parser::block(const MTPDpageBlockFooter &data) {
  388. return tag("footer", {
  389. { "class", "footer" },
  390. { "dir", "auto" },
  391. }, rich(data.vtext()));
  392. }
  393. QByteArray Parser::block(const MTPDpageBlockDivider &data) {
  394. return tag("hr", Attributes{ { "class", "divider" } });
  395. }
  396. QByteArray Parser::block(const MTPDpageBlockAnchor &data) {
  397. return tag("a", { { "name", utf(data.vname()) } });
  398. }
  399. QByteArray Parser::block(const MTPDpageBlockList &data) {
  400. return tag("ul", list(data.vitems()));
  401. }
  402. QByteArray Parser::block(const MTPDpageBlockBlockquote &data) {
  403. const auto caption = rich(data.vcaption());
  404. const auto cite = caption.isEmpty()
  405. ? QByteArray()
  406. : tag("cite", { { "dir", "auto" } }, caption);
  407. return tag("blockquote", {
  408. { "dir", "auto" }
  409. }, rich(data.vtext()) + cite);
  410. }
  411. QByteArray Parser::block(const MTPDpageBlockPullquote &data) {
  412. const auto caption = rich(data.vcaption());
  413. const auto cite = caption.isEmpty()
  414. ? QByteArray()
  415. : tag("cite", { { "dir", "auto" } }, caption);
  416. return tag("div", {
  417. { "class", "pullquote" },
  418. { "dir", "auto" },
  419. }, rich(data.vtext()) + cite);
  420. }
  421. QByteArray Parser::block(
  422. const MTPDpageBlockPhoto &data,
  423. const Ui::GroupMediaLayout &layout,
  424. QSize outer) {
  425. const auto collage = !layout.geometry.isEmpty();
  426. const auto slideshow = !collage && !outer.isEmpty();
  427. const auto photo = photoById(data.vphoto_id().v);
  428. if (!photo.id) {
  429. return "Photo not found.";
  430. }
  431. const auto src = photoFullUrl(photo);
  432. auto wrapStyle = QByteArray();
  433. if (collage) {
  434. const auto wcoef = 1. / outer.width();
  435. const auto hcoef = 1. / outer.height();
  436. wrapStyle += "left: " + Percent(layout.geometry.x() * wcoef) + "%; "
  437. + "top: " + Percent(layout.geometry.y() * hcoef) + "%; "
  438. + "width: " + Percent(layout.geometry.width() * wcoef) + "%; "
  439. + "height: " + Percent(layout.geometry.height() * hcoef) + "%";
  440. } else if (!slideshow && photo.width) {
  441. wrapStyle += "max-width:" + Number(photo.width) + "px";
  442. }
  443. const auto dimension = collage
  444. ? (layout.geometry.height() / float64(layout.geometry.width()))
  445. : (photo.width && photo.height)
  446. ? (photo.height / float64(photo.width))
  447. : (3 / 4.);
  448. const auto paddingTop = collage
  449. ? Percent(dimension) + "%"
  450. : "calc(min(480px, " + Percent(dimension) + "%))";
  451. const auto style = "background-image:url('" + src + "');"
  452. "padding-top: " + paddingTop + ";";
  453. auto inner = tag("div", {
  454. { "class", "photo" },
  455. { "style", style } });
  456. const auto minithumb = Images::ExpandInlineBytes(photo.minithumbnail);
  457. if (!minithumb.isEmpty()) {
  458. const auto image = Images::Read({ .content = minithumb });
  459. inner = tag("div", {
  460. { "class", "photo-bg" },
  461. { "style", "background-image:url('data:image/jpeg;base64,"
  462. + minithumb.toBase64()
  463. + "');" },
  464. }) + inner;
  465. }
  466. auto attributes = Attributes{
  467. { "class", "photo-wrap" },
  468. { "style", wrapStyle }
  469. };
  470. auto result = tag("div", attributes, inner);
  471. const auto href = data.vurl() ? utf(*data.vurl()) : photoFullUrl(photo);
  472. const auto id = Number(photo.id);
  473. result = tag("a", {
  474. { "href", href },
  475. { "oncontextmenu", data.vurl() ? QByteArray() : "return false;" },
  476. { "data-context", data.vurl() ? QByteArray() : "viewer-photo" + id },
  477. }, result);
  478. if (!slideshow) {
  479. result += caption(data.vcaption());
  480. if (!collage) {
  481. result = tag("div", { { "class", "media-outer" } }, result);
  482. }
  483. }
  484. return result;
  485. }
  486. QByteArray Parser::block(
  487. const MTPDpageBlockVideo &data,
  488. const Ui::GroupMediaLayout &layout,
  489. QSize outer) {
  490. const auto collage = !layout.geometry.isEmpty();
  491. const auto slideshow = !collage && !outer.isEmpty();
  492. const auto collageSmall = collage
  493. && (layout.geometry.width() < outer.width());
  494. const auto video = documentById(data.vvideo_id().v);
  495. if (!video.id) {
  496. return "Video not found.";
  497. }
  498. auto inner = tag("div", {
  499. { "class", "video" },
  500. { "data-src", documentFullUrl(video) },
  501. { "data-autoplay", data.is_autoplay() ? "1" : "0" },
  502. { "data-loop", data.is_loop() ? "1" : "0" },
  503. { "data-small", collageSmall ? "1" : "0" },
  504. });
  505. const auto minithumb = Images::ExpandInlineBytes(video.minithumbnail);
  506. if (!minithumb.isEmpty()) {
  507. const auto image = Images::Read({ .content = minithumb });
  508. inner = tag("div", {
  509. { "class", "video-bg" },
  510. { "style", "background-image:url('data:image/jpeg;base64,"
  511. + minithumb.toBase64()
  512. + "');" },
  513. }) + inner;
  514. }
  515. auto wrapStyle = QByteArray();
  516. if (collage) {
  517. const auto wcoef = 1. / outer.width();
  518. const auto hcoef = 1. / outer.height();
  519. wrapStyle += "left: " + Percent(layout.geometry.x() * wcoef) + "%; "
  520. + "top: " + Percent(layout.geometry.y() * hcoef) + "%; "
  521. + "width: " + Percent(layout.geometry.width() * wcoef) + "%; "
  522. + "height: " + Percent(layout.geometry.height() * hcoef) + "%; ";
  523. } else {
  524. const auto dimension = (video.width && video.height)
  525. ? (video.height / float64(video.width))
  526. : (3 / 4.);
  527. wrapStyle += "padding-top: calc(min(480px, "
  528. + Percent(dimension)
  529. + "%));";
  530. }
  531. auto attributes = Attributes{
  532. { "class", "video-wrap" },
  533. { "style", wrapStyle },
  534. };
  535. auto result = tag("div", attributes, inner);
  536. if (data.is_autoplay() || collageSmall) {
  537. const auto id = Number(video.id);
  538. const auto href = resource("video" + id);
  539. result = tag("a", {
  540. { "href", href },
  541. { "oncontextmenu", "return false;" },
  542. { "data-context", "viewer-video" + id },
  543. }, result);
  544. }
  545. if (!slideshow) {
  546. result += caption(data.vcaption());
  547. if (!collage) {
  548. result = tag("div", { { "class", "media-outer" } }, result);
  549. }
  550. }
  551. return result;
  552. }
  553. QByteArray Parser::block(const MTPDpageBlockCover &data) {
  554. return tag("figure", data.vcover().match([&](const auto &data) {
  555. return block(data);
  556. }));
  557. }
  558. QByteArray Parser::block(const MTPDpageBlockEmbed &data) {
  559. _result.hasEmbeds = true;
  560. auto eclass = data.is_full_width() ? QByteArray() : "nowide";
  561. auto width = QByteArray();
  562. auto height = QByteArray();
  563. auto iframeWidth = QByteArray();
  564. auto iframeHeight = QByteArray();
  565. const auto autosize = !data.vw();
  566. if (autosize) {
  567. iframeWidth = "100%";
  568. eclass = "nowide";
  569. } else if (data.is_full_width() || !data.vw()->v) {
  570. width = "100%";
  571. height = Number(data.vh()->v) + "px";
  572. iframeWidth = "100%";
  573. iframeHeight = height;
  574. } else {
  575. width = Number(data.vw()->v) + "px";
  576. height = Percent(data.vh()->v / float64(data.vw()->v)) + "%";
  577. }
  578. auto attributes = Attributes();
  579. if (autosize) {
  580. attributes.push_back({ "class", "autosize" });
  581. }
  582. attributes.push_back({ "width", iframeWidth });
  583. attributes.push_back({ "height", iframeHeight });
  584. if (const auto url = data.vurl()) {
  585. if (!autosize) {
  586. attributes.push_back({ "src", utf(url) });
  587. } else {
  588. attributes.push_back({ "srcdoc", utf(url) });
  589. }
  590. } else if (const auto html = data.vhtml()) {
  591. attributes.push_back({ "src", embedUrl(html->v) });
  592. }
  593. if (!data.is_allow_scrolling()) {
  594. attributes.push_back({ "scrolling", "no" });
  595. }
  596. attributes.push_back({ "frameborder", "0" });
  597. attributes.push_back({ "allowtransparency", "true" });
  598. attributes.push_back({ "allowfullscreen", "true" });
  599. auto result = tag("iframe", attributes);
  600. if (!autosize) {
  601. result = tag("div", {
  602. { "class", "iframe-wrap" },
  603. { "style", "width:" + width },
  604. }, tag("div", {
  605. { "style", "padding-bottom: " + height },
  606. }, result));
  607. }
  608. result += caption(data.vcaption());
  609. return tag("figure", { { "class", eclass } }, result);
  610. }
  611. QByteArray Parser::block(const MTPDpageBlockEmbedPost &data) {
  612. auto result = QByteArray();
  613. if (!data.vblocks().v.isEmpty()) {
  614. auto address = QByteArray();
  615. const auto photo = photoById(data.vauthor_photo_id().v);
  616. if (photo.id) {
  617. const auto src = photoFullUrl(photo);
  618. address += tag(
  619. "figure",
  620. { { "style", "background-image:url('" + src + "')" } });
  621. }
  622. address += tag(
  623. "a",
  624. { { "rel", "author" }, { "onclick", "return false;" } },
  625. utf(data.vauthor()));
  626. if (const auto date = data.vdate().v) {
  627. const auto parsed = base::unixtime::parse(date);
  628. address += tag("time", Date(date));
  629. }
  630. const auto inner = tag("address", address) + list(data.vblocks());
  631. result = tag("blockquote", { { "class", "embed-post" } }, inner);
  632. } else {
  633. const auto url = utf(data.vurl());
  634. const auto inner = tag("strong", utf(data.vauthor()))
  635. + tag(
  636. "small",
  637. tag("a", { { "href", url } }, url));
  638. result = tag("section", { { "class", "embed-post" } }, inner);
  639. }
  640. result += caption(data.vcaption());
  641. return tag("figure", result);
  642. }
  643. QByteArray Parser::block(const MTPDpageBlockCollage &data) {
  644. const auto &items = data.vitems().v;
  645. const auto dimensions = computeCollageDimensions(items);
  646. if (dimensions.empty()) {
  647. return tag(
  648. "figure",
  649. tag("figure", list(data.vitems())) + caption(data.vcaption()));
  650. }
  651. return tag(
  652. "figure",
  653. { { "class", "collage-wrap" } },
  654. collage(items, dimensions) + caption(data.vcaption()));
  655. }
  656. QByteArray Parser::block(const MTPDpageBlockSlideshow &data) {
  657. const auto &items = data.vitems().v;
  658. const auto dimensions = computeSlideshowDimensions(items);
  659. if (dimensions.isEmpty()) {
  660. return list(data.vitems());
  661. }
  662. const auto result = slideshow(items, dimensions);
  663. return tag("figure", result + caption(data.vcaption()));
  664. }
  665. QByteArray Parser::block(const MTPDpageBlockChannel &data) {
  666. auto name = QByteArray();
  667. auto username = QByteArray();
  668. auto id = data.vchannel().match([](const auto &data) {
  669. return Number(data.vid().v);
  670. });
  671. data.vchannel().match([&](const MTPDchannel &data) {
  672. if (const auto has = data.vusername()) {
  673. username = utf(*has);
  674. }
  675. name = utf(data.vtitle());
  676. }, [&](const MTPDchat &data) {
  677. name = utf(data.vtitle());
  678. }, [](const auto &) {
  679. });
  680. auto result = tag(
  681. "div",
  682. { { "class", "join" }, { "data-context", "join_link" + id } },
  683. tag("span")
  684. ) + tag("h4", name);
  685. const auto link = username.isEmpty()
  686. ? "javascript:alert('Channel Link');"
  687. : "https://t.me/" + username;
  688. result = tag(
  689. "a",
  690. { { "href", link }, { "data-context", "channel" + id } },
  691. result);
  692. _result.channelIds.emplace(id);
  693. return tag("section", {
  694. { "class", "channel joined" },
  695. { "data-context", "channel" + id },
  696. }, result);
  697. }
  698. QByteArray Parser::block(const MTPDpageBlockAudio &data) {
  699. const auto audio = documentById(data.vaudio_id().v);
  700. if (!audio.id) {
  701. return "Audio not found.";
  702. }
  703. const auto src = documentFullUrl(audio);
  704. return tag("figure", tag("audio", {
  705. { "src", src },
  706. { "oncontextmenu", "return false;" },
  707. { "controls", std::nullopt },
  708. }) + caption(data.vcaption()));
  709. }
  710. QByteArray Parser::block(const MTPDpageBlockKicker &data) {
  711. return tag("h5", {
  712. { "class", "kicker" },
  713. { "dir", "auto" },
  714. }, rich(data.vtext()));
  715. }
  716. QByteArray Parser::block(const MTPDpageBlockTable &data) {
  717. auto classes = QByteArrayList();
  718. if (data.is_bordered()) {
  719. classes.push_back("bordered");
  720. }
  721. if (data.is_striped()) {
  722. classes.push_back("striped");
  723. }
  724. auto attibutes = Attributes();
  725. if (!classes.isEmpty()) {
  726. attibutes.push_back({ "class", classes.join(" ") });
  727. }
  728. auto title = rich(data.vtitle());
  729. if (!title.isEmpty()) {
  730. title = tag("caption", { { "dir", "auto" } }, title);
  731. }
  732. auto result = tag("table", attibutes, title + list(data.vrows()));
  733. result = tag("figure", { { "class", "table" } }, result);
  734. result = tag("figure", { { "class", "table-wrap" } }, result);
  735. return tag("figure", result);
  736. }
  737. QByteArray Parser::block(const MTPDpageBlockOrderedList &data) {
  738. return tag("ol", list(data.vitems()));
  739. }
  740. QByteArray Parser::block(const MTPDpageBlockDetails &data) {
  741. auto attributes = Attributes();
  742. if (data.is_open()) {
  743. attributes.push_back({ "open", std::nullopt });
  744. }
  745. return tag(
  746. "details",
  747. attributes,
  748. (tag("summary", { { "dir", "auto" } }, rich(data.vtitle()))
  749. + list(data.vblocks())));
  750. }
  751. QByteArray Parser::block(const MTPDpageBlockRelatedArticles &data) {
  752. const auto result = list(data.varticles());
  753. if (result.isEmpty()) {
  754. return QByteArray();
  755. }
  756. auto title = rich(data.vtitle());
  757. if (!title.isEmpty()) {
  758. title = tag("h4", {
  759. { "class", "related-title" },
  760. { "dir", "auto" },
  761. }, title);
  762. }
  763. return tag("section", { { "class", "related" } }, title + result);
  764. }
  765. QByteArray Parser::block(const MTPDpageBlockMap &data) {
  766. const auto geo = parse(data.vgeo());
  767. if (!geo.access) {
  768. return "Map not found.";
  769. }
  770. const auto width = 650;
  771. const auto height = std::min(450, (data.vh().v * width / data.vw().v));
  772. return tag("figure", tag("img", {
  773. { "src", mapUrl(geo, width, height, data.vzoom().v) },
  774. }) + caption(data.vcaption()));
  775. }
  776. QByteArray Parser::block(const MTPDpageRelatedArticle &data) {
  777. auto result = QByteArray();
  778. const auto photo = photoById(data.vphoto_id().value_or_empty());
  779. if (photo.id) {
  780. const auto src = photoFullUrl(photo);
  781. result += tag("i", {
  782. { "class", "related-link-thumb" },
  783. { "style", "background-image:url('" + src + "')" },
  784. });
  785. }
  786. const auto title = data.vtitle();
  787. const auto description = data.vdescription();
  788. const auto author = data.vauthor();
  789. const auto published = data.vpublished_date();
  790. if (title || description || author || published) {
  791. auto inner = QByteArray();
  792. if (title) {
  793. inner += tag(
  794. "span",
  795. { { "class", "related-link-title" } },
  796. utf(*title));
  797. }
  798. if (description) {
  799. inner += tag(
  800. "span",
  801. { { "class", "related-link-desc" } },
  802. utf(*description));
  803. }
  804. if (author || published) {
  805. inner += tag(
  806. "span",
  807. { { "class", "related-link-source" } },
  808. ((author ? utf(*author) : QByteArray())
  809. + ((author && published) ? ", " : QByteArray())
  810. + (published ? Date(published->v) : QByteArray())));
  811. }
  812. result += tag("span", {
  813. { "class", "related-link-content" },
  814. }, inner);
  815. }
  816. const auto webpageId = data.vwebpage_id().v;
  817. const auto context = webpageId
  818. ? ("webpage" + Number(webpageId))
  819. : QByteArray();
  820. return tag("a", {
  821. { "class", "related-link" },
  822. { "href", utf(data.vurl()) },
  823. { "data-context", context },
  824. }, result);
  825. }
  826. QByteArray Parser::block(const MTPDpageTableRow &data) {
  827. return tag("tr", list(data.vcells()));
  828. }
  829. QByteArray Parser::block(const MTPDpageTableCell &data) {
  830. const auto text = data.vtext() ? rich(*data.vtext()) : QByteArray();
  831. auto style = QByteArray();
  832. if (data.is_align_right()) {
  833. style += "text-align:right;";
  834. } else if (data.is_align_center()) {
  835. style += "text-align:center;";
  836. } else {
  837. style += "text-align:left;";
  838. }
  839. if (data.is_valign_bottom()) {
  840. style += "vertical-align:bottom;";
  841. } else if (data.is_valign_middle()) {
  842. style += "vertical-align:middle;";
  843. } else {
  844. style += "vertical-align:top;";
  845. }
  846. auto attributes = Attributes{ { "style", style }, { "dir", "auto" } };
  847. if (const auto cs = data.vcolspan()) {
  848. attributes.push_back({ "colspan", Number(cs->v) });
  849. }
  850. if (const auto rs = data.vrowspan()) {
  851. attributes.push_back({ "rowspan", Number(rs->v) });
  852. }
  853. return tag(data.is_header() ? "th" : "td", attributes, text);
  854. }
  855. QByteArray Parser::block(const MTPDpageListItemText &data) {
  856. return tag("li", { { "dir", "auto" } }, rich(data.vtext()));
  857. }
  858. QByteArray Parser::block(const MTPDpageListItemBlocks &data) {
  859. return tag("li", list(data.vblocks()));
  860. }
  861. QByteArray Parser::block(const MTPDpageListOrderedItemText &data) {
  862. return tag(
  863. "li",
  864. { { "value", utf(data.vnum()) }, { "dir", "auto" } },
  865. rich(data.vtext()));
  866. }
  867. QByteArray Parser::block(const MTPDpageListOrderedItemBlocks &data) {
  868. return tag(
  869. "li",
  870. { { "value", utf(data.vnum()) } },
  871. list(data.vblocks()));
  872. }
  873. QByteArray Parser::utf(const MTPstring &text) {
  874. return Escape(text.v);
  875. }
  876. QByteArray Parser::utf(const tl::conditional<MTPstring> &text) {
  877. return text ? utf(*text) : QByteArray();
  878. }
  879. QByteArray Parser::wrap(const QByteArray &content, int views) {
  880. const auto sep = " \xE2\x80\xA2 ";
  881. const auto viewsText = views
  882. ? (tr::lng_stories_views(tr::now, lt_count_decimal, views) + sep)
  883. : QString();
  884. return R"(
  885. <div class="page-slide">
  886. <article>)"_q + content + R"(</article>
  887. </div>
  888. <div class="page-footer">
  889. <div class="content">
  890. )"_q
  891. + viewsText.toUtf8()
  892. + R"(<a class="wrong" data-context="report-iv">)"_q
  893. + tr::lng_iv_wrong_layout(tr::now).toUtf8()
  894. + R"(</a>
  895. </div>
  896. </div>)"_q;
  897. }
  898. QByteArray Parser::tag(
  899. const QByteArray &name,
  900. const QByteArray &body) {
  901. return tag(name, {}, body);
  902. }
  903. QByteArray Parser::tag(
  904. const QByteArray &name,
  905. const Attributes &attributes,
  906. const QByteArray &body) {
  907. auto list = QByteArrayList();
  908. list.reserve(attributes.size());
  909. for (auto &[name, value] : attributes) {
  910. list.push_back(' ' + name + (value ? "=\"" + *value + "\"" : ""));
  911. }
  912. const auto serialized = list.join(QByteArray());
  913. return (IsVoidElement(name) && body.isEmpty())
  914. ? ('<' + name + serialized + " />")
  915. : ('<' + name + serialized + '>' + body + "</" + name + '>');
  916. }
  917. QByteArray Parser::rich(const MTPRichText &text) {
  918. return text.match([&](const MTPDtextEmpty &data) {
  919. return QByteArray();
  920. }, [&](const MTPDtextPlain &data) {
  921. struct Replacement {
  922. QByteArray from;
  923. QByteArray to;
  924. };
  925. const auto replacements = std::vector<Replacement>{
  926. { "\xE2\x81\xA6", "<span dir=\"ltr\">" },
  927. { "\xE2\x81\xA7", "<span dir=\"rtl\">" },
  928. { "\xE2\x81\xA8", "<span dir=\"auto\">" },
  929. { "\xE2\x81\xA9", "</span>" },
  930. };
  931. auto text = utf(data.vtext());
  932. for (const auto &[from, to] : replacements) {
  933. text.replace(from, to);
  934. }
  935. return text;
  936. }, [&](const MTPDtextConcat &data) {
  937. const auto &list = data.vtexts().v;
  938. auto result = QByteArrayList();
  939. result.reserve(list.size());
  940. for (const auto &item : list) {
  941. result.append(rich(item));
  942. }
  943. return result.join(QByteArray());
  944. }, [&](const MTPDtextImage &data) {
  945. const auto image = documentById(data.vdocument_id().v);
  946. if (!image.id) {
  947. return "Image not found."_q;
  948. }
  949. auto attributes = Attributes{
  950. { "class", "pic" },
  951. { "src", documentFullUrl(image) },
  952. };
  953. if (const auto width = data.vw().v) {
  954. attributes.push_back({ "width", Number(width) });
  955. }
  956. if (const auto height = data.vh().v) {
  957. attributes.push_back({ "height", Number(height) });
  958. }
  959. return tag("img", attributes);
  960. }, [&](const MTPDtextBold &data) {
  961. return tag("b", rich(data.vtext()));
  962. }, [&](const MTPDtextItalic &data) {
  963. return tag("i", rich(data.vtext()));
  964. }, [&](const MTPDtextUnderline &data) {
  965. return tag("u", rich(data.vtext()));
  966. }, [&](const MTPDtextStrike &data) {
  967. return tag("s", rich(data.vtext()));
  968. }, [&](const MTPDtextFixed &data) {
  969. return tag("code", rich(data.vtext()));
  970. }, [&](const MTPDtextUrl &data) {
  971. const auto webpageId = data.vwebpage_id().v;
  972. const auto context = webpageId
  973. ? ("webpage" + Number(webpageId))
  974. : QByteArray();
  975. return tag("a", {
  976. { "href", utf(data.vurl()) },
  977. { "class", webpageId ? "internal-iv-link" : "" },
  978. { "data-context", context },
  979. }, rich(data.vtext()));
  980. }, [&](const MTPDtextEmail &data) {
  981. return tag("a", {
  982. { "href", "mailto:" + utf(data.vemail()) },
  983. }, rich(data.vtext()));
  984. }, [&](const MTPDtextSubscript &data) {
  985. return tag("sub", rich(data.vtext()));
  986. }, [&](const MTPDtextSuperscript &data) {
  987. return tag("sup", rich(data.vtext()));
  988. }, [&](const MTPDtextMarked &data) {
  989. return tag("mark", rich(data.vtext()));
  990. }, [&](const MTPDtextPhone &data) {
  991. return tag("a", {
  992. { "href", "tel:" + utf(data.vphone()) },
  993. }, rich(data.vtext()));
  994. }, [&](const MTPDtextAnchor &data) {
  995. const auto inner = rich(data.vtext());
  996. const auto name = utf(data.vname());
  997. return inner.isEmpty()
  998. ? tag("a", { { "name", name } })
  999. : tag(
  1000. "span",
  1001. { { "class", "reference" } },
  1002. tag("a", { { "name", name } }) + inner);
  1003. });
  1004. }
  1005. QByteArray Parser::caption(const MTPPageCaption &caption) {
  1006. auto text = rich(caption.data().vtext());
  1007. const auto credit = rich(caption.data().vcredit());
  1008. if (!credit.isEmpty()) {
  1009. text += tag("cite", { { "dir", "auto" } }, credit);
  1010. } else if (text.isEmpty()) {
  1011. return QByteArray();
  1012. }
  1013. return tag("figcaption", { { "dir", "auto" } }, text);
  1014. }
  1015. Photo Parser::parse(const MTPPhoto &photo) {
  1016. auto result = Photo{
  1017. .id = photo.match([&](const auto &d) { return d.vid().v; }),
  1018. };
  1019. auto sizes = base::flat_map<QByteArray, QSize>();
  1020. photo.match([](const MTPDphotoEmpty &) {
  1021. }, [&](const MTPDphoto &data) {
  1022. for (const auto &size : data.vsizes().v) {
  1023. size.match([&](const MTPDphotoSizeEmpty &data) {
  1024. }, [&](const MTPDphotoSize &data) {
  1025. sizes.emplace(
  1026. data.vtype().v,
  1027. QSize(data.vw().v, data.vh().v));
  1028. }, [&](const MTPDphotoCachedSize &data) {
  1029. sizes.emplace(
  1030. data.vtype().v,
  1031. QSize(data.vw().v, data.vh().v));
  1032. }, [&](const MTPDphotoStrippedSize &data) {
  1033. result.minithumbnail = data.vbytes().v;
  1034. }, [&](const MTPDphotoSizeProgressive &data) {
  1035. sizes.emplace(
  1036. data.vtype().v,
  1037. QSize(data.vw().v, data.vh().v));
  1038. }, [&](const MTPDphotoPathSize &data) {
  1039. });
  1040. }
  1041. });
  1042. for (const auto attempt : { "y", "x", "w" }) {
  1043. const auto i = sizes.find(QByteArray(attempt));
  1044. if (i != end(sizes)) {
  1045. result.width = i->second.width();
  1046. result.height = i->second.height();
  1047. break;
  1048. }
  1049. }
  1050. return result;
  1051. }
  1052. Document Parser::parse(const MTPDocument &document) {
  1053. auto result = Document{
  1054. .id = document.match([&](const auto &d) { return d.vid().v; }),
  1055. };
  1056. document.match([](const MTPDdocumentEmpty &) {
  1057. }, [&](const MTPDdocument &data) {
  1058. for (const auto &attribute : data.vattributes().v) {
  1059. attribute.match([&](const MTPDdocumentAttributeImageSize &data) {
  1060. result.width = data.vw().v;
  1061. result.height = data.vh().v;
  1062. }, [&](const MTPDdocumentAttributeVideo &data) {
  1063. result.width = data.vw().v;
  1064. result.height = data.vh().v;
  1065. }, [](const auto &) {});
  1066. }
  1067. if (const auto sizes = data.vthumbs()) {
  1068. for (const auto &size : sizes->v) {
  1069. size.match([&](const MTPDphotoStrippedSize &data) {
  1070. result.minithumbnail = data.vbytes().v;
  1071. }, [&](const auto &data) {
  1072. });
  1073. }
  1074. }
  1075. });
  1076. return result;
  1077. }
  1078. Geo Parser::parse(const MTPGeoPoint &geo) {
  1079. return geo.match([](const MTPDgeoPointEmpty &) {
  1080. return Geo();
  1081. }, [&](const MTPDgeoPoint &data) {
  1082. return Geo{
  1083. .lat = data.vlat().v,
  1084. .lon = data.vlong().v,
  1085. .access = data.vaccess_hash().v,
  1086. };
  1087. });
  1088. }
  1089. Photo Parser::photoById(uint64 id) {
  1090. const auto i = _photosById.find(id);
  1091. return (i != end(_photosById)) ? i->second : Photo();
  1092. }
  1093. Document Parser::documentById(uint64 id) {
  1094. const auto i = _documentsById.find(id);
  1095. return (i != end(_documentsById)) ? i->second : Document();
  1096. }
  1097. QByteArray Parser::photoFullUrl(const Photo &photo) {
  1098. return resource("photo/" + Number(photo.id) + _fileOriginPostfix);
  1099. }
  1100. QByteArray Parser::documentFullUrl(const Document &document) {
  1101. return resource("document/" + Number(document.id) + _fileOriginPostfix);
  1102. }
  1103. QByteArray Parser::embedUrl(const QByteArray &html) {
  1104. auto binary = std::array<uchar, SHA256_DIGEST_LENGTH>{};
  1105. SHA256(
  1106. reinterpret_cast<const unsigned char*>(html.data()),
  1107. html.size(),
  1108. binary.data());
  1109. const auto hex = [](uchar value) -> char {
  1110. return (value >= 10) ? ('a' + (value - 10)) : ('0' + value);
  1111. };
  1112. auto result = QByteArray();
  1113. result.reserve(binary.size() * 2);
  1114. for (const auto byte : binary) {
  1115. result.push_back(hex(byte / 16));
  1116. result.push_back(hex(byte % 16));
  1117. }
  1118. result += ".html";
  1119. _result.embeds.emplace(result, html);
  1120. return resource("html/" + result);
  1121. }
  1122. QByteArray Parser::mapUrl(const Geo &geo, int width, int height, int zoom) {
  1123. return resource("map/"
  1124. + GeoPointId(geo) + "&"
  1125. + Number(width) + ","
  1126. + Number(height) + "&"
  1127. + Number(zoom));
  1128. }
  1129. QByteArray Parser::resource(QByteArray id) {
  1130. return '/' + id;
  1131. }
  1132. std::vector<QSize> Parser::computeCollageDimensions(
  1133. const QVector<MTPPageBlock> &items) {
  1134. if (items.size() < 2) {
  1135. return {};
  1136. }
  1137. auto result = std::vector<QSize>(items.size());
  1138. for (auto i = 0, count = int(items.size()); i != count; ++i) {
  1139. items[i].match([&](const MTPDpageBlockPhoto &data) {
  1140. const auto photo = photoById(data.vphoto_id().v);
  1141. if (photo.id && photo.width > 0 && photo.height > 0) {
  1142. result[i] = QSize(photo.width, photo.height);
  1143. }
  1144. }, [&](const MTPDpageBlockVideo &data) {
  1145. const auto document = documentById(data.vvideo_id().v);
  1146. if (document.id && document.width > 0 && document.height > 0) {
  1147. result[i] = QSize(document.width, document.height);
  1148. }
  1149. }, [](const auto &) {});
  1150. if (result[i].isEmpty()) {
  1151. return {};
  1152. }
  1153. }
  1154. return result;
  1155. }
  1156. QSize Parser::computeSlideshowDimensions(
  1157. const QVector<MTPPageBlock> &items) {
  1158. if (items.size() < 2) {
  1159. return {};
  1160. }
  1161. auto result = QSize();
  1162. for (const auto &item : items) {
  1163. auto size = QSize();
  1164. item.match([&](const MTPDpageBlockPhoto &data) {
  1165. const auto photo = photoById(data.vphoto_id().v);
  1166. if (photo.id && photo.width > 0 && photo.height > 0) {
  1167. size = QSize(photo.width, photo.height);
  1168. }
  1169. }, [&](const MTPDpageBlockVideo &data) {
  1170. const auto document = documentById(data.vvideo_id().v);
  1171. if (document.id && document.width > 0 && document.height > 0) {
  1172. size = QSize(document.width, document.height);
  1173. }
  1174. }, [](const auto &) {});
  1175. if (size.isEmpty()) {
  1176. return {};
  1177. } else if (result.height() * size.width()
  1178. < result.width() * size.height()) {
  1179. result = size;
  1180. }
  1181. }
  1182. return result;
  1183. }
  1184. } // namespace
  1185. Prepared Prepare(const Source &source, const Options &options) {
  1186. auto parser = Parser(source, options);
  1187. return parser.result();
  1188. }
  1189. } // namespace Iv