mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-08-10 21:53:50 +00:00
207 lines
5.3 KiB
C++
207 lines
5.3 KiB
C++
#include "iv/markdown/iv_markdown_microtex.h"
|
|
|
|
#include "base/base_file_utilities.h"
|
|
|
|
#include <QtCore/QSize>
|
|
#include <QtCore/QString>
|
|
#include <QtGui/QPainter>
|
|
|
|
#include <exception>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <utility>
|
|
|
|
#include "latex.h"
|
|
#include "platform/qt/graphic_qt.h"
|
|
|
|
namespace Iv::Markdown {
|
|
namespace {
|
|
|
|
constexpr auto kBytesPerPixel = int64(4);
|
|
constexpr auto kMaxFormulaImageBytes = int64(128) * 1024 * 1024;
|
|
constexpr auto kFormulaForegroundRgba = 0xFFFFFFFFU;
|
|
|
|
std::once_flag MicrotexInitOnce;
|
|
bool MicrotexInitialized = false;
|
|
QString MicrotexInitError;
|
|
|
|
[[nodiscard]] QString ExceptionText(const std::exception &exception) {
|
|
return QString::fromUtf8(exception.what());
|
|
}
|
|
|
|
[[nodiscard]] std::wstring ToWide(const QString &value) {
|
|
auto result = std::wstring();
|
|
result.reserve(size_t(value.size()));
|
|
for (const auto ch : value) {
|
|
result.push_back(wchar_t(ch.unicode()));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
[[nodiscard]] bool PhysicalImageRejected(int64 width, int64 height) {
|
|
if (width <= 0 || height <= 0) {
|
|
return true;
|
|
}
|
|
return (width > std::numeric_limits<int>::max())
|
|
|| (height > std::numeric_limits<int>::max())
|
|
|| (width * height > (kMaxFormulaImageBytes / kBytesPerPixel));
|
|
}
|
|
|
|
[[nodiscard]] bool ComputePhysicalSize(
|
|
QSize logicalSize,
|
|
int ratio,
|
|
QSize *physicalSize) {
|
|
const auto width = int64(logicalSize.width()) * ratio;
|
|
const auto height = int64(logicalSize.height()) * ratio;
|
|
if (PhysicalImageRejected(width, height)) {
|
|
return false;
|
|
}
|
|
if (physicalSize) {
|
|
*physicalSize = QSize(int(width), int(height));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
[[nodiscard]] QString PreparedTeX(MathKind kind, const QString &trimmedTex) {
|
|
auto result = trimmedTex;
|
|
if (kind == MathKind::Display) {
|
|
result = u"\\displaystyle "_q + result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool EnsureMicrotexInitialized(QString *error) {
|
|
std::call_once(MicrotexInitOnce, [] {
|
|
try {
|
|
#ifdef Q_OS_MAC // Use resources from the .app bundle on macOS.
|
|
|
|
base::RegisterBundledResources(u"external_microtex_bundled.rcc"_q);
|
|
|
|
#else // Q_OS_MAC
|
|
|
|
Q_INIT_RESOURCE(bundled);
|
|
|
|
#endif // Q_OS_MAC
|
|
|
|
tex::LaTeX::initBundled();
|
|
MicrotexInitialized = true;
|
|
} catch (const std::exception &exception) {
|
|
MicrotexInitError = ExceptionText(exception);
|
|
} catch (...) {
|
|
MicrotexInitError = u"unknown-exception"_q;
|
|
}
|
|
});
|
|
if (!MicrotexInitialized && error) {
|
|
*error = MicrotexInitError;
|
|
} else if (MicrotexInitialized && error) {
|
|
error->clear();
|
|
}
|
|
return MicrotexInitialized;
|
|
}
|
|
|
|
MicrotexRenderResult RenderWithMicrotex(const MicrotexRenderRequest &request) {
|
|
auto result = MicrotexRenderResult();
|
|
if (!EnsureMicrotexInitialized(&result.error)) {
|
|
return result;
|
|
}
|
|
if (request.devicePixelRatio <= 0) {
|
|
result.error = u"invalid-device-pixel-ratio"_q;
|
|
return result;
|
|
}
|
|
if (request.textSize <= 0) {
|
|
result.error = u"invalid-text-size"_q;
|
|
return result;
|
|
}
|
|
if (request.renderWidthCap <= 0) {
|
|
result.error = u"invalid-render-width"_q;
|
|
return result;
|
|
}
|
|
if (request.renderHeightCap <= 0) {
|
|
result.error = u"invalid-render-height"_q;
|
|
return result;
|
|
}
|
|
const auto trimmedTex = request.trimmedTex.trimmed();
|
|
if (trimmedTex.isEmpty()) {
|
|
result.error = u"empty-tex"_q;
|
|
return result;
|
|
}
|
|
try {
|
|
const auto ratio = request.devicePixelRatio;
|
|
const auto textSize = int64(request.textSize);
|
|
const auto maxWidth = int64(request.renderWidthCap);
|
|
if (textSize > std::numeric_limits<int>::max()) {
|
|
result.error = u"text-size-overflow"_q;
|
|
return result;
|
|
}
|
|
if (maxWidth > std::numeric_limits<int>::max()) {
|
|
result.error = u"render-width-overflow"_q;
|
|
return result;
|
|
}
|
|
const auto tex = PreparedTeX(request.kind, trimmedTex);
|
|
auto render = std::unique_ptr<tex::TeXRender>(tex::LaTeX::parse(
|
|
ToWide(tex),
|
|
int(maxWidth),
|
|
float(textSize),
|
|
float(textSize) * 0.25f,
|
|
kFormulaForegroundRgba));
|
|
if (!render) {
|
|
result.error = u"parse-returned-null"_q;
|
|
return result;
|
|
}
|
|
const auto logicalSize = QSize(
|
|
render->getWidth(),
|
|
render->getHeight());
|
|
const auto logicalDepth = render->getDepth();
|
|
if (logicalSize.width() <= 0 || logicalSize.height() <= 0) {
|
|
result.error = u"invalid-render-size"_q;
|
|
return result;
|
|
}
|
|
if (logicalSize.width() > request.renderWidthCap
|
|
|| logicalSize.height() > request.renderHeightCap) {
|
|
result.error = u"logical-size-cap-exceeded"_q;
|
|
return result;
|
|
}
|
|
auto physicalSize = QSize();
|
|
if (!ComputePhysicalSize(logicalSize, ratio, &physicalSize)) {
|
|
result.error = u"physical-image-cap-exceeded"_q;
|
|
return result;
|
|
}
|
|
auto image = QImage(
|
|
physicalSize,
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
if (image.isNull()) {
|
|
result.error = u"image-allocation-failed"_q;
|
|
return result;
|
|
}
|
|
image.setDevicePixelRatio(ratio);
|
|
image.fill(Qt::transparent);
|
|
{
|
|
QPainter painter(&image);
|
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
|
painter.setRenderHint(QPainter::TextAntialiasing, true);
|
|
tex::Graphics2D_qt graphics(&painter);
|
|
render->draw(graphics, 0, 0);
|
|
}
|
|
result.image = std::move(image);
|
|
result.logicalSize = logicalSize;
|
|
result.logicalDepth = logicalDepth;
|
|
result.ok = true;
|
|
return result;
|
|
} catch (const std::exception &exception) {
|
|
result.error = ExceptionText(exception);
|
|
return result;
|
|
} catch (...) {
|
|
result.error = u"unknown-exception"_q;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
bool MicrotexBackendLinked() {
|
|
return true;
|
|
}
|
|
|
|
} // namespace Iv::Markdown
|