[solved] how to paint QTextDocument on QImage with correct DPI
-
I'm trying to use QTextDocument to render rich text onto a QImage, which will then be used for printing (stupid non-free binary-only CD Label printer driver requires me to provide an image - can't use QPrinter).
I'm stuck now, because QTextDocument fails to render at the correct font size. I.e. the code looks like this:
@
QImage img = m_baseImage;
img.setDotsPerMeterX(qRound(img.width() / 0.118)); // the CD label has a diameter of 118mm
img.setDotsPerMeterY(qRound(img.height() / 0.118));
QPainter p(&img);
// p.translate to the right position
m_document->drawContents(&p);
// compare with QPainter::drawText:
p.setFont(m_document->defaultFont());
p.drawText(0, 0, QLatin1String("test text"));
p.end();
img.save("/tmp/check.png", "PNG");
@
The result is that QTextDocument renders at the DPI of the screen, while QPainter::drawText renders the font at the expected 600x600 dpi (which is the value img.logicalDpiX() and img.physicalDpiX() (and Y) return).I can add a QPainter::scale call that would lead to the correct magnification of the font, but it results in incorrect font rendering. This is most visible for small font sizes and serifs: they are rendered in a way to accommodate for the low number of pixels on screen, and this looks very ugly in print.
Is this a bug in QTextDocument or am I missing something?
-
Does anybody have experience with QTextDocument? I'm stuck and could use some other opinion, please.
-
I finally found a way to fix it now:
QTextDocument uses a different QPaintDevice to do the layouting than it uses to do the rendering. I.e. you need to access QTextDocument::documentLayout() and call setPaintDevice(QPaintDevice *) on it. This will lead to correct font size but incorrect placement. So you still need to call some QTextDocument function that will lead to a relayout, for example setTextWidth. So the final code that works goes like this:
@
QImage img = m_baseImage;
img.setDotsPerMeterX(qRound(img.width() / 0.118)); // the CD label has a diameter of 118mm
img.setDotsPerMeterY(qRound(img.height() / 0.118));
QTextDocument *doc = m_document->clone();
doc->documentLayout()->setPaintDevice(&img);
doc->setTextWidth(img.width());
QPainter p(&img);
// p.translate to the right position
doc->drawContents(&p);
p.end();
delete doc;
img.save("/tmp/check.png", "PNG");
@I consider this behavior unintuitive if not incorrect.
-
you could too change the scale of the painter QPaint::scale(), but like you are saying it's impossible to avoid the relayouting of the text (the text width changes! For trick if you call the function QTextDocument::size() it's make too a relayout of the text)
-
[quote author="BilbonSacquet" date="1321861434"]you could too change the scale of the painter QPaint::scale()[/quote]
[quote author="mkretz" date="1319557277"]
I can add a QPainter::scale call that would lead to the correct magnification of the font, but it results in incorrect font rendering. This is most visible for small font sizes and serifs: they are rendered in a way to accommodate for the low number of pixels on screen, and this looks very ugly in print.[/quote]So please, don't use QPainter::scale with font rendering. It can lead to ugly looking fonts!
-
[quote author="BilbonSacquet" date="1321861434"]like you are saying it's impossible to avoid the relayouting of the text (the text width changes! For trick if you call the function QTextDocument::size() it's make too a relayout of the text)[/quote]
Actually when you only use QPainter::scale there's no need to relayout the QTextDocument (unless, of course the text width on screen and in the image must be different). In my case I simply didn't define a text width/page size. So scale didn't need a relayout.
Also, I added a QTextDocument::size() call to my solution (so without the setTextWidth call) and it did not change the wrong layout.
-
thank you mkretz for this post.
You saved my day!!