Fix non-integer Font Metrics in Graph (#1545)

This commit is contained in:
Florian Märkl 2019-05-17 13:00:54 +02:00 committed by GitHub
parent 61b0374a82
commit ef22f20548
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 390 additions and 333 deletions

View File

@ -1,84 +1,104 @@
#ifndef CACHEDFONTMETRICS_H #ifndef CACHEDFONTMETRICS_H
#define CACHEDFONTMETRICS_H #define CACHEDFONTMETRICS_H
#include <QObject> #include "common/Metrics.h"
#include <QFont>
#include <QFontMetrics> #include <QObject>
#include <QFont>
class CachedFontMetrics : public QObject #include <QFontMetrics>
{
Q_OBJECT template<typename T>
public: class CachedFontMetrics
explicit CachedFontMetrics(QObject *parent, const QFont &font) {
: QObject(parent), public:
mFontMetrics(font) explicit CachedFontMetrics(const QFont &font)
{ : mFontMetrics(font)
memset(mWidths, 0, sizeof(mWidths)); {
mHeight = mFontMetrics.height(); memset(mWidths, 0, sizeof(mWidths));
} mHeight = mFontMetrics.height();
}
int width(const QChar &ch)
{ T width(const QChar &ch)
auto unicode = ch.unicode(); {
if (unicode >= 0xD800) { //return mFontMetrics.width(ch);
if (unicode >= 0xE000) auto unicode = ch.unicode();
unicode -= 0xE000 - 0xD800; if (unicode >= 0xD800) {
else if (unicode >= 0xE000)
// is lonely surrogate unicode -= 0xE000 - 0xD800;
return mFontMetrics.width(ch); else
} // is lonely surrogate
if (!mWidths[unicode]) return fetchWidth(ch);
return mWidths[unicode] = mFontMetrics.width(ch); }
return mWidths[unicode]; if (!mWidths[unicode])
} return mWidths[unicode] = fetchWidth(ch);
return mWidths[unicode];
int width(const QString &text) }
{
int result = 0; T width(const QString &text)
QChar temp; {
for (const QChar &ch : text) { T result = 0;
if (ch.isHighSurrogate()) QChar temp;
temp = ch; for (const QChar &ch : text) {
else if (ch.isLowSurrogate()) if (ch.isHighSurrogate())
result += mFontMetrics.width(QString(temp) + ch); temp = ch;
else else if (ch.isLowSurrogate())
result += width(ch); result += fetchWidth(QString(temp) + ch);
} else
return result; result += width(ch);
} }
return result;
int height() }
{
return mHeight; T height()
} {
return mHeight;
int position(const QString &text, int offset) }
{
int curWidth = 0; T position(const QString &text, T offset)
QChar temp; {
T curWidth = 0;
for (int i = 0; i < text.length(); i++) { QChar temp;
QChar ch = text[i];
for (int i = 0; i < text.length(); i++) {
if (ch.isHighSurrogate()) QChar ch = text[i];
temp = ch;
else if (ch.isLowSurrogate()) if (ch.isHighSurrogate())
curWidth += mFontMetrics.width(QString(temp) + ch); temp = ch;
else else if (ch.isLowSurrogate())
curWidth += width(ch); curWidth += fetchWidth(QString(temp) + ch);
else
if (curWidth >= offset) { curWidth += width(ch);
return i;
} if (curWidth >= offset) {
} return i;
}
return -1; }
}
return -1;
private: }
QFontMetrics mFontMetrics;
uchar mWidths[0x10000 - 0xE000 + 0xD800]; private:
int mHeight; typename Metrics<T>::FontMetrics mFontMetrics;
}; T mWidths[0x10000 - 0xE000 + 0xD800];
T mHeight;
#endif // CACHEDFONTMETRICS_H
T fetchWidth(QChar c)
{
#if QT_VERSION < QT_VERSION_CHECK(5,11,0)
return mFontMetrics.width(c);
#else
return mFontMetrics.horizontalAdvance(c);
#endif
}
T fetchWidth(const QString &s)
{
#if QT_VERSION < QT_VERSION_CHECK(5,11,0)
return mFontMetrics.width(s);
#else
return mFontMetrics.horizontalAdvance(s);
#endif
}
};
#endif // CACHEDFONTMETRICS_H

26
src/common/Metrics.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef METRICS_H
#define METRICS_H
#include <QtGlobal>
class QRect;
class QRectF;
class QFontMetrics;
class QFontMetricsF;
template<typename T> struct Metrics {};
template<> struct Metrics<int>
{
using Rect = QRect;
using FontMetrics = QFontMetrics;
};
template<> struct Metrics<qreal>
{
using Rect = QRectF;
using FontMetrics = QFontMetricsF;
};
#endif //METRICS_H

View File

@ -1,188 +1,196 @@
/* x64dbg RichTextPainter */ /* x64dbg RichTextPainter */
#include "RichTextPainter.h" #include "RichTextPainter.h"
#include "CachedFontMetrics.h" #include "CachedFontMetrics.h"
#include "common/Configuration.h" #include "common/Configuration.h"
#include <QPainter> #include <QPainter>
#include <QTextBlock> #include <QTextBlock>
#include <QTextFragment> #include <QTextFragment>
//TODO: fix performance (possibly use QTextLayout?) //TODO: fix performance (possibly use QTextLayout?)
void RichTextPainter::paintRichText(QPainter *painter, int x, int y, int w, int h, int xinc,
const List &richText, CachedFontMetrics *fontMetrics)
{ template<typename T>
QPen pen; void RichTextPainter::paintRichText(QPainter *painter, T x, T y, T w, T h, T xinc,
QPen highlightPen; const List &richText, CachedFontMetrics<T> *fontMetrics)
QBrush brush(Qt::cyan); {
for (const CustomRichText_t &curRichText : richText) { QPen pen;
int textWidth = fontMetrics->width(curRichText.text); QPen highlightPen;
int backgroundWidth = textWidth; QBrush brush(Qt::cyan);
if (backgroundWidth + xinc > w) for (const CustomRichText_t &curRichText : richText) {
backgroundWidth = w - xinc; T textWidth = fontMetrics->width(curRichText.text);
if (backgroundWidth <= 0) //stop drawing when going outside the specified width T backgroundWidth = textWidth;
break; if (backgroundWidth + xinc > w)
switch (curRichText.flags) { backgroundWidth = w - xinc;
case FlagNone: //defaults if (backgroundWidth <= 0) //stop drawing when going outside the specified width
pen.setColor(ConfigColor("btext").name()); break;
painter->setPen(pen); switch (curRichText.flags) {
break; case FlagNone: //defaults
case FlagColor: //color only pen.setColor(ConfigColor("btext").name());
pen.setColor(curRichText.textColor); painter->setPen(pen);
painter->setPen(pen); break;
break; case FlagColor: //color only
case FlagBackground: //background only pen.setColor(curRichText.textColor);
if (backgroundWidth > 0 && curRichText.textBackground.alpha()) { painter->setPen(pen);
brush.setColor(curRichText.textBackground); break;
painter->fillRect(QRect(x + xinc, y, backgroundWidth, h), brush); case FlagBackground: //background only
} if (backgroundWidth > 0 && curRichText.textBackground.alpha()) {
break; brush.setColor(curRichText.textBackground);
case FlagAll: //color+background painter->fillRect(QRectF(x + xinc, y, backgroundWidth, h), brush);
if (backgroundWidth > 0 && curRichText.textBackground.alpha()) { }
brush.setColor(curRichText.textBackground); break;
painter->fillRect(QRect(x + xinc, y, backgroundWidth, h), brush); case FlagAll: //color+background
} if (backgroundWidth > 0 && curRichText.textBackground.alpha()) {
pen.setColor(curRichText.textColor); brush.setColor(curRichText.textBackground);
painter->setPen(pen); painter->fillRect(QRectF(x + xinc, y, backgroundWidth, h), brush);
break; }
} pen.setColor(curRichText.textColor);
painter->drawText(QRect(x + xinc, y, w - xinc, h), Qt::TextBypassShaping, curRichText.text); painter->setPen(pen);
if (curRichText.highlight && curRichText.highlightColor.alpha()) { break;
highlightPen.setColor(curRichText.highlightColor); }
highlightPen.setWidth(curRichText.highlightWidth); painter->drawText(typename Metrics<T>::Rect(x + xinc, y, w - xinc, h), Qt::TextBypassShaping, curRichText.text);
painter->setPen(highlightPen); if (curRichText.highlight && curRichText.highlightColor.alpha()) {
int highlightOffsetX = curRichText.highlightConnectPrev ? -1 : 1; highlightPen.setColor(curRichText.highlightColor);
painter->drawLine(x + xinc + highlightOffsetX, y + h - 1, x + xinc + backgroundWidth - 1, highlightPen.setWidth(curRichText.highlightWidth);
y + h - 1); painter->setPen(highlightPen);
} T highlightOffsetX = curRichText.highlightConnectPrev ? -1 : 1;
xinc += textWidth; painter->drawLine(x + xinc + highlightOffsetX, y + h - 1, x + xinc + backgroundWidth - 1,
} y + h - 1);
} }
xinc += textWidth;
/** }
* @brief RichTextPainter::htmlRichText Convert rich text in x64dbg to HTML, for use by other applications }
* @param richText The rich text to be converted to HTML format
* @param textHtml The HTML source. Any previous content will be preserved and new content will be appended at the end. template
* @param textPlain The plain text. Any previous content will be preserved and new content will be appended at the end. void RichTextPainter::paintRichText<qreal>(QPainter *painter, qreal x, qreal y, qreal w, qreal h, qreal xinc,
*/ const List &richText, CachedFontMetrics<qreal> *fontMetrics);
void RichTextPainter::htmlRichText(const List &richText, QString &textHtml, QString &textPlain)
{
for (const CustomRichText_t &curRichText : richText) { /**
if (curRichText.text == " ") { //blank * @brief RichTextPainter::htmlRichText Convert rich text in x64dbg to HTML, for use by other applications
textHtml += " "; * @param richText The rich text to be converted to HTML format
textPlain += " "; * @param textHtml The HTML source. Any previous content will be preserved and new content will be appended at the end.
continue; * @param textPlain The plain text. Any previous content will be preserved and new content will be appended at the end.
} */
switch (curRichText.flags) { void RichTextPainter::htmlRichText(const List &richText, QString &textHtml, QString &textPlain)
case FlagNone: //defaults {
textHtml += "<span>"; for (const CustomRichText_t &curRichText : richText) {
break; if (curRichText.text == " ") { //blank
case FlagColor: //color only textHtml += " ";
textHtml += QString("<span style=\"color:%1\">").arg(curRichText.textColor.name()); textPlain += " ";
break; continue;
case FlagBackground: //background only }
if (curRichText.textBackground != switch (curRichText.flags) {
Qt::transparent) // QColor::name() returns "#000000" for transparent color. That's not desired. Leave it blank. case FlagNone: //defaults
textHtml += QString("<span style=\"background-color:%1\">").arg(curRichText.textBackground.name()); textHtml += "<span>";
else break;
textHtml += QString("<span>"); case FlagColor: //color only
break; textHtml += QString("<span style=\"color:%1\">").arg(curRichText.textColor.name());
case FlagAll: //color+background break;
if (curRichText.textBackground != case FlagBackground: //background only
Qt::transparent) // QColor::name() returns "#000000" for transparent color. That's not desired. Leave it blank. if (curRichText.textBackground !=
textHtml += QString("<span style=\"color:%1; background-color:%2\">").arg( Qt::transparent) // QColor::name() returns "#000000" for transparent color. That's not desired. Leave it blank.
curRichText.textColor.name(), curRichText.textBackground.name()); textHtml += QString("<span style=\"background-color:%1\">").arg(curRichText.textBackground.name());
else else
textHtml += QString("<span style=\"color:%1\">").arg(curRichText.textColor.name()); textHtml += QString("<span>");
break; break;
} case FlagAll: //color+background
if (curRichText.highlight) //Underline highlighted token if (curRichText.textBackground !=
textHtml += "<u>"; Qt::transparent) // QColor::name() returns "#000000" for transparent color. That's not desired. Leave it blank.
textHtml += curRichText.text.toHtmlEscaped(); textHtml += QString("<span style=\"color:%1; background-color:%2\">").arg(
if (curRichText.highlight) curRichText.textColor.name(), curRichText.textBackground.name());
textHtml += "</u>"; else
textHtml += "</span>"; //Close the tag textHtml += QString("<span style=\"color:%1\">").arg(curRichText.textColor.name());
textPlain += curRichText.text; break;
} }
} if (curRichText.highlight) //Underline highlighted token
textHtml += "<u>";
RichTextPainter::List RichTextPainter::fromTextDocument(const QTextDocument &doc) textHtml += curRichText.text.toHtmlEscaped();
{ if (curRichText.highlight)
List r; textHtml += "</u>";
textHtml += "</span>"; //Close the tag
for (QTextBlock block = doc.begin(); block != doc.end(); block = block.next()) { textPlain += curRichText.text;
for (QTextBlock::iterator it = block.begin(); it != block.end(); ++it) { }
QTextFragment fragment = it.fragment(); }
QTextCharFormat format = fragment.charFormat();
RichTextPainter::List RichTextPainter::fromTextDocument(const QTextDocument &doc)
CustomRichText_t text; {
text.text = fragment.text(); List r;
text.textColor = format.foreground().color();
text.textBackground = format.background().color(); for (QTextBlock block = doc.begin(); block != doc.end(); block = block.next()) {
for (QTextBlock::iterator it = block.begin(); it != block.end(); ++it) {
bool hasForeground = format.hasProperty(QTextFormat::ForegroundBrush); QTextFragment fragment = it.fragment();
bool hasBackground = format.hasProperty(QTextFormat::BackgroundBrush); QTextCharFormat format = fragment.charFormat();
if (hasForeground && !hasBackground) { CustomRichText_t text;
text.flags = FlagColor; text.text = fragment.text();
} else if (!hasForeground && hasBackground) { text.textColor = format.foreground().color();
text.flags = FlagBackground; text.textBackground = format.background().color();
} else if (hasForeground && hasBackground) {
text.flags = FlagAll; bool hasForeground = format.hasProperty(QTextFormat::ForegroundBrush);
} else { bool hasBackground = format.hasProperty(QTextFormat::BackgroundBrush);
text.flags = FlagNone;
} if (hasForeground && !hasBackground) {
text.flags = FlagColor;
r.push_back(text); } else if (!hasForeground && hasBackground) {
} text.flags = FlagBackground;
} } else if (hasForeground && hasBackground) {
text.flags = FlagAll;
return r; } else {
} text.flags = FlagNone;
}
RichTextPainter::List RichTextPainter::cropped(const RichTextPainter::List &richText, int maxCols,
const QString &indicator, bool *croppedOut) r.push_back(text);
{ }
List r; }
r.reserve(richText.size());
return r;
int cols = 0; }
bool cropped = false;
for (const auto &text : richText) { RichTextPainter::List RichTextPainter::cropped(const RichTextPainter::List &richText, int maxCols,
int textLength = text.text.size(); const QString &indicator, bool *croppedOut)
if (cols + textLength <= maxCols) { {
r.push_back(text); List r;
cols += textLength; r.reserve(richText.size());
} else if (cols == maxCols) {
break; int cols = 0;
} else { bool cropped = false;
CustomRichText_t croppedText = text; for (const auto &text : richText) {
croppedText.text.truncate(maxCols - cols); int textLength = text.text.size();
r.push_back(croppedText); if (cols + textLength <= maxCols) {
cropped = true; r.push_back(text);
break; cols += textLength;
} } else if (cols == maxCols) {
} break;
} else {
if (cropped && !indicator.isEmpty()) { CustomRichText_t croppedText = text;
int indicatorCropLength = indicator.length(); croppedText.text.truncate(maxCols - cols);
if (indicatorCropLength > maxCols) { r.push_back(croppedText);
indicatorCropLength = maxCols; cropped = true;
} break;
}
while (!r.empty()) { }
auto &text = r.back();
if (cropped && !indicator.isEmpty()) {
if (text.text.length() >= indicatorCropLength) { int indicatorCropLength = indicator.length();
text.text.replace(text.text.length() - indicatorCropLength, indicatorCropLength, indicator); if (indicatorCropLength > maxCols) {
break; indicatorCropLength = maxCols;
} }
indicatorCropLength -= text.text.length(); while (!r.empty()) {
r.pop_back(); auto &text = r.back();
}
} if (text.text.length() >= indicatorCropLength) {
text.text.replace(text.text.length() - indicatorCropLength, indicatorCropLength, indicator);
if (croppedOut) { break;
*croppedOut = cropped; }
}
return r; indicatorCropLength -= text.text.length();
} r.pop_back();
}
}
if (croppedOut) {
*croppedOut = cropped;
}
return r;
}

View File

@ -1,48 +1,52 @@
/* x64dbg RichTextPainter */ /* x64dbg RichTextPainter */
#ifndef RICHTEXTPAINTER_H #ifndef RICHTEXTPAINTER_H
#define RICHTEXTPAINTER_H #define RICHTEXTPAINTER_H
#include <QString> #include "common/Metrics.h"
#include <QTextDocument>
#include <QColor> #include <QString>
#include <vector> #include <QTextDocument>
#include <QColor>
class CachedFontMetrics; #include <vector>
class QPainter;
class QFontMetricsF;
class RichTextPainter template<typename T> class CachedFontMetrics;
{ class QPainter;
public:
//structures class RichTextPainter
enum CustomRichTextFlags { {
FlagNone, public:
FlagColor, //structures
FlagBackground, enum CustomRichTextFlags {
FlagAll FlagNone,
}; FlagColor,
FlagBackground,
struct CustomRichText_t { FlagAll
QString text; };
QColor textColor;
QColor textBackground; struct CustomRichText_t {
CustomRichTextFlags flags; QString text;
bool highlight = false; QColor textColor;
QColor highlightColor; QColor textBackground;
int highlightWidth = 2; CustomRichTextFlags flags;
bool highlightConnectPrev = false; bool highlight = false;
}; QColor highlightColor;
int highlightWidth = 2;
typedef std::vector<CustomRichText_t> List; bool highlightConnectPrev = false;
};
//functions
static void paintRichText(QPainter *painter, int x, int y, int w, int h, int xinc, typedef std::vector<CustomRichText_t> List;
const List &richText, CachedFontMetrics *fontMetrics);
static void htmlRichText(const List &richText, QString &textHtml, QString &textPlain); //functions
template<typename T = qreal>
static List fromTextDocument(const QTextDocument &doc); static void paintRichText(QPainter *painter, T x, T y, T w, T h, T xinc,
const List &richText, CachedFontMetrics<T> *fontMetrics);
static List cropped(const List &richText, int maxCols, const QString &indicator = nullptr, static void htmlRichText(const List &richText, QString &textHtml, QString &textPlain);
bool *croppedOut = nullptr);
}; static List fromTextDocument(const QTextDocument &doc);
#endif // RICHTEXTPAINTER_H static List cropped(const List &richText, int maxCols, const QString &indicator = nullptr,
bool *croppedOut = nullptr);
};
#endif // RICHTEXTPAINTER_H

View File

@ -377,8 +377,7 @@ void DisassemblerGraphView::initFont()
charWidth = metrics.width('X'); charWidth = metrics.width('X');
charHeight = static_cast<int>(metrics.height()); charHeight = static_cast<int>(metrics.height());
charOffset = 0; charOffset = 0;
delete mFontMetrics; mFontMetrics.reset(new CachedFontMetrics<qreal>(font()));
mFontMetrics = new CachedFontMetrics(this, font());
} }
void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block) void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
@ -464,7 +463,7 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
// Highlight selected tokens // Highlight selected tokens
if (highlight_token != nullptr) { if (highlight_token != nullptr) {
int y = static_cast<int>(blockY + (2 * charWidth) + (db.header_text.lines.size() * charHeight)); int y = static_cast<int>(blockY + (2 * charWidth) + (db.header_text.lines.size() * charHeight));
int tokenWidth = mFontMetrics->width(highlight_token->content); qreal tokenWidth = mFontMetrics->width(highlight_token->content);
for (const Instr &instr : db.instrs) { for (const Instr &instr : db.instrs) {
int pos = -1; int pos = -1;
@ -477,19 +476,19 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
continue; continue;
} }
int widthBefore = mFontMetrics->width(instr.plainText.left(pos)); qreal widthBefore = mFontMetrics->width(instr.plainText.left(pos));
if (charWidth * 3 + widthBefore > block.width - (10 + 2 * charWidth)) { if (charWidth * 3 + widthBefore > block.width - (10 + 2 * charWidth)) {
continue; continue;
} }
int highlightWidth = tokenWidth; qreal highlightWidth = tokenWidth;
if (charWidth * 3 + widthBefore + tokenWidth >= block.width - (10 + 2 * charWidth)) { if (charWidth * 3 + widthBefore + tokenWidth >= block.width - (10 + 2 * charWidth)) {
highlightWidth = static_cast<int>(block.width - widthBefore - (10 + 4 * charWidth)); highlightWidth = block.width - widthBefore - (10 + 4 * charWidth);
} }
QColor selectionColor = ConfigColor("wordhl"); QColor selectionColor = ConfigColor("wordhl");
p.fillRect(QRect(static_cast<int>(blockX + charWidth * 3 + widthBefore), y, highlightWidth, p.fillRect(QRectF(blockX + charWidth * 3 + widthBefore, y, highlightWidth,
charHeight), selectionColor); charHeight), selectionColor);
} }
@ -535,8 +534,8 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
continue; continue;
} }
RichTextPainter::paintRichText(&p, static_cast<int>(x), y, block.width, charHeight, 0, line, RichTextPainter::paintRichText<qreal>(&p, x, y, block.width, charHeight, 0, line,
mFontMetrics); mFontMetrics.get());
y += charHeight; y += charHeight;
} }
@ -569,9 +568,9 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
QRectF bpRect(x - rectSize / 3.0, y + (charHeight - rectSize) / 2.0, rectSize, rectSize); QRectF bpRect(x - rectSize / 3.0, y + (charHeight - rectSize) / 2.0, rectSize, rectSize);
Q_UNUSED(bpRect); Q_UNUSED(bpRect);
RichTextPainter::paintRichText(&p, static_cast<int>(x + charWidth), y, RichTextPainter::paintRichText<qreal>(&p, x + charWidth, y,
static_cast<int>(block.width - charWidth), charHeight, 0, line, block.width - charWidth, charHeight, 0, line,
mFontMetrics); mFontMetrics.get());
y += charHeight; y += charHeight;
} }

View File

@ -141,7 +141,7 @@ private:
Token *highlight_token; Token *highlight_token;
// Font data // Font data
CachedFontMetrics *mFontMetrics; std::unique_ptr<CachedFontMetrics<qreal>> mFontMetrics;
qreal charWidth; qreal charWidth;
int charHeight; int charHeight;
int charOffset; int charOffset;