WYSIWYG with Qt - font size woes - c++

I am creating a custom Qt widget that mimics an A4 printed page and am having problems getting fonts to render at the correct size. My widget uses QPainter::setViewport and QPainter::setWindow to mimic the A4 page, using units of 10ths of a millimetre which enables me to draw easily. However, attempting to create a font at a specific point size doesn't seem to work and using QFont:setPixelSize isn't accurate. Here is some code:
View::View(QWidget *parent) :
QWidget(parent),
printer(new QPrinter)
{
printer->setPaperSize(QPrinter::A4);
printer->setFullPage(true);
}
void View::paintEvent(QPaintEvent*)
{
QPainter painter(this);
painter.setWindow(0, 0, 2100, 2970);
painter.setViewport(0, 0, printer->width(), printer->height());
// Draw a rect at x = 1cm, y = 1cm, 6cm wide and 1 inch high
painter.drawRect(100, 100, 600, 254);
// Create a 72pt (1 inch) high font
QFont font("Arial");
font.setPixelSize(254);
painter.setFont(font);
// Draw in the same box
// The font is too large
painter.drawText(QRect(100, 100, 600, 254), tr("Wg\u0102"));
// Ack - the actual font size reported by the metrics is 283 pixels!
const QFontMetrics fontMetrics = painter.fontMetrics();
qDebug() << "Font height = " << fontMetrics.height();
}
So I'm asking for a 254 high font (1 inch, 72 pts) and it's too big and sure enough when I query for the font height via QFontMetrics it is 283 high.
Does anyone else know how to use font sizes in points when using custom mapping modes like this? It must be possible. Note that I cannot see how to convert between logical/device points either (i.e. the Win32 DPtoLP/LPtoDP equivalents.)
EDIT: Well, it turns out that my code was working fine after all. I converted it to work with a printer, printed it out and then printed the same text using various word processors and the results are exactly the same. It seems that asking for a font size doesn't take the descent into account and this seems to be the norm.

QFont may or may not be able to match the exact font that you request. QFontMetrics::height() returns a size of 284 on my system, but QFontInfo::pixelSize() returns a size of 254 which is what was requested. I assume the difference is that height() includes the descent where pixelSize() returns the size in pixels of the matched font, implying that I had a match.
As for drawing, placement, and conversions, you'll need to be careful because the printer device won't be the same as the screen device nor will the resolutions match. To further exacerbate the problem, you can't get the exact printer metrics without calling setup on the print dialog. Device independence gets you close to WYSIWYG, but not always close enough.
In your sample, the paint device is this. Thus, you can get at the logical dpi through the logicalDpiX() and logicalDpiY() functions (as well as their physical equivalents).

Related

why does wxDC::GetTextExtent() return the same value for a high DPI display in wxWidgets?

I am trying to scale my application written in c++ with wxWidgets for high DPI displays. I am following the guidelines in the official link. Everythings work fine so far except the return value of wxDC::GetTextExtent() function. When I move my window to a monitor with a different DPI, the font size scales but the return value of the wxDC remains the same as before. However if I use wxWindow::GetTextExtent(), it returns the correct value! In the document, it says all wxWidgets API use logical pixels but it does not seem so.
In other words, if you try to draw "text" on a device context (dc) in a high DPI display, the drawn text would be small cause the measured value of the font height by wxDC is small ( e.g does not scale ). However all other text drawn by wxWindow scales correctly.
Is this behavior intentional? what should I do to have a correct value? I am using wxWidgets 3.1.5 and Win 10.
Also it is not clear whether wxWidgets use Device Independent Pixels (DIP) or logical Pixels?
wxDC::GetTextExtent() and wxWindow::GetTextExtent() should return the same value and if I insert this code in the minimal sample:
void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
{
wxClientDC dc(this);
wxLogMessage("wxDC: %d, wxWindow: %d",
dc.GetTextExtent("Hello").x,
GetTextExtent("Hello").x);
}
it displays "wxDC: 28, wxWindow: 28" with default DPI screen and "wxDC: 56, wxWindow: 56" when using 200% DPI scaling with wxWidgets 3.1.6, so you probably just need to update to this release.

Qt MainWindow with QOpenGLWIdget in Retina display displays wrong size

I have a Qt application with a MainWindow.
I embed a QOpenGLWidget in it. Everything works fine until I start using an Apple Retina Display and run my app in High DPI mode: my QOpenGLWidget is just 1/4 of the size it was supposed to have (i.e., it only fills the bottom-left part of the area it is supposed to fill). This widget is displaying raw OpenGL data (actually, an OpenSceneGraph context)
What can I do to solve this?
Found out that the best option for now, for OpenGL related widgets and events, is to use the QPaintDevice::devicePixelRatio() (http://doc.qt.io/qt-5/qpaintdevice.html#devicePixelRatio)
This implies multiplying everything that uses pixel coordinates, i.e., mouse events, resizing events, and so on. Example:
void MyGLWidget::resizeGL(int width, int height) {
width *= Application::desktop()->devicePixelRatio();
height *= Application::desktop()->devicePixelRatio();
...
// Continue with previous code
}
When running in low resolution mode in a Retina/HighDPI display, or when running in a regular display, this ratio is 1, so this seems portable to me.
From Qt docs (section OsX),
Note: The scaling is not applied to Open GL windows
I didn't try this approach on Mac, but it helped with the same issue on my Windows machine. I'm not sure if it's the best solution, though, there could be easier. Try and see if it works.
The main idea is to scale up your OpenGL content sizes manually.
First, Define the amount of scale to be performed. You can use the characteristic of physical dot per inch:
QApplication app(argc, argv);
int x = QApplication::desktop()->physicalDpiX();
int y = QApplication::desktop()->physicalDpiY();
// values 284 and 285 are the examples of reference values that we determined when DPI scaling was disabled
double scaleX = 284.0/double(x);
double scaleY = 285.0/double(y);
physicalDpi* makes possible to judge how many pixels we have for an inch. In order to define the scale, detect how much is reference value of density and then scale proportionally against the density of the physical device (next step).
Second, you have to use the scaleX and scaleY inside your QOpenGLWidget and make sure we manually scale:
sizes such as QOpenGLWidget::width() and QOpenGLWidget::height() will turn into this->width()*m_scaleX and this->height()*m_scaleY
mouse events coordinates, e.g., event->x()*m_scaleX and event->y()*m_scaleY

Changing DPI scaling size of display make Qt application's font size get rendered bigger

I have created some GUI application using Qt. My GUI application contains controls like push button and radio button. When I run the application, buttons and fonts inside button looks normal. When I change the DPI scaling size of display from 100% to 150% or 200%, font size of controls rendered bigger but not control size (pushbutton, radio button) irrespective of resolution. Due to this the text inside controls were cut off. please see the attached image.
Qt application look when DPI scaling size set to 100%
Qt application look when DPI scaling size set to 200%
I am running my application in some tablets also. In tablets, DPI scale value should be more than 150% else everything will be shown very small.
I searched in the web for creating UI application in Qt irrespective of resolution and DPI scale value but no luck. So I am posting my questing here. Please let me know if there is some way to get rid of this.
High DPI support is enabled from Qt 5.6 onward.
Setting QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling) in your application source code allows automatic high-DPI scaling.
NOTICE: To use the attribute method, you must set the attribute before you create your QApplication object:
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
return app.exec();
}
Using layouts correctly can help.
http://qt-project.org/doc/qt-4.8/layout.html
Telling the OS that you handle DPI changes, will prevent weird font changes that you weren't expecting.
http://msdn.microsoft.com/en-us/library/ms701681(v=vs.85).aspx
For spacing critical places, you can check the size of your rendered font, and then set the minimum size of your object based on the resulting size of your text.
http://qt-project.org/doc/qt-4.8/qfontmetrics.html#details
https://blog.qt.digia.com/blog/2009/06/26/improving-support-for-higher-dpi-on-vista/
You could try checking with other built in measurements from Qt:
http://qt-project.org/doc/qt-4.8/qpaintdevice.html#widthMM
http://qt-project.org/doc/qt-4.8/qpaintdevice.html#logicalDpiX
If you are using QML, try for pristine layouts of only anchor based placement.
http://qt-project.org/doc/qt-4.8/qml-anchor-layout.html
QApplication has some settings that are somewhat related.
http://qt-project.org/doc/qt-4.8/qapplication.html#setDesktopSettingsAware
You could manually specify the font, too.
http://qt-project.org/doc/qt-4.8/qapplication.html#setFont
Hope that helps.
I had a fixed size window which was not large enough to fit all the text it contained when Windows accessibility settings where applied to scale up all text sizes. Windows does this via dpi increases. I fixed this by retreiving the os scaling factor and then adjusted the size of the my window and some of it's layouts (which I couldn't get to scale automatically for some reason).
Here's how I got the dpi scale (in a file called "WindowsDpiScale.h"):
#ifndef WINDOWSDPISCALE_H
#define WINDOWSDPISCALE_H
#include <QtGlobal>
#ifdef Q_OS_WIN
#include <windows.h>
const float DEFAULT_DPI = 96.0;
float windowsDpiScale()
{
HDC screen = GetDC( 0 );
FLOAT dpiX = static_cast<FLOAT>( GetDeviceCaps( screen, LOGPIXELSX ) );
ReleaseDC( 0, screen );
return dpiX / DEFAULT_DPI;
}
#endif //Q_OS_WIN
#endif // WINDOWSDPISCALE_H
And then, how I applied it in my case:
...
#include "WindowsDpiScale.h"
MainWindow::MainWindow( QWidget *parent )
: QMainWindow( parent )
{
...
// Enlarge the window and various child widgets to accomendate
// OS display scaling (i.e. accessibily options)
setScaleToOsSettings();
...
}
void MainWindow::setScaleToOsSettings()
{
#ifdef Q_OS_WIN
setScale( windowsDpiScale() );
#endif
}
void MainWindow::setScale( float scale )
{
// Resize the window
this->setFixedSize( (int)(scale * this->maximumWidth()),
(int)(scale * this->maximumHeight()) );
// Resize the layouts within the stacked widget
foreach( QVBoxLayout * layout,
windowUi_->pagerStackedWidget->findChildren<QVBoxLayout *>() )
layout->parentWidget()->setFixedSize(
(int)(scale * layout->parentWidget()->contentsRect().width()),
(int)(scale * layout->parentWidget()->contentsRect().height()) );
}
Here is a workaround:
Create a file qt.conf and add these lines to it.
[Platforms]
WindowsArguments = dpiawareness=0
Put this file in the application binary folder. That's all. It will work well but only issue is that the look will not be that crisp.
For delivering it to the customer, add this file where you have your dependency files & while creating setup, like normally you run your dependency files, run this same as well.
There are several options when dealing with high-resolution displays:
Do nothing and develop in high resolution. Eventually, a higher resolution becomes a new standard, everybody will get 13-15-17 inches. It did work in the 2000th, didn't it?
Leave it to the OS. For example, Windows has a special compatibility setting which scales everything to correct size, while keeping the application think that it renders of a low-resolution display. This is called DPI Unaware on Windows.
Try to use Qt capabilities, QT_AUTO_SCREEN_SCALE_FACTOR=0, QT_SCALE_FACTOR=1.
Use a little bit of help from Qt by setting QT_AUTO_SCREEN_SCALE_FACTOR to 1 or a correspondent AA_EnableHighDpiScaling attribute (introduced in Qt 5.6). This will scale widget sizes relative to the font size, so you only need to deal with raster images.
Turn off AA_EnableHighDpiScaling and rethink all your pixel sizes by making them relative to the font size or multiplying them over the device pixel ratio.
An improvement over a previous step: take into an account the device pixel ratio on every display, so that UI is scaled appropriately when you move it to another display.
Sources:
https://doc.qt.io/qt-5/scalability.html
https://doc.qt.io/qt-5/highdpi.html
https://learn.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
There are many ways.
one of them is to have reference (height, width and DPI) and when you run your application on a different setup all you need is to get height, width and the logical dots per inch
you can use qreal dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch();
and then you calculate the new font size by multiplying it with the calculated ratio.
qreal refDpi = 216.;
qreal refHeight = 1776.;
qreal refWidth = 1080.;
QRect rect = QGuiApplication::primaryScreen()->geometry();
qreal height = qMax(rect.width(), rect.height());
qreal width = qMin(rect.width(), rect.height());
qreal dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch();
m_ratio = qMin(height/refHeight, width/refWidth);
m_ratioFont = qMin(height*refDpi/(dpi*refHeight), width*refDpi/(dpi*refWidth));
the second way, which is extremely simple, more productive and personally I use it, is simply by using stylesheets!!, and the font size won't change by your DPI scaling.
you can use font property like that for example
font: bold italic large "Times New Roman";

Resizing a CStatic control dynamically to fit text

As of now, I'm using the following to code to resize my CStatic controls:
WINDOWPLACEMENT wndpl;
m_myStaticControl.GetWindowPlacement(&wndpl);
// Increase the static box's width
wndpl.rcNormalPosition.right += 10;
m_myStaticControl.SetWindowPlacement(&wndpl);
m_myStaticControl.SetWindowText("Some text");
I obtain the constant (in the above case, 10) by trial-and-error. Because this seems like a really inelegant and hard-to-maintain solution, I want to change this. After some research, I think I have a basic idea; which is:
Get the pixel width and height of the required text using GetTextExtentPoint32.
Get the current window placement of the CStatic control as in code example above.
If the current width < obtained pixel width, add obtained pixel width. Do the same for height.
Set the window placement as in code example above.
Set the window text as in code example above.
Would this be a good, efficient approach? Also, does GetTextExtentPoint32 use pixels or dialog units?

Printed CDC appears tiny on paper

When I print the CDC for a report control that I've created it appears tiny (less than 1 square inch on paper). How can I get the report to be printed to occupy the entire page ?
Or in other words, how can I make the entire report to appear in one printed page.
CPrintDialog printDialog(FALSE);
printDialog.DoModal();
CDC dcPrint;
if(dcPrint.Attach(printDialog.GetPrinterDC()))
{
int iHorzRes = dcPrint.GetDeviceCaps(HORZRES);
int iVertRes = dcPrint.GetDeviceCaps(VERTRES);
int iHorzResCDC = m_CDC.GetDeviceCaps(HORZRES);
int iVertResCDC = m_CDC.GetDeviceCaps(VERTRES);
dcPrint.m_bPrinting = TRUE;
dcPrint.BitBlt(0,0, iHorzRes, iVertRes, &m_CDC, iHorzResCDC, iVertResCDC, SRCCOPY);
CFont* pOldFont = dcPrint.SelectObject(&m_HeaderFont);
dcPrint.TextOut(0,0,"HelloWorld") ;
dcPrint.SelectObject(pOldFont);
CPrintInfo printInfo;
printInfo.m_rectDraw.SetRect(0,0, iHorzRes, iVertRes);
dcPrint.StartDoc("Report Print");
dcPrint.StartPage();
if(dcPrint.EndPage())
dcPrint.EndDoc();
else
dcPrint.AbortDoc();
}
dcPrint.DeleteDC();
m_CDC is the memory DC that I use to buffer and display the entire report on screen.
As others have said, this is because, in general, the display resolution of printers is a lot higher than displays. Displays are usually 96 to 120DPI: at 96DPI this means that an image of 96 pixels (dots) by 96 pixels occupies approximately 1 square inch on the display. However, if you just take that image and print it out on a 600DPI printer, the size of the image will be about 1/6" by 1/6" - much smaller. This is a bane of the publishing world - images that look fine on displays often look either tiny or terrible when printed.
You could, as has been suggested, use StretchBlt rather than BitBlt to scale up your image. Depending on the difference between your display and printer, this will either look a bit blocky, or utterly hideously blocky.
A much better option is to rewrite your code that does the drawing of the control so that you've got a method that takes a device context (and some co-ordinates) and draws into it. Your normal window painting code can pass the memory DC to this routine and then BitBlt the result to the window, and your painting code can call this method with the printer DC and some suitable co-ordinates.
When writing this routine you'll have to worry about scaling: for example, you'll need to create fonts for the given device context, and with a scaling-indepdendant size (that is, specify the font size in points, not pixels), rather than relying on a pre-created font.
I suppose that you're not scaling your report to the printer's resolution. Typical screen resolution is 72 DPI (sometimes 96 DPI). Printer resolution can be 300DPI, 600DPI or higher.
You should repaint the report to the printer DC with all coordinates and sizes scaled to the printer's resolution.
Your printer has a lot more dots per inch than your screen. You will need to scale things to fit the printed page a bit better.
Try using StretchBlt() instead of BitBlt().