diagrams: render mermaid as PNG instead of SVG

Qt's SVG renderer doesn't support CSS class-based styling that mermaid
generates (<style> blocks with class selectors). This caused text and
connectors to be invisible — they had no fill/stroke applied.

Fix: output PNG from mmdc for mermaid diagrams. Qt renders PNG perfectly.
PlantUML and Draw.io continue using SVG (they use inline styles).
This commit is contained in:
Levi Neely 2026-07-29 16:38:34 +02:00
parent 3b27bccdda
commit a598d0c5cc
1 changed files with 20 additions and 16 deletions

View File

@ -880,16 +880,19 @@ QString ChatBlockModel::diagramUrl(const QString &source, const QString &languag
QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
+ QStringLiteral("/diagrams");
QDir().mkpath(cacheDir);
QString svgPath = cacheDir + "/" + QString::fromLatin1(hash) + ".svg";
// Mermaid uses PNG (Qt's SVG renderer can't handle CSS class-based styles);
// PlantUML and Draw.io use SVG (inline styles, Qt-compatible).
QString ext = (lang == QStringLiteral("mermaid")) ? QStringLiteral(".png") : QStringLiteral(".svg");
QString outPath = cacheDir + "/" + QString::fromLatin1(hash) + ext;
// Return cached result if available
if (QFileInfo::exists(svgPath))
return QUrl::fromLocalFile(svgPath).toString();
if (QFileInfo::exists(outPath))
return QUrl::fromLocalFile(outPath).toString();
if (lang == QStringLiteral("plantuml")) {
// Try local PlantUML server first (localhost:8080)
if (fetchFromPlantUmlServer(source, svgPath))
return QUrl::fromLocalFile(svgPath).toString();
if (fetchFromPlantUmlServer(source, outPath))
return QUrl::fromLocalFile(outPath).toString();
// Fall back to CLI
QTemporaryFile input;
@ -937,16 +940,16 @@ QString ChatBlockModel::diagramUrl(const QString &source, const QString &languag
return QString();
// plantuml names output after input file, rename if needed
if (!QFileInfo::exists(svgPath)) {
if (!QFileInfo::exists(outPath)) {
QString baseName = QFileInfo(input.fileName()).completeBaseName();
QString actualOutput = cacheDir + "/" + baseName + ".svg";
if (QFileInfo::exists(actualOutput))
QFile::rename(actualOutput, svgPath);
QFile::rename(actualOutput, outPath);
}
} else if (lang == QStringLiteral("mermaid")) {
// Mermaid — try local server first (localhost:8081), fall back to mmdc CLI
if (fetchFromMermaidServer(source, svgPath))
return QUrl::fromLocalFile(svgPath).toString();
// Note: server returns SVG which Qt can't render (CSS classes), so skip it
// and always use CLI with PNG output for Qt compatibility.
QTemporaryFile input;
input.setFileTemplate(QStringLiteral("/tmp/ollie-diagram-XXXXXX.mmd"));
@ -965,7 +968,7 @@ QString ChatBlockModel::diagramUrl(const QString &source, const QString &languag
proc.setProcessChannelMode(QProcess::MergedChannels);
proc.start(mmdc, {
QStringLiteral("-i"), input.fileName(),
QStringLiteral("-o"), svgPath,
QStringLiteral("-o"), outPath,
QStringLiteral("-t"), QStringLiteral("default"),
QStringLiteral("-b"), QStringLiteral("white")
});
@ -998,7 +1001,7 @@ QString ChatBlockModel::diagramUrl(const QString &source, const QString &languag
if (reply->isFinished() && reply->error() == QNetworkReply::NoError) {
QByteArray svg = reply->readAll();
if (!svg.isEmpty() && svg.contains("<svg")) {
QFile out(svgPath);
QFile out(outPath);
if (out.open(QIODevice::WriteOnly)) {
out.write(svg);
serverOk = true;
@ -1026,7 +1029,7 @@ QString ChatBlockModel::diagramUrl(const QString &source, const QString &languag
proc.start(drawio, {
QStringLiteral("--export"),
QStringLiteral("--format"), QStringLiteral("svg"),
QStringLiteral("--output"), svgPath,
QStringLiteral("--output"), outPath,
input.fileName()
});
@ -1035,10 +1038,10 @@ QString ChatBlockModel::diagramUrl(const QString &source, const QString &languag
}
}
if (!QFileInfo::exists(svgPath))
if (!QFileInfo::exists(outPath))
return QString();
return QUrl::fromLocalFile(svgPath).toString();
return QUrl::fromLocalFile(outPath).toString();
}
bool ChatBlockModel::invalidateDiagram(const QString &source, const QString &language) const
@ -1053,7 +1056,8 @@ bool ChatBlockModel::invalidateDiagram(const QString &source, const QString &lan
(lang + "\x1f" + source).toUtf8(), QCryptographicHash::Sha256).toHex().left(16);
QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
+ QStringLiteral("/diagrams");
QString svgPath = cacheDir + "/" + QString::fromLatin1(hash) + ".svg";
QString ext = (lang == QStringLiteral("mermaid")) ? QStringLiteral(".png") : QStringLiteral(".svg");
QString outPath = cacheDir + "/" + QString::fromLatin1(hash) + ext;
return QFile::remove(svgPath);
return QFile::remove(outPath);
}