Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Qt text size in points

Tags:

I'm trying to print an invoice document on A4 in millimetres instead of the default device units. Except that when changing the units to millimetres the pointsize of text on the printed document no longer matches up with the pointsize in for instance Word or Adobe Illustrator. I tried converting the point size to the corresponding pixel size, but they had issues.

QFont::SetPixelSize only takes an int so if the calculations falls below 1 it will trunctuate to 0

font.setPixelSize((9.0 * 72.0) / printer.resolution());

And the other method made the text the correct vertical size, but there are some artefacts:

int phys_w = printer.width();
font.setPointSizeF((9.0 / phys_w) * 210.0);

enter image description here
Where you can see the unusually large gaps between some characters. (Perhaps there is some of precision issue inside Qt its text drawing code?)


Here is a minimal example showing the issues:

QPrinter printer(QPrinter::HighResolution);
printer.setPageSize(QPrinter::A4);
printer.setOrientation(QPrinter::Portrait);
printer.setFullPage(true);
printer.setPageMargins(QMarginsF(0, 0, 0, 0));

printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName("invoice.pdf");

QPainter painter(&printer);

auto page_size = printer.pageRect(QPrinter::Unit::Millimeter);
painter.setWindow(page_size.toRect());

QFont font = painter.font();

// Either this
font.setPixelSize((9.0 * 72.0) / printer.resolution());

// or this
int phys_w = printer.width();
font.setPointSizeF((9.0 / phys_w) * 210.0);

painter.setFont(font);
painter.drawText(35, 46, "John Doe");

How can I have the positioning in Millimetres (or any arbitrary unit) and have the text size be correct in points (or some correct recalculation)?

This is on Qt 5.10.0 and Windows 10.

EDIT In the end I opted to go for a 10x scale increase (so tenths of a millimetre) which fixed the kerning issues visible for setPointSizeF. Now the last issue I'm having with the scale is that of setting the width of a line and other shapes (QPen::setWidth) and I cant find a calculation so it's in millimetres.

EDIT In the end the linewidth didn't need any recalculations. The final code is below:

QPrinter printer(QPrinter::HighResolution);
printer.setPageSize(QPrinter::A4);
printer.setOrientation(QPrinter::Portrait);
printer.setFullPage(true);
printer.setPageMargins(QMarginsF(0, 0, 0, 0));

printer.setOutputFormat(QPrinter::NativeFormat);

QPainter painter(&printer);
painter.setWindow(0, 0, 2100, 2970);
painter.setViewport(0, 0, printer.width(), printer.height());

QFont font(fontFamily, 0, weight, italic);
font.setPointSizeF(static_cast<float>(pixelSize) / printer.width() * 2100);
like image 217
Eejin Avatar asked Apr 27 '18 17:04

Eejin


2 Answers

I think you're dividing where you should multiply and vice versa. Take a look with the units written out explicitly:

9 points * (1 inch / 72 points) * (printer.res pixels/inch)

multiplying the units, the numerator gets (points * inch * pixels) , denominator gets (points * inch) . Cancel out the like units and you get pixels in the numerator. So the calculation should be:

font.setPixelSize(9.0 / 72.0 * printer.resolution());

For your second problem,

QPen::setWidthF(w*printer.resolution()/25.4);

where w is your desired width in mm.

like image 78
Chris H Avatar answered Oct 11 '22 10:10

Chris H


By making detailed observations (printing a long sentence) it's noticed that these gaps between characters are directly related to characters themselves, wide characters and Capital letters eat up the gap (J, s , r, c) while narrow characters leave more gap (i , l , t) .. this is just to say its not a random behavior.

In general this is known as kerning nicely explained here. To minimize this, Qt sets QFont kerning to true (default) QFont Kerning ... but the problem is that kerning is much dependent on the used font and pixel, and Qt kerning enabled sometimes has not effect as in this post, probably because setting Kerning to true

(Will apply kerning between adjacent glyphs. Note that OpenType GPOS based kerning is currently not supported QRawFont::LayoutFlags

Which means that some font Ascent / Descent will still cause limitation how the gap is controlled. Two solutions are considered below, the first is with still sticking to default painter font, yet you can stretch the characters to enforce reasonable gab:

font.setStretch(70); // value is experimental 

enter image description here

I don't think this is a robust solution, unless one is limited to use specific font, while font itself is not important for invoice printing, the second attractive approach is to find out best font that meets requirements: render well with set resolution / little loss when drawn to PDF (from Qt) / and most important efficient for kerning! "MS Gothic" (ttf) for example performs well for such setup, here how it performs (without stretch)

painter.setFont(QFont("MS Gothic"));
QFont font = painter.font();
int phys_w = printer.width();
font.setPointSizeF((9.0 / phys_w) * 210.0);
//font.setStretch(70);
painter.setFont(font);

enter image description here

The good thing about selecting a suitable font is that more control is possible (especially for invoice printing where small space is valuable).

For example the same can printed with less space by reducing the gap between letters:

font.setLetterSpacing(QFont::PercentageSpacing,65); // 65% gap of default

enter image description here

And one can go even for lower font size without loosing visual clearance.


sharing below some work while trying to clarify how printing small font size is affecting gap between letters. the idea is to draw each character separately inside QRect and at the same time draw equivalent QRectF on same area.

That's to see when font size is large QRectF are drawn fit next to each other, so also each character ... while when font size goes low the adjacent QRectFs will severely overlap and gaps between characters start to get disordered.

QPen pen=painter.pen();
pen.setWidth(0.1);
painter.setPen(pen);
QString td("John Doe");
auto spacer = font.pointSizeF(); // font size used to set width of QRect of each character.
spacer *=30.0; // large Rect width.
auto b = 35.0;

for (int i=0; i < td.length() ;i++ )
{
    QRectF rectf(b+=spacer,47.0,4.0,4.0);
    QRect rect(b, 47.0,4.0,4.0);
    QString ch = td.at(i);
    //painter.drawText(b+=spacer,46,ch);
    painter.drawText(rect,Qt::AlignCenter,ch);
    painter.drawRect(rectf);
}
painter.end();

Result for large font size:

enter image description here

Next make QRectF overlap:

spacer *=10.0;

Result, letters get less gaps and wide adjacent characters get narrow gap.

enter image description here

like image 31
Mohammad Kanan Avatar answered Oct 11 '22 11:10

Mohammad Kanan