C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 9s
JS/TS / lint (pull_request) Successful in 9s
JS/TS / fmt (pull_request) Successful in 16s
C++ / clang-tidy (pull_request) Failing after 43s
Python / static (pull_request) Failing after 57s
Rust / fmt (pull_request) Successful in 1m30s
Rust / build (pull_request) Successful in 2m5s
Rust / clippy (pull_request) Successful in 1m55s
Python / verify (pull_request) Successful in 2m23s
227 lines
5.8 KiB
C++
227 lines
5.8 KiB
C++
#include "mathtext.hpp"
|
|
|
|
#include "latinmodern-fonts.hpp"
|
|
|
|
#include <jkqtmathtext/jkqtmathtext.h>
|
|
|
|
#include <QBuffer>
|
|
#include <QCoreApplication>
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QFontDatabase>
|
|
#include <QHash>
|
|
#include <QPointer>
|
|
#include <QThreadPool>
|
|
|
|
namespace ZShell::llm {
|
|
|
|
namespace {
|
|
constexpr int kRenderMargin = 2;
|
|
constexpr unsigned int kResolutionDpi = 96;
|
|
constexpr int kCacheLimit = 512;
|
|
|
|
struct LatinModern {
|
|
bool roman = false;
|
|
bool math = false;
|
|
};
|
|
|
|
const LatinModern& loadLatinModern() {
|
|
static const LatinModern fonts = [] {
|
|
LatinModern result;
|
|
const auto add = [](const unsigned char* data,
|
|
size_t size,
|
|
const QString& fileName,
|
|
const QString& family) {
|
|
const QString path = QDir::tempPath() + QLatin1Char('/') + fileName;
|
|
{
|
|
QFile f(path);
|
|
if (!f.open(QIODevice::WriteOnly) ||
|
|
f.write(
|
|
reinterpret_cast<const char*>(data),
|
|
static_cast<qint64>(size)) != static_cast<qint64>(size))
|
|
return false;
|
|
}
|
|
const int key = QFontDatabase::addApplicationFont(path);
|
|
if (key < 0) return false;
|
|
return QFontDatabase::applicationFontFamilies(key).contains(family);
|
|
};
|
|
result.roman = add(lmfont::lmroman10_regular,
|
|
sizeof(lmfont::lmroman10_regular),
|
|
QStringLiteral("lmroman10-regular.otf"),
|
|
QStringLiteral("LMRoman10")) &&
|
|
add(lmfont::lmroman10_italic,
|
|
sizeof(lmfont::lmroman10_italic),
|
|
QStringLiteral("lmroman10-italic.otf"),
|
|
QStringLiteral("LMRoman10")) &&
|
|
add(lmfont::lmroman10_bold,
|
|
sizeof(lmfont::lmroman10_bold),
|
|
QStringLiteral("lmroman10-bold.otf"),
|
|
QStringLiteral("LMRoman10")) &&
|
|
add(lmfont::lmroman10_bolditalic,
|
|
sizeof(lmfont::lmroman10_bolditalic),
|
|
QStringLiteral("lmroman10-bolditalic.otf"),
|
|
QStringLiteral("LMRoman10"));
|
|
result.math =
|
|
add(lmfont::latinmodern_math,
|
|
sizeof(lmfont::latinmodern_math),
|
|
QStringLiteral("latinmodern-math.otf"),
|
|
QStringLiteral("Latin Modern Math"));
|
|
return result;
|
|
}();
|
|
return fonts;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
LlmMathText::LlmMathText(QObject* parent)
|
|
: QObject(parent)
|
|
, m_renderer(
|
|
std::make_shared<JKQTMathText>(nullptr, /* useFontsForGUI */ true)) {
|
|
const LatinModern& fonts = loadLatinModern();
|
|
if (fonts.roman)
|
|
m_renderer->setFontRomanAndMath(
|
|
QStringLiteral("LMRoman10"), JKQTMathTextFontEncoding::MTFEUnicode);
|
|
if (fonts.math) {
|
|
m_renderer->setFontMathRoman(
|
|
QStringLiteral("Latin Modern Math"),
|
|
JKQTMathTextFontEncoding::MTFEUnicode);
|
|
m_renderer->setFallbackFontSymbols(
|
|
QStringLiteral("Latin Modern Math"),
|
|
JKQTMathTextFontEncoding::MTFEUnicode);
|
|
}
|
|
}
|
|
|
|
void LlmMathText::setLatex(const QString& value) {
|
|
if (m_latex == value) return;
|
|
m_latex = value;
|
|
reRender();
|
|
}
|
|
|
|
void LlmMathText::setColor(const QColor& value) {
|
|
if (m_color == value) return;
|
|
m_color = value;
|
|
reRender();
|
|
}
|
|
|
|
void LlmMathText::setFontPointSize(double value) {
|
|
if (qFuzzyCompare(m_fontPointSize, value)) return;
|
|
m_fontPointSize = value;
|
|
reRender();
|
|
}
|
|
|
|
void LlmMathText::setDevicePixelRatio(qreal value) {
|
|
if (qFuzzyCompare(m_devicePixelRatio, value)) return;
|
|
m_devicePixelRatio = value;
|
|
reRender();
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct MathRender {
|
|
bool ok = false;
|
|
QImage image;
|
|
QUrl url;
|
|
qreal width = 0;
|
|
qreal height = 0;
|
|
};
|
|
|
|
QHash<QString, MathRender>& mathCache() {
|
|
static QHash<QString, MathRender> cache;
|
|
return cache;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void LlmMathText::reRender() {
|
|
++m_requestId;
|
|
if (m_latex.trimmed().isEmpty()) {
|
|
m_image = QImage();
|
|
m_imageUrl = QUrl();
|
|
m_width = 0;
|
|
m_height = 0;
|
|
m_ok = false;
|
|
Q_EMIT changed();
|
|
return;
|
|
}
|
|
|
|
const QString key = m_latex + QLatin1Char(0x1f) + m_color.name() +
|
|
QLatin1Char(0x1f) + QString::number(m_fontPointSize) +
|
|
QLatin1Char(0x1f) + QString::number(m_devicePixelRatio);
|
|
if (auto it = mathCache().find(key); it != mathCache().end()) {
|
|
m_image = it->image;
|
|
m_imageUrl = it->url;
|
|
m_width = it->width;
|
|
m_height = it->height;
|
|
m_ok = it->ok;
|
|
Q_EMIT changed();
|
|
return;
|
|
}
|
|
|
|
if (m_inFlight) return;
|
|
m_inFlight = true;
|
|
|
|
const QString latex = m_latex;
|
|
const QColor color = m_color;
|
|
const double pointSize = m_fontPointSize;
|
|
const qreal dpr = m_devicePixelRatio;
|
|
auto renderer = m_renderer;
|
|
QThreadPool::globalInstance()->start(
|
|
[this, renderer, id = m_requestId, key, latex, color, pointSize, dpr]() {
|
|
MathRender render;
|
|
renderer->setFontPointSize(pointSize);
|
|
renderer->setFontColor(color);
|
|
if (renderer->parse(
|
|
latex,
|
|
JKQTMathText::LatexParser,
|
|
JKQTMathText::DefaultParseOptions)) {
|
|
const QImage image = renderer->drawIntoImage(
|
|
/* drawBoxes */ false,
|
|
QColor(Qt::transparent),
|
|
kRenderMargin,
|
|
dpr,
|
|
kResolutionDpi);
|
|
if (!image.isNull()) {
|
|
QByteArray png;
|
|
{
|
|
QBuffer buffer(&png);
|
|
buffer.open(QIODevice::WriteOnly);
|
|
image.save(&buffer, "PNG");
|
|
}
|
|
render.image = image;
|
|
render.url = QUrl(
|
|
QStringLiteral("data:image/png;base64,") +
|
|
QString::fromLatin1(png.toBase64()));
|
|
render.width = image.width() / dpr;
|
|
render.height = image.height() / dpr;
|
|
render.ok = true;
|
|
}
|
|
}
|
|
QPointer<LlmMathText> guard(this);
|
|
QMetaObject::invokeMethod(
|
|
QCoreApplication::instance(),
|
|
[guard, id, key, render = std::move(render)]() mutable {
|
|
LlmMathText* self = guard;
|
|
if (!self) return;
|
|
self->m_inFlight = false;
|
|
if (id != self->m_requestId) {
|
|
self->reRender();
|
|
return;
|
|
}
|
|
if (render.ok) {
|
|
auto& cache = mathCache();
|
|
if (cache.size() >= kCacheLimit) cache.clear();
|
|
cache.insert(key, render);
|
|
}
|
|
self->m_image = render.image;
|
|
self->m_imageUrl = render.url;
|
|
self->m_width = render.width;
|
|
self->m_height = render.height;
|
|
self->m_ok = render.ok;
|
|
Q_EMIT self->changed();
|
|
},
|
|
Qt::QueuedConnection);
|
|
});
|
|
}
|
|
|
|
} // namespace ZShell::llm
|