vpreviewmanager.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. #include "vpreviewmanager.h"
  2. #include <QTextDocument>
  3. #include <QDebug>
  4. #include <QDir>
  5. #include <QUrl>
  6. #include <QVector>
  7. #include "vconfigmanager.h"
  8. #include "utils/vutils.h"
  9. #include "vdownloader.h"
  10. #include "hgmarkdownhighlighter.h"
  11. extern VConfigManager *g_config;
  12. VPreviewManager::VPreviewManager(VMdEditor *p_editor, HGMarkdownHighlighter *p_highlighter)
  13. : QObject(p_editor),
  14. m_editor(p_editor),
  15. m_document(p_editor->document()),
  16. m_highlighter(p_highlighter),
  17. m_previewEnabled(false),
  18. m_timeStamp(0)
  19. {
  20. m_downloader = new VDownloader(this);
  21. connect(m_downloader, &VDownloader::downloadFinished,
  22. this, &VPreviewManager::imageDownloaded);
  23. }
  24. void VPreviewManager::imageLinksUpdated(const QVector<VElementRegion> &p_imageRegions)
  25. {
  26. if (!m_previewEnabled) {
  27. return;
  28. }
  29. TS ts = ++m_timeStamp;
  30. m_imageRegions = p_imageRegions;
  31. previewImages(ts);
  32. }
  33. void VPreviewManager::imageDownloaded(const QByteArray &p_data, const QString &p_url)
  34. {
  35. if (!m_previewEnabled) {
  36. return;
  37. }
  38. auto it = m_urlToName.find(p_url);
  39. if (it == m_urlToName.end()) {
  40. return;
  41. }
  42. QString name = it.value();
  43. m_urlToName.erase(it);
  44. if (m_editor->containsImage(name) || name.isEmpty()) {
  45. return;
  46. }
  47. QPixmap image;
  48. image.loadFromData(p_data);
  49. if (!image.isNull()) {
  50. m_editor->addImage(name, image);
  51. qDebug() << "downloaded image inserted in resource manager" << p_url << name;
  52. emit requestUpdateImageLinks();
  53. }
  54. }
  55. void VPreviewManager::setPreviewEnabled(bool p_enabled)
  56. {
  57. if (m_previewEnabled != p_enabled) {
  58. m_previewEnabled = p_enabled;
  59. if (!m_previewEnabled) {
  60. clearPreview();
  61. } else {
  62. requestUpdateImageLinks();
  63. }
  64. }
  65. }
  66. void VPreviewManager::clearPreview()
  67. {
  68. m_imageRegions.clear();
  69. long long ts = ++m_timeStamp;
  70. for (int i = 0; i < (int)PreviewSource::MaxNumberOfSources; ++i) {
  71. clearBlockObsoletePreviewInfo(ts, static_cast<PreviewSource>(i));
  72. clearObsoleteImages(ts, static_cast<PreviewSource>(i));
  73. }
  74. }
  75. void VPreviewManager::previewImages(TS p_timeStamp)
  76. {
  77. QVector<ImageLinkInfo> imageLinks;
  78. fetchImageLinksFromRegions(imageLinks);
  79. updateBlockPreviewInfo(p_timeStamp, imageLinks);
  80. clearBlockObsoletePreviewInfo(p_timeStamp, PreviewSource::ImageLink);
  81. clearObsoleteImages(p_timeStamp, PreviewSource::ImageLink);
  82. }
  83. // Returns true if p_text[p_start, p_end) is all spaces.
  84. static bool isAllSpaces(const QString &p_text, int p_start, int p_end)
  85. {
  86. int len = qMin(p_text.size(), p_end);
  87. for (int i = p_start; i < len; ++i) {
  88. if (!p_text[i].isSpace()) {
  89. return false;
  90. }
  91. }
  92. return true;
  93. }
  94. void VPreviewManager::fetchImageLinksFromRegions(QVector<ImageLinkInfo> &p_imageLinks)
  95. {
  96. p_imageLinks.clear();
  97. if (m_imageRegions.isEmpty()) {
  98. return;
  99. }
  100. p_imageLinks.reserve(m_imageRegions.size());
  101. QTextDocument *doc = m_editor->document();
  102. for (int i = 0; i < m_imageRegions.size(); ++i) {
  103. VElementRegion &reg = m_imageRegions[i];
  104. QTextBlock block = doc->findBlock(reg.m_startPos);
  105. if (!block.isValid()) {
  106. continue;
  107. }
  108. int blockStart = block.position();
  109. int blockEnd = blockStart + block.length() - 1;
  110. QString text = block.text();
  111. Q_ASSERT(reg.m_endPos <= blockEnd);
  112. ImageLinkInfo info(reg.m_startPos,
  113. reg.m_endPos,
  114. blockStart,
  115. block.blockNumber(),
  116. calculateBlockMargin(block));
  117. if ((reg.m_startPos == blockStart
  118. || isAllSpaces(text, 0, reg.m_startPos - blockStart))
  119. && (reg.m_endPos == blockEnd
  120. || isAllSpaces(text, reg.m_endPos - blockStart, blockEnd - blockStart))) {
  121. // Image block.
  122. info.m_isBlock = true;
  123. info.m_linkUrl = fetchImagePathToPreview(text, info.m_linkShortUrl);
  124. } else {
  125. // Inline image.
  126. info.m_isBlock = false;
  127. info.m_linkUrl = fetchImagePathToPreview(text.mid(reg.m_startPos - blockStart,
  128. reg.m_endPos - reg.m_startPos),
  129. info.m_linkShortUrl);
  130. }
  131. if (info.m_linkUrl.isEmpty()) {
  132. continue;
  133. }
  134. p_imageLinks.append(info);
  135. qDebug() << "image region" << i
  136. << info.m_startPos << info.m_endPos << info.m_blockNumber
  137. << info.m_linkShortUrl << info.m_linkUrl << info.m_isBlock;
  138. }
  139. }
  140. QString VPreviewManager::fetchImageUrlToPreview(const QString &p_text)
  141. {
  142. QRegExp regExp(VUtils::c_imageLinkRegExp);
  143. int index = regExp.indexIn(p_text);
  144. if (index == -1) {
  145. return QString();
  146. }
  147. int lastIndex = regExp.lastIndexIn(p_text);
  148. if (lastIndex != index) {
  149. return QString();
  150. }
  151. return regExp.capturedTexts()[2].trimmed();
  152. }
  153. QString VPreviewManager::fetchImagePathToPreview(const QString &p_text, QString &p_url)
  154. {
  155. p_url = fetchImageUrlToPreview(p_text);
  156. if (p_url.isEmpty()) {
  157. return p_url;
  158. }
  159. const VFile *file = m_editor->getFile();
  160. QString imagePath;
  161. QFileInfo info(file->fetchBasePath(), p_url);
  162. if (info.exists()) {
  163. if (info.isNativePath()) {
  164. // Local file.
  165. imagePath = QDir::cleanPath(info.absoluteFilePath());
  166. } else {
  167. imagePath = p_url;
  168. }
  169. } else {
  170. QString decodedUrl(p_url);
  171. VUtils::decodeUrl(decodedUrl);
  172. QFileInfo dinfo(file->fetchBasePath(), decodedUrl);
  173. if (dinfo.exists()) {
  174. if (dinfo.isNativePath()) {
  175. // Local file.
  176. imagePath = QDir::cleanPath(dinfo.absoluteFilePath());
  177. } else {
  178. imagePath = p_url;
  179. }
  180. } else {
  181. QUrl url(p_url);
  182. imagePath = url.toString();
  183. }
  184. }
  185. return imagePath;
  186. }
  187. QString VPreviewManager::imageResourceName(const ImageLinkInfo &p_link)
  188. {
  189. QString name = p_link.m_linkShortUrl;
  190. if (m_editor->containsImage(name)
  191. || name.isEmpty()) {
  192. return name;
  193. }
  194. // Add it to the resource.
  195. QString imgPath = p_link.m_linkUrl;
  196. QFileInfo info(imgPath);
  197. QPixmap image;
  198. if (info.exists()) {
  199. // Local file.
  200. image = QPixmap(imgPath);
  201. } else {
  202. // URL. Try to download it.
  203. m_downloader->download(imgPath);
  204. m_urlToName.insert(imgPath, name);
  205. }
  206. if (image.isNull()) {
  207. return QString();
  208. }
  209. m_editor->addImage(name, image);
  210. return name;
  211. }
  212. int VPreviewManager::calculateBlockMargin(const QTextBlock &p_block)
  213. {
  214. static QHash<QString, int> spaceWidthOfFonts;
  215. if (!p_block.isValid()) {
  216. return 0;
  217. }
  218. QString text = p_block.text();
  219. int nrSpaces = 0;
  220. for (int i = 0; i < text.size(); ++i) {
  221. if (!text[i].isSpace()) {
  222. break;
  223. } else if (text[i] == ' ') {
  224. ++nrSpaces;
  225. } else if (text[i] == '\t') {
  226. nrSpaces += m_editor->tabStopWidth();
  227. }
  228. }
  229. if (nrSpaces == 0) {
  230. return 0;
  231. }
  232. int spaceWidth = 0;
  233. QFont font = p_block.charFormat().font();
  234. QString fontName = font.toString();
  235. auto it = spaceWidthOfFonts.find(fontName);
  236. if (it != spaceWidthOfFonts.end()) {
  237. spaceWidth = it.value();
  238. } else {
  239. spaceWidth = QFontMetrics(font).width(' ');
  240. spaceWidthOfFonts.insert(fontName, spaceWidth);
  241. }
  242. return spaceWidth * nrSpaces;
  243. }
  244. void VPreviewManager::updateBlockPreviewInfo(TS p_timeStamp,
  245. const QVector<ImageLinkInfo> &p_imageLinks)
  246. {
  247. for (auto const & link : p_imageLinks) {
  248. QTextBlock block = m_document->findBlockByNumber(link.m_blockNumber);
  249. if (!block.isValid()) {
  250. continue;
  251. }
  252. QString name = imageResourceName(link);
  253. if (name.isEmpty()) {
  254. continue;
  255. }
  256. VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(block.userData());
  257. Q_ASSERT(blockData);
  258. VPreviewInfo *info = new VPreviewInfo(PreviewSource::ImageLink,
  259. p_timeStamp,
  260. link.m_startPos - link.m_blockPos,
  261. link.m_endPos - link.m_blockPos,
  262. link.m_padding,
  263. !link.m_isBlock,
  264. name,
  265. m_editor->imageSize(name));
  266. blockData->insertPreviewInfo(info);
  267. imageCache(PreviewSource::ImageLink).insert(name, p_timeStamp);
  268. qDebug() << "block" << link.m_blockNumber
  269. << imageCache(PreviewSource::ImageLink).size()
  270. << blockData->toString();
  271. }
  272. }
  273. void VPreviewManager::clearObsoleteImages(long long p_timeStamp, PreviewSource p_source)
  274. {
  275. auto cache = imageCache(p_source);
  276. for (auto it = cache.begin(); it != cache.end();) {
  277. if (it.value() < p_timeStamp) {
  278. m_editor->removeImage(it.key());
  279. it = cache.erase(it);
  280. } else {
  281. ++it;
  282. }
  283. }
  284. }
  285. void VPreviewManager::clearBlockObsoletePreviewInfo(long long p_timeStamp,
  286. PreviewSource p_source)
  287. {
  288. QSet<int> affectedBlocks;
  289. QVector<int> obsoleteBlocks;
  290. auto blocks = m_highlighter->getPossiblePreviewBlocks();
  291. qDebug() << "possible preview blocks" << blocks;
  292. for (auto i : blocks) {
  293. QTextBlock block = m_document->findBlockByNumber(i);
  294. if (!block.isValid()) {
  295. obsoleteBlocks.append(i);
  296. continue;
  297. }
  298. VTextBlockData *blockData = dynamic_cast<VTextBlockData *>(block.userData());
  299. if (!blockData) {
  300. continue;
  301. }
  302. if (blockData->clearObsoletePreview(p_timeStamp, p_source)) {
  303. affectedBlocks.insert(i);
  304. }
  305. if (blockData->getPreviews().isEmpty()) {
  306. obsoleteBlocks.append(i);
  307. }
  308. }
  309. m_highlighter->clearPossiblePreviewBlocks(obsoleteBlocks);
  310. m_editor->relayout(affectedBlocks);
  311. }
  312. void VPreviewManager::refreshPreview()
  313. {
  314. if (!m_previewEnabled) {
  315. return;
  316. }
  317. clearPreview();
  318. requestUpdateImageLinks();
  319. }