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
#define CACHEDFONTMETRICS_H
#include <QObject>
#include <QFont>
#include <QFontMetrics>
class CachedFontMetrics : public QObject
{
Q_OBJECT
public:
explicit CachedFontMetrics(QObject *parent, const QFont &font)
: QObject(parent),
mFontMetrics(font)
{
memset(mWidths, 0, sizeof(mWidths));
mHeight = mFontMetrics.height();
}
int width(const QChar &ch)
{
auto unicode = ch.unicode();
if (unicode >= 0xD800) {
if (unicode >= 0xE000)
unicode -= 0xE000 - 0xD800;
else
// is lonely surrogate
return mFontMetrics.width(ch);
}
if (!mWidths[unicode])
return mWidths[unicode] = mFontMetrics.width(ch);
return mWidths[unicode];
}
int width(const QString &text)
{
int result = 0;
QChar temp;
for (const QChar &ch : text) {
if (ch.isHighSurrogate())
temp = ch;
else if (ch.isLowSurrogate())
result += mFontMetrics.width(QString(temp) + ch);
else
result += width(ch);
}
return result;
}
int height()
{
return mHeight;
}
int position(const QString &text, int offset)
{
int curWidth = 0;
QChar temp;
for (int i = 0; i < text.length(); i++) {
QChar ch = text[i];
if (ch.isHighSurrogate())
temp = ch;
else if (ch.isLowSurrogate())
curWidth += mFontMetrics.width(QString(temp) + ch);
else
curWidth += width(ch);
if (curWidth >= offset) {
return i;
}
}
return -1;
}
private:
QFontMetrics mFontMetrics;
uchar mWidths[0x10000 - 0xE000 + 0xD800];
int mHeight;
};
#endif // CACHEDFONTMETRICS_H
#ifndef CACHEDFONTMETRICS_H
#define CACHEDFONTMETRICS_H
#include "common/Metrics.h"
#include <QObject>
#include <QFont>
#include <QFontMetrics>
template<typename T>
class CachedFontMetrics
{
public:
explicit CachedFontMetrics(const QFont &font)
: mFontMetrics(font)
{
memset(mWidths, 0, sizeof(mWidths));
mHeight = mFontMetrics.height();
}
T width(const QChar &ch)
{
//return mFontMetrics.width(ch);
auto unicode = ch.unicode();
if (unicode >= 0xD800) {
if (unicode >= 0xE000)
unicode -= 0xE000 - 0xD800;
else
// is lonely surrogate
return fetchWidth(ch);
}
if (!mWidths[unicode])
return mWidths[unicode] = fetchWidth(ch);
return mWidths[unicode];
}
T width(const QString &text)
{
T result = 0;
QChar temp;
for (const QChar &ch : text) {
if (ch.isHighSurrogate())
temp = ch;
else if (ch.isLowSurrogate())
result += fetchWidth(QString(temp) + ch);
else
result += width(ch);
}
return result;
}
T height()
{
return mHeight;
}
T position(const QString &text, T offset)
{
T curWidth = 0;
QChar temp;
for (int i = 0; i < text.length(); i++) {
QChar ch = text[i];
if (ch.isHighSurrogate())
temp = ch;
else if (ch.isLowSurrogate())
curWidth += fetchWidth(QString(temp) + ch);
else
curWidth += width(ch);
if (curWidth >= offset) {
return i;
}
}
return -1;
}
private:
typename Metrics<T>::FontMetrics mFontMetrics;
T mWidths[0x10000 - 0xE000 + 0xD800];
T mHeight;
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 */
#include "RichTextPainter.h"
#include "CachedFontMetrics.h"
#include "common/Configuration.h"
#include <QPainter>
#include <QTextBlock>
#include <QTextFragment>
//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)
{
QPen pen;
QPen highlightPen;
QBrush brush(Qt::cyan);
for (const CustomRichText_t &curRichText : richText) {
int textWidth = fontMetrics->width(curRichText.text);
int backgroundWidth = textWidth;
if (backgroundWidth + xinc > w)
backgroundWidth = w - xinc;
if (backgroundWidth <= 0) //stop drawing when going outside the specified width
break;
switch (curRichText.flags) {
case FlagNone: //defaults
pen.setColor(ConfigColor("btext").name());
painter->setPen(pen);
break;
case FlagColor: //color only
pen.setColor(curRichText.textColor);
painter->setPen(pen);
break;
case FlagBackground: //background only
if (backgroundWidth > 0 && curRichText.textBackground.alpha()) {
brush.setColor(curRichText.textBackground);
painter->fillRect(QRect(x + xinc, y, backgroundWidth, h), brush);
}
break;
case FlagAll: //color+background
if (backgroundWidth > 0 && curRichText.textBackground.alpha()) {
brush.setColor(curRichText.textBackground);
painter->fillRect(QRect(x + xinc, y, backgroundWidth, h), brush);
}
pen.setColor(curRichText.textColor);
painter->setPen(pen);
break;
}
painter->drawText(QRect(x + xinc, y, w - xinc, h), Qt::TextBypassShaping, curRichText.text);
if (curRichText.highlight && curRichText.highlightColor.alpha()) {
highlightPen.setColor(curRichText.highlightColor);
highlightPen.setWidth(curRichText.highlightWidth);
painter->setPen(highlightPen);
int highlightOffsetX = curRichText.highlightConnectPrev ? -1 : 1;
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.
* @param textPlain The plain text. Any previous content will be preserved and new content will be appended at the end.
*/
void RichTextPainter::htmlRichText(const List &richText, QString &textHtml, QString &textPlain)
{
for (const CustomRichText_t &curRichText : richText) {
if (curRichText.text == " ") { //blank
textHtml += " ";
textPlain += " ";
continue;
}
switch (curRichText.flags) {
case FlagNone: //defaults
textHtml += "<span>";
break;
case FlagColor: //color only
textHtml += QString("<span style=\"color:%1\">").arg(curRichText.textColor.name());
break;
case FlagBackground: //background only
if (curRichText.textBackground !=
Qt::transparent) // QColor::name() returns "#000000" for transparent color. That's not desired. Leave it blank.
textHtml += QString("<span style=\"background-color:%1\">").arg(curRichText.textBackground.name());
else
textHtml += QString("<span>");
break;
case FlagAll: //color+background
if (curRichText.textBackground !=
Qt::transparent) // QColor::name() returns "#000000" for transparent color. That's not desired. Leave it blank.
textHtml += QString("<span style=\"color:%1; background-color:%2\">").arg(
curRichText.textColor.name(), curRichText.textBackground.name());
else
textHtml += QString("<span style=\"color:%1\">").arg(curRichText.textColor.name());
break;
}
if (curRichText.highlight) //Underline highlighted token
textHtml += "<u>";
textHtml += curRichText.text.toHtmlEscaped();
if (curRichText.highlight)
textHtml += "</u>";
textHtml += "</span>"; //Close the tag
textPlain += curRichText.text;
}
}
RichTextPainter::List RichTextPainter::fromTextDocument(const QTextDocument &doc)
{
List r;
for (QTextBlock block = doc.begin(); block != doc.end(); block = block.next()) {
for (QTextBlock::iterator it = block.begin(); it != block.end(); ++it) {
QTextFragment fragment = it.fragment();
QTextCharFormat format = fragment.charFormat();
CustomRichText_t text;
text.text = fragment.text();
text.textColor = format.foreground().color();
text.textBackground = format.background().color();
bool hasForeground = format.hasProperty(QTextFormat::ForegroundBrush);
bool hasBackground = format.hasProperty(QTextFormat::BackgroundBrush);
if (hasForeground && !hasBackground) {
text.flags = FlagColor;
} else if (!hasForeground && hasBackground) {
text.flags = FlagBackground;
} else if (hasForeground && hasBackground) {
text.flags = FlagAll;
} else {
text.flags = FlagNone;
}
r.push_back(text);
}
}
return r;
}
RichTextPainter::List RichTextPainter::cropped(const RichTextPainter::List &richText, int maxCols,
const QString &indicator, bool *croppedOut)
{
List r;
r.reserve(richText.size());
int cols = 0;
bool cropped = false;
for (const auto &text : richText) {
int textLength = text.text.size();
if (cols + textLength <= maxCols) {
r.push_back(text);
cols += textLength;
} else if (cols == maxCols) {
break;
} else {
CustomRichText_t croppedText = text;
croppedText.text.truncate(maxCols - cols);
r.push_back(croppedText);
cropped = true;
break;
}
}
if (cropped && !indicator.isEmpty()) {
int indicatorCropLength = indicator.length();
if (indicatorCropLength > maxCols) {
indicatorCropLength = maxCols;
}
while (!r.empty()) {
auto &text = r.back();
if (text.text.length() >= indicatorCropLength) {
text.text.replace(text.text.length() - indicatorCropLength, indicatorCropLength, indicator);
break;
}
indicatorCropLength -= text.text.length();
r.pop_back();
}
}
if (croppedOut) {
*croppedOut = cropped;
}
return r;
}
/* x64dbg RichTextPainter */
#include "RichTextPainter.h"
#include "CachedFontMetrics.h"
#include "common/Configuration.h"
#include <QPainter>
#include <QTextBlock>
#include <QTextFragment>
//TODO: fix performance (possibly use QTextLayout?)
template<typename T>
void RichTextPainter::paintRichText(QPainter *painter, T x, T y, T w, T h, T xinc,
const List &richText, CachedFontMetrics<T> *fontMetrics)
{
QPen pen;
QPen highlightPen;
QBrush brush(Qt::cyan);
for (const CustomRichText_t &curRichText : richText) {
T textWidth = fontMetrics->width(curRichText.text);
T backgroundWidth = textWidth;
if (backgroundWidth + xinc > w)
backgroundWidth = w - xinc;
if (backgroundWidth <= 0) //stop drawing when going outside the specified width
break;
switch (curRichText.flags) {
case FlagNone: //defaults
pen.setColor(ConfigColor("btext").name());
painter->setPen(pen);
break;
case FlagColor: //color only
pen.setColor(curRichText.textColor);
painter->setPen(pen);
break;
case FlagBackground: //background only
if (backgroundWidth > 0 && curRichText.textBackground.alpha()) {
brush.setColor(curRichText.textBackground);
painter->fillRect(QRectF(x + xinc, y, backgroundWidth, h), brush);
}
break;
case FlagAll: //color+background
if (backgroundWidth > 0 && curRichText.textBackground.alpha()) {
brush.setColor(curRichText.textBackground);
painter->fillRect(QRectF(x + xinc, y, backgroundWidth, h), brush);
}
pen.setColor(curRichText.textColor);
painter->setPen(pen);
break;
}
painter->drawText(typename Metrics<T>::Rect(x + xinc, y, w - xinc, h), Qt::TextBypassShaping, curRichText.text);
if (curRichText.highlight && curRichText.highlightColor.alpha()) {
highlightPen.setColor(curRichText.highlightColor);
highlightPen.setWidth(curRichText.highlightWidth);
painter->setPen(highlightPen);
T highlightOffsetX = curRichText.highlightConnectPrev ? -1 : 1;
painter->drawLine(x + xinc + highlightOffsetX, y + h - 1, x + xinc + backgroundWidth - 1,
y + h - 1);
}
xinc += textWidth;
}
}
template
void RichTextPainter::paintRichText<qreal>(QPainter *painter, qreal x, qreal y, qreal w, qreal h, qreal xinc,
const List &richText, CachedFontMetrics<qreal> *fontMetrics);
/**
* @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.
* @param textPlain The plain text. Any previous content will be preserved and new content will be appended at the end.
*/
void RichTextPainter::htmlRichText(const List &richText, QString &textHtml, QString &textPlain)
{
for (const CustomRichText_t &curRichText : richText) {
if (curRichText.text == " ") { //blank
textHtml += " ";
textPlain += " ";
continue;
}
switch (curRichText.flags) {
case FlagNone: //defaults
textHtml += "<span>";
break;
case FlagColor: //color only
textHtml += QString("<span style=\"color:%1\">").arg(curRichText.textColor.name());
break;
case FlagBackground: //background only
if (curRichText.textBackground !=
Qt::transparent) // QColor::name() returns "#000000" for transparent color. That's not desired. Leave it blank.
textHtml += QString("<span style=\"background-color:%1\">").arg(curRichText.textBackground.name());
else
textHtml += QString("<span>");
break;
case FlagAll: //color+background
if (curRichText.textBackground !=
Qt::transparent) // QColor::name() returns "#000000" for transparent color. That's not desired. Leave it blank.
textHtml += QString("<span style=\"color:%1; background-color:%2\">").arg(
curRichText.textColor.name(), curRichText.textBackground.name());
else
textHtml += QString("<span style=\"color:%1\">").arg(curRichText.textColor.name());
break;
}
if (curRichText.highlight) //Underline highlighted token
textHtml += "<u>";
textHtml += curRichText.text.toHtmlEscaped();
if (curRichText.highlight)
textHtml += "</u>";
textHtml += "</span>"; //Close the tag
textPlain += curRichText.text;
}
}
RichTextPainter::List RichTextPainter::fromTextDocument(const QTextDocument &doc)
{
List r;
for (QTextBlock block = doc.begin(); block != doc.end(); block = block.next()) {
for (QTextBlock::iterator it = block.begin(); it != block.end(); ++it) {
QTextFragment fragment = it.fragment();
QTextCharFormat format = fragment.charFormat();
CustomRichText_t text;
text.text = fragment.text();
text.textColor = format.foreground().color();
text.textBackground = format.background().color();
bool hasForeground = format.hasProperty(QTextFormat::ForegroundBrush);
bool hasBackground = format.hasProperty(QTextFormat::BackgroundBrush);
if (hasForeground && !hasBackground) {
text.flags = FlagColor;
} else if (!hasForeground && hasBackground) {
text.flags = FlagBackground;
} else if (hasForeground && hasBackground) {
text.flags = FlagAll;
} else {
text.flags = FlagNone;
}
r.push_back(text);
}
}
return r;
}
RichTextPainter::List RichTextPainter::cropped(const RichTextPainter::List &richText, int maxCols,
const QString &indicator, bool *croppedOut)
{
List r;
r.reserve(richText.size());
int cols = 0;
bool cropped = false;
for (const auto &text : richText) {
int textLength = text.text.size();
if (cols + textLength <= maxCols) {
r.push_back(text);
cols += textLength;
} else if (cols == maxCols) {
break;
} else {
CustomRichText_t croppedText = text;
croppedText.text.truncate(maxCols - cols);
r.push_back(croppedText);
cropped = true;
break;
}
}
if (cropped && !indicator.isEmpty()) {
int indicatorCropLength = indicator.length();
if (indicatorCropLength > maxCols) {
indicatorCropLength = maxCols;
}
while (!r.empty()) {
auto &text = r.back();
if (text.text.length() >= indicatorCropLength) {
text.text.replace(text.text.length() - indicatorCropLength, indicatorCropLength, indicator);
break;
}
indicatorCropLength -= text.text.length();
r.pop_back();
}
}
if (croppedOut) {
*croppedOut = cropped;
}
return r;
}

View File

@ -1,48 +1,52 @@
/* x64dbg RichTextPainter */
#ifndef RICHTEXTPAINTER_H
#define RICHTEXTPAINTER_H
#include <QString>
#include <QTextDocument>
#include <QColor>
#include <vector>
class CachedFontMetrics;
class QPainter;
class RichTextPainter
{
public:
//structures
enum CustomRichTextFlags {
FlagNone,
FlagColor,
FlagBackground,
FlagAll
};
struct CustomRichText_t {
QString text;
QColor textColor;
QColor textBackground;
CustomRichTextFlags flags;
bool highlight = false;
QColor highlightColor;
int highlightWidth = 2;
bool highlightConnectPrev = false;
};
typedef std::vector<CustomRichText_t> List;
//functions
static void paintRichText(QPainter *painter, int x, int y, int w, int h, int xinc,
const List &richText, CachedFontMetrics *fontMetrics);
static void htmlRichText(const List &richText, QString &textHtml, QString &textPlain);
static List fromTextDocument(const QTextDocument &doc);
static List cropped(const List &richText, int maxCols, const QString &indicator = nullptr,
bool *croppedOut = nullptr);
};
#endif // RICHTEXTPAINTER_H
/* x64dbg RichTextPainter */
#ifndef RICHTEXTPAINTER_H
#define RICHTEXTPAINTER_H
#include "common/Metrics.h"
#include <QString>
#include <QTextDocument>
#include <QColor>
#include <vector>
class QFontMetricsF;
template<typename T> class CachedFontMetrics;
class QPainter;
class RichTextPainter
{
public:
//structures
enum CustomRichTextFlags {
FlagNone,
FlagColor,
FlagBackground,
FlagAll
};
struct CustomRichText_t {
QString text;
QColor textColor;
QColor textBackground;
CustomRichTextFlags flags;
bool highlight = false;
QColor highlightColor;
int highlightWidth = 2;
bool highlightConnectPrev = false;
};
typedef std::vector<CustomRichText_t> List;
//functions
template<typename T = qreal>
static void paintRichText(QPainter *painter, T x, T y, T w, T h, T xinc,
const List &richText, CachedFontMetrics<T> *fontMetrics);
static void htmlRichText(const List &richText, QString &textHtml, QString &textPlain);
static List fromTextDocument(const QTextDocument &doc);
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');
charHeight = static_cast<int>(metrics.height());
charOffset = 0;
delete mFontMetrics;
mFontMetrics = new CachedFontMetrics(this, font());
mFontMetrics.reset(new CachedFontMetrics<qreal>(font()));
}
void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
@ -464,7 +463,7 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
// Highlight selected tokens
if (highlight_token != nullptr) {
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) {
int pos = -1;
@ -477,19 +476,19 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
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)) {
continue;
}
int highlightWidth = tokenWidth;
qreal highlightWidth = tokenWidth;
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");
p.fillRect(QRect(static_cast<int>(blockX + charWidth * 3 + widthBefore), y, highlightWidth,
p.fillRect(QRectF(blockX + charWidth * 3 + widthBefore, y, highlightWidth,
charHeight), selectionColor);
}
@ -535,8 +534,8 @@ void DisassemblerGraphView::drawBlock(QPainter &p, GraphView::GraphBlock &block)
continue;
}
RichTextPainter::paintRichText(&p, static_cast<int>(x), y, block.width, charHeight, 0, line,
mFontMetrics);
RichTextPainter::paintRichText<qreal>(&p, x, y, block.width, charHeight, 0, line,
mFontMetrics.get());
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);
Q_UNUSED(bpRect);
RichTextPainter::paintRichText(&p, static_cast<int>(x + charWidth), y,
static_cast<int>(block.width - charWidth), charHeight, 0, line,
mFontMetrics);
RichTextPainter::paintRichText<qreal>(&p, x + charWidth, y,
block.width - charWidth, charHeight, 0, line,
mFontMetrics.get());
y += charHeight;
}

View File

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