File indexing completed on 2024-04-28 04:58:04
0001 /* This file is part of the KDE libraries 0002 SPDX-FileCopyrightText: 2000, 2002 Carsten Pfeiffer <pfeiffer@kde.org> 0003 SPDX-FileCopyrightText: 2000 Malte Starostik <malte@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "textcreator.h" 0009 0010 #include <QFile> 0011 #include <QFontDatabase> 0012 #include <QImage> 0013 #include <QPainter> 0014 #include <QPalette> 0015 #include <QTextCodec> 0016 #include <QTextDocument> 0017 0018 #include <KDesktopFile> 0019 #include <KPluginFactory> 0020 #include <KSyntaxHighlighting/Definition> 0021 #include <KSyntaxHighlighting/SyntaxHighlighter> 0022 #include <KSyntaxHighlighting/Theme> 0023 0024 // TODO Fix or remove kencodingprober code 0025 // #include <kencodingprober.h> 0026 0027 K_PLUGIN_CLASS_WITH_JSON(TextCreator, "textthumbnail.json") 0028 0029 TextCreator::TextCreator(QObject *parent, const QVariantList &args) 0030 : KIO::ThumbnailCreator(parent, args) 0031 , m_data(nullptr) 0032 , m_dataSize(0) 0033 { 0034 } 0035 0036 TextCreator::~TextCreator() 0037 { 0038 delete[] m_data; 0039 } 0040 0041 static QTextCodec *codecFromContent(const char *data, int dataSize) 0042 { 0043 #if 0 // ### Use this when KEncodingProber does not return junk encoding for UTF-8 data) 0044 KEncodingProber prober; 0045 prober.feed(data, dataSize); 0046 return QTextCodec::codecForName(prober.encoding()); 0047 #else 0048 QByteArray ba = QByteArray::fromRawData(data, dataSize); 0049 // try to detect UTF text, fall back to locale default (which is usually UTF-8) 0050 return QTextCodec::codecForUtfText(ba, QTextCodec::codecForLocale()); 0051 #endif 0052 } 0053 0054 KIO::ThumbnailResult TextCreator::create(const KIO::ThumbnailRequest &request) 0055 { 0056 const QString path = request.url().toLocalFile(); 0057 // Desktop files, .directory files, and flatpakrefs aren't traditional 0058 // text files, so their icons should be shown instead 0059 if (KDesktopFile::isDesktopFile(path) || path.endsWith(QStringLiteral(".directory")) || path.endsWith(QStringLiteral(".flatpakref"))) { 0060 return KIO::ThumbnailResult::fail(); 0061 } 0062 0063 bool ok = false; 0064 0065 // determine some sizes... 0066 // example: width: 60, height: 64 0067 0068 const int width = request.targetSize().width(); 0069 const int height = request.targetSize().height(); 0070 const qreal dpr = request.devicePixelRatio(); 0071 0072 QImage img; 0073 0074 QSize pixmapSize(width, height); 0075 if (height * 3 > width * 4) 0076 pixmapSize.setHeight(width * 4 / 3); 0077 else 0078 pixmapSize.setWidth(height * 3 / 4); 0079 0080 if (pixmapSize != m_pixmap.size()) { 0081 m_pixmap = QPixmap(pixmapSize); 0082 m_pixmap.setDevicePixelRatio(dpr); 0083 } 0084 0085 // one pixel for the rectangle, the rest. whitespace 0086 int xborder = 1 + pixmapSize.width() / 16 / dpr; // minimum x-border 0087 int yborder = 1 + pixmapSize.height() / 16 / dpr; // minimum y-border 0088 0089 // this font is supposed to look good at small sizes 0090 QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); 0091 0092 font.setPixelSize(qMax(7.0, qMin(10.0, (pixmapSize.height() / dpr - 2 * yborder) / 16))); 0093 QFontMetrics fm(font); 0094 0095 // calculate a better border so that the text is centered 0096 const QSizeF canvasSize(pixmapSize.width() / dpr - 2 * xborder, pixmapSize.height() / dpr - 2 * yborder); 0097 const int numLines = (int)(canvasSize.height() / fm.height()); 0098 0099 // assumes an average line length of <= 120 chars 0100 const int bytesToRead = 120 * numLines; 0101 0102 // create text-preview 0103 QFile file(path); 0104 if (file.open(QIODevice::ReadOnly)) { 0105 if (!m_data || m_dataSize < bytesToRead + 1) { 0106 delete[] m_data; 0107 m_data = new char[bytesToRead + 1]; 0108 m_dataSize = bytesToRead + 1; 0109 } 0110 0111 int read = file.read(m_data, bytesToRead); 0112 if (read > 0) { 0113 ok = true; 0114 m_data[read] = '\0'; 0115 QString text = codecFromContent(m_data, read)->toUnicode(m_data, read).trimmed(); 0116 // FIXME: maybe strip whitespace and read more? 0117 0118 // If the text contains tabs or consecutive spaces, it is probably 0119 // formatted using white space. Use a fixed pitch font in this case. 0120 const auto textLines = QStringView(text).split(QLatin1Char('\n')); 0121 for (const auto &line : textLines) { 0122 const auto trimmedLine = line.trimmed(); 0123 if (trimmedLine.contains('\t') || trimmedLine.contains(QLatin1String(" "))) { 0124 font.setFamily(QFontDatabase::systemFont(QFontDatabase::FixedFont).family()); 0125 break; 0126 } 0127 } 0128 0129 QColor bgColor = QColor(245, 245, 245); // light-grey background 0130 m_pixmap.fill(bgColor); 0131 0132 QPainter painter(&m_pixmap); 0133 0134 QTextDocument textDocument(text); 0135 0136 // QTextDocument only supports one margin value for all borders, 0137 // so we do a page-in-page behind its back, and do our own borders 0138 textDocument.setDocumentMargin(0); 0139 textDocument.setPageSize(canvasSize); 0140 textDocument.setDefaultFont(font); 0141 0142 QTextOption textOption(Qt::AlignTop | Qt::AlignLeft); 0143 textOption.setTabStopDistance(8 * painter.fontMetrics().horizontalAdvance(QLatin1Char(' '))); 0144 textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); 0145 textDocument.setDefaultTextOption(textOption); 0146 0147 KSyntaxHighlighting::SyntaxHighlighter syntaxHighlighter; 0148 syntaxHighlighter.setDefinition(m_highlightingRepository.definitionForFileName(path)); 0149 const auto highlightingTheme = m_highlightingRepository.defaultTheme(KSyntaxHighlighting::Repository::LightTheme); 0150 syntaxHighlighter.setTheme(highlightingTheme); 0151 syntaxHighlighter.setDocument(&textDocument); 0152 syntaxHighlighter.rehighlight(); 0153 0154 // draw page-in-page, with clipping as needed 0155 painter.translate(xborder, yborder); 0156 textDocument.drawContents(&painter, QRectF(QPointF(0, 0), canvasSize)); 0157 0158 painter.end(); 0159 0160 img = m_pixmap.toImage(); 0161 } 0162 0163 file.close(); 0164 } 0165 return ok ? KIO::ThumbnailResult::pass(img) : KIO::ThumbnailResult::fail(); 0166 } 0167 0168 #include "moc_textcreator.cpp" 0169 #include "textcreator.moc"