From 4c69ef39aba9819e7eb3469c783b83a553cf960d Mon Sep 17 00:00:00 2001 From: WHC Date: Sun, 11 Oct 2026 20:35:41 +0800 Subject: [PATCH] pdf: text that names no colour comes out black, not the palette A QTextDocument takes its default text colour from the application palette, and markdown names no colour at all: in a window running a dark palette -- which is what a phone in dark mode hands Qt -- every plain paragraph was laid out #eeeeee on a white page. Printed, that is a blank document; on screen it reads as "the font is oddly faint". The first PDFs off the phone were exactly that. - PdfTools: paperDocument() sets the default stylesheet (body colour #000000) before setHtml, and writePdf() lays that out. A colour the document does name still wins, so an .html handed to the tool keeps its own -- checked with a #cc0000 document under both palettes. - --uitest: draws that document on a white image under a dark palette and looks for ink (238 before the fix, 0 after). Verified on the NX789J: md_to_pdf on the device now writes CID TrueType NotoSansCJKHK-Regular/-Bold text whose page renders at min=0. --- app/src/core/PdfTools.cpp | 34 ++++++++++++++++++++++++++-------- app/src/core/ToolProvider.h | 7 +++++++ app/src/ui/UiTest.cpp | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/app/src/core/PdfTools.cpp b/app/src/core/PdfTools.cpp index 8015417..bf62630 100644 --- a/app/src/core/PdfTools.cpp +++ b/app/src/core/PdfTools.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include "core/AgentCore.h" #include "core/ToolProvider.h" @@ -168,10 +169,9 @@ bool sourceHtml(const cst_tool_ctx_t *ctx, const ctl_tool_call_t *c, * strip. */ bool writePdf(const QString &html, const QString &path, const QString &title, QString *err) { - QTextDocument doc; - doc.setHtml(html); + std::unique_ptr doc(paperDocument(html)); if (!title.isEmpty()) - doc.setMetaInformation(QTextDocument::DocumentTitle, title); + doc->setMetaInformation(QTextDocument::DocumentTitle, title); QPdfWriter writer(path); writer.setPageSize(QPageSize(QPageSize::A4)); @@ -186,17 +186,17 @@ bool writePdf(const QString &html, const QString &path, const QString &title, return false; } const QRectF page = writer.pageLayout().paintRectPixels(writer.resolution()); - doc.setPageSize(page.size()); - const int pages = qMax(1, doc.pageCount()); + doc->setPageSize(page.size()); + const int pages = qMax(1, doc->pageCount()); for (int i = 0; i < pages; i++) { if (i) writer.newPage(); painter.save(); painter.translate(page.topLeft()); painter.translate(0, -i * page.height()); - doc.drawContents(&painter, - QRectF(0, 0, page.width(), page.height()) - .translated(0, i * page.height())); + doc->drawContents(&painter, + QRectF(0, 0, page.width(), page.height()) + .translated(0, i * page.height())); painter.restore(); } painter.end(); @@ -285,6 +285,24 @@ int mdToPdfRun(const cst_tool_ctx_t *ctx, const ctl_tool_call_t *c, } // namespace +/* The document writePdf lays out, with a colour filled in for text that names + * none of its own. + * + * Paper is white and has no theme, but a QTextDocument takes its default text + * colour from the application palette: in a window running a dark palette -- + * which is what a phone in dark mode hands Qt -- every plain markdown + * paragraph came out #eeeeee on a white page, i.e. a PDF that looks blank + * when printed, and reads as "the font is oddly faint" on screen. Naming the + * colour makes the page depend on the paper rather than on the window. A + * colour the document does name still wins (an .html file somebody handed us + * keeps its own), so this only fills the holes. */ +QTextDocument *paperDocument(const QString &html) { + auto *doc = new QTextDocument; + doc->setDefaultStyleSheet(QStringLiteral("body { color: #000000; }")); + doc->setHtml(html); + return doc; +} + int registerQtTools(AgentCore *core) { if (!core) return 0; diff --git a/app/src/core/ToolProvider.h b/app/src/core/ToolProvider.h index e46417e..679cbab 100644 --- a/app/src/core/ToolProvider.h +++ b/app/src/core/ToolProvider.h @@ -19,6 +19,9 @@ /* The engine wrapper of the front end (core/AgentCore.h). Only a pointer * travels through this header, so the class stays incomplete here. */ class AgentCore; +/* Same for the document md_to_pdf lays out (PdfTools.cpp): the caller owns + * it, and the test in ui/UiTest.cpp inspects how it draws. */ +class QTextDocument; namespace app { @@ -48,6 +51,10 @@ int registerPlatformTools(AgentCore *core); * (they are Qt, not a platform API): today md_to_pdf, owned by "app:qt". * Called next to registerPlatformTools(). */ int registerQtTools(AgentCore *core); +/* The document md_to_pdf lays out from `html`: the page's own defaults + * applied (text with no colour of its own is black, not the window's palette + * colour). The caller owns the result. */ +QTextDocument *paperDocument(const QString &html); /* Drop them again (they belong to the owners "app:*"). */ void unregisterPlatformTools(AgentCore *core); diff --git a/app/src/ui/UiTest.cpp b/app/src/ui/UiTest.cpp index 118928d..6223988 100644 --- a/app/src/ui/UiTest.cpp +++ b/app/src/ui/UiTest.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -22,14 +23,17 @@ #include #include #include +#include #include #include #include +#include #include "cst_agent.h" #include "cst_attach.h" +#include "core/ToolProvider.h" #include "ui/SessionImages.h" #include "ui/ThemeIcons.h" @@ -482,6 +486,36 @@ int runUiTest(AgentCore &core, const QString &size) { } } + /* Paper is white whatever the window's palette says: markdown carries no + * colour, so a dark window turned every plain paragraph #eeeeee -- a PDF + * that looked blank. Draw it on a white image and look for ink. */ + { + const QPalette was = QGuiApplication::palette(); + QPalette darkPalette = was; + darkPalette.setColor(QPalette::Text, QColor(0xee, 0xee, 0xee)); + darkPalette.setColor(QPalette::WindowText, QColor(0xee, 0xee, 0xee)); + QGuiApplication::setPalette(darkPalette); + std::unique_ptr page(app::paperDocument( + QStringLiteral("text that names no colour of its own" + ""))); + QImage paper(320, 60, QImage::Format_RGB32); + paper.fill(0xffffffffu); + QPainter ink(&paper); + page->drawContents(&ink); + ink.end(); + int darkest = 255; + for (int y = 0; y < paper.height(); y++) { + const QRgb *row = reinterpret_cast(paper.constScanLine(y)); + for (int x = 0; x < paper.width(); x++) + darkest = qMin(darkest, qGray(row[x])); + } + check(darkest < 64, + QStringLiteral("a dark window still lays out black ink on paper " + "(darkest pixel %1)") + .arg(darkest)); + QGuiApplication::setPalette(was); + } + /* new sessions: the engine moves, the view empties, the store grows */ const int before = core.sessions().size(); clickMouse(win, QStringLiteral("newSessionButton"));