1<?php
2/**
3 * @copyright Copyright (c) 2016, ownCloud, Inc.
4 *
5 * @author Joas Schilling <coding@schilljs.com>
6 * @author Julius Härtl <jus@bitgrid.net>
7 * @author Morris Jobke <hey@morrisjobke.de>
8 * @author Robin Appelman <robin@icewind.nl>
9 * @author Roeland Jago Douma <roeland@famdouma.nl>
10 *
11 * @license AGPL-3.0
12 *
13 * This code is free software: you can redistribute it and/or modify
14 * it under the terms of the GNU Affero General Public License, version 3,
15 * as published by the Free Software Foundation.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU Affero General Public License for more details.
21 *
22 * You should have received a copy of the GNU Affero General Public License, version 3,
23 * along with this program. If not, see <http://www.gnu.org/licenses/>
24 *
25 */
26namespace OC\Preview;
27
28use OCP\Files\File;
29use OCP\IImage;
30
31class MarkDown extends TXT {
32	/**
33	 * {@inheritDoc}
34	 */
35	public function getMimeType(): string {
36		return '/text\/(x-)?markdown/';
37	}
38
39	public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
40		$content = $file->fopen('r');
41
42		if ($content === false) {
43			return null;
44		}
45
46		$content = stream_get_contents($content,3000);
47
48		//don't create previews of empty text files
49		if (trim($content) === '') {
50			return null;
51		}
52
53		// Merge text paragraph lines that might belong together
54		$content = preg_replace('/^(\s*)\*\s/mU', '$1- ', $content);
55
56		$content = preg_replace('/((?!^(\s*-|#)).*)(\w|\\|\.)(\r\n|\n|\r)(\w|\*)/mU', '$1 $3', $content);
57
58		// Remove markdown symbols that we cannot easily represent in rendered text in the preview
59		$content = preg_replace('/\*\*(.*)\*\*/U', '$1', $content);
60		$content = preg_replace('/\*(.*)\*/U', '$1', $content);
61		$content = preg_replace('/\_\_(.*)\_\_/U', '$1', $content);
62		$content = preg_replace('/\_(.*)\_/U', '$1', $content);
63		$content = preg_replace('/\~\~(.*)\~\~/U', '$1', $content);
64
65		$content = preg_replace('/\!?\[((.|\n)*)\]\((.*)\)/mU', '$1 ($3)', $content);
66		$content = preg_replace('/\n\n+/', "\n", $content);
67
68		$content = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $content);
69
70		$lines = preg_split("/\r\n|\n|\r/", $content);
71
72		// Define text size of text file preview
73		$fontSize = $maxX ? (int) ((1 / ($maxX >= 512 ? 60 : 40) * $maxX)) : 10;
74
75		$image = imagecreate($maxX, $maxY);
76		imagecolorallocate($image, 255, 255, 255);
77		$textColor = imagecolorallocate($image, 0, 0, 0);
78
79		$fontFile = __DIR__ . '/../../../core/fonts/NotoSans-Regular.ttf';
80		$fontFileBold = __DIR__ . '/../../../core/fonts/NotoSans-Bold.ttf';
81
82		$canUseTTF = function_exists('imagettftext');
83
84		$textOffset = (int)min($maxX * 0.05, $maxY * 0.05);
85		$nextLineStart = 0;
86		$y = $textOffset;
87		foreach ($lines as $line) {
88			$actualFontSize = $fontSize;
89			if (mb_strpos($line, '# ') === 0) {
90				$actualFontSize *= 2;
91			}
92			if (mb_strpos($line, '## ') === 0) {
93				$actualFontSize *= 1.8;
94			}
95			if (mb_strpos($line, '### ') === 0) {
96				$actualFontSize *= 1.6;
97			}
98			if (mb_strpos($line, '#### ') === 0) {
99				$actualFontSize *= 1.4;
100			}
101			if (mb_strpos($line, '##### ') === 0) {
102				$actualFontSize *= 1.2;
103			}
104			if (mb_strpos($line, '###### ') === 0) {
105				$actualFontSize *= 1.1;
106			}
107
108			// Add spacing before headlines
109			if ($actualFontSize !== $fontSize && $y !== $textOffset) {
110				$y += (int)($actualFontSize * 2);
111			}
112
113			$x = $textOffset;
114			$y += (int)($nextLineStart + $actualFontSize);
115
116			if ($canUseTTF === true) {
117				$wordWrap = (int)((1 / $actualFontSize * 1.3) * $maxX);
118
119				// Get rid of markdown symbols that we still needed for the font size
120				$line = preg_replace('/^#*\s/', '', $line);
121
122				$wrappedText = wordwrap($line, $wordWrap,"\n");
123				$linesWrapped = count(explode("\n", $wrappedText));
124				imagettftext($image, $actualFontSize, 0, $x, $y, $textColor, $actualFontSize === $fontSize ? $fontFile : $fontFileBold, $wrappedText);
125				$nextLineStart = (int)($linesWrapped * ceil($actualFontSize * 2));
126				if ($actualFontSize !== $fontSize && $y !== $textOffset) {
127					$nextLineStart -= $actualFontSize;
128				}
129			} else {
130				$y -= $fontSize;
131				imagestring($image, 1, $x, $y, $line, $textColor);
132				$nextLineStart = $fontSize;
133			}
134
135			if ($y >= $maxY) {
136				break;
137			}
138		}
139
140		$imageObject = new \OC_Image();
141		$imageObject->setResource($image);
142
143		return $imageObject->valid() ? $imageObject : null;
144	}
145}
146