lib/private/Preview/TXT.php
<?php
/**
* @author Georg Ehrke <georg@owncloud.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Nmz <nemesiz@nmz.lt>
* @author Robin Appelman <icewind@owncloud.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @copyright Copyright (c) 2018, ownCloud GmbH
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\Preview;
use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Preview\IProvider2;
use OC\Utf8Analyzer;
class TXT implements IProvider2 {
private $utf8Analyzer;
public function __construct() {
$this->utf8Analyzer = new Utf8Analyzer();
}
/**
* {@inheritDoc}
*/
public function getMimeType() {
return '/text\/plain/';
}
/**
* {@inheritDoc}
*
* Note that the thumbnail might show "squared characters" if multiple scripts (Arabic,
* Japanese, etc) are present in the text that will be shown in the preview.
*/
public function getThumbnail(File $file, $maxX, $maxY, $scalingUp) {
$stream = $file->fopen('r');
$content = \stream_get_contents($stream, 2048);
\fclose($stream);
//don't create previews of empty text files
if (\trim($content) === '') {
return false;
}
$analyzerOpts = ['count', 'lines'];
$info = $this->utf8Analyzer->analyzeString($content, $analyzerOpts);
$fontSizePixels = ($maxX) ? (int) ((4 / 32) * $maxX) : 4; //4px
$fontSizePoints = $fontSizePixels * 0.75; // 3pt = 4px
$lineSize = \ceil($fontSizePixels * 1.25);
$image = \imagecreate($maxX, $maxY);
\imagecolorallocate($image, 255, 255, 255);
$textColor = \imagecolorallocate($image, 0, 0, 0);
$canUseTTF = \function_exists('imagettftext');
if ($canUseTTF) {
// FIXME: We'll only use a font based on the characters detected. In case the characters
// contain multiple scripts (Arabic, Japanese, etc), not all characters will be shown
// in the preview.
$fontFile = $this->getFontFile($info);
}
foreach ($info['lines']['lines'] as $index => $line) {
$index = $index + 1;
$x = (int) 1;
$y = (int) ($index * $lineSize);
if ($canUseTTF === true) {
$charsInLine = \count($line);
$initialNChars = ceil($maxX / $fontSizePixels); // initial estimation for the number of chars we want to draw
$nChars = 0;
do {
$nChars += $initialNChars;
$printLine = \implode('', \array_slice($line, 0, $nChars));
// check the bounding box, it might be smaller than the initial estimation, so it
// might be possible to fit more chars in the image
$bbox = \imagettfbbox($fontSizePoints, 0, $fontFile, $printLine);
$bboxWidth = $bbox[2] - $bbox[0];
} while ($bboxWidth < $maxX && $nChars < $charsInLine);
\imagettftext($image, $fontSizePoints, 0, $x, $y, $textColor, $fontFile, $printLine);
} else {
$y -= $fontSizePixels;
$printLine = \implode('', \array_slice($line, 0, $nChars));
\imagestring($image, 1, $x, $y, $printLine, $textColor);
}
if (($index * $lineSize) >= $maxY) {
break;
}
}
$image = new \OC_Image($image);
return $image->valid() ? $image : false;
}
/**
* @inheritdoc
*/
public function isAvailable(FileInfo $file) {
return $file->getSize() > 0;
}
/**
* Determine which font will be used
*
* @param array $info the information coming from the utf8Analyzer. Information of the
* "count" processor of the utf8Analyzer is required.
* @return string the chosen font file
*/
private function getFontFile(array $info): string {
$fontFileDir = __DIR__;
$fontFileDir .= '/../../../core/fonts/';
$fontSet = [
'Han' => 'NotoSansCJKsc/NotoSansMonoCJKsc-Regular.otf', // chinese
'Hiragana' => 'NotoSansCJKjp/NotoSansMonoCJKjp-Regular.otf', // japanese
'Katakana' => 'NotoSansCJKjp/NotoSansMonoCJKjp-Regular.otf', // japanese
'Hangul' => 'NotoSansCJKkr/NotoSansMonoCJKkr-Regular.otf', // korean
'Devanagari' => 'NotoSansDevanagari/NotoSansDevanagari-Regular.ttf', // devanagari
'Arabic' => 'NotoSansArabic/NotoSansArabic-Regular.ttf', // arabic
'Latin' => 'NotoSans/NotoSans-Regular.ttf', // latin
];
$countInfo = $info['count'];
\uasort($countInfo, function ($a, $b) {
if ($a === $b) {
return 0;
}
return ($b - $a);
});
// Information is ordered by counted chars, from highest number of counted chars to lowest.
foreach ($countInfo as $key => $value) {
if (!isset($fontSet[$key])) {
continue; // we don't have a specific font for it, so check the next
}
if ($key === 'Han') {
if (isset($countInfo['Hiragana']) || isset($countInfo['Katakana'])) {
// if there are Hiragana or Katakana chars, prioritize japanese instead of chinese
// (font is the same for Hiragana and Katakana)
$font = $fontSet['Hiragana'];
} else {
$font = $fontSet['Han'];
}
} else {
$font = $fontSet[$key];
}
break; // we're only interested in the most used script. Once we've chosen a font, we're done
}
// we might not have chosen a font yet (latin or greek text, for example), so use a
// default one
$finalFont = $font ?? 'NotoSans/NotoSans-Regular.ttf';
return "{$fontFileDir}{$finalFont}";
}
}