Text is not appearing at specified position in Qt - c++

I have a QGraphicsView which contains many QGraphicsItem like rectangle, polylines etc.
I want to name each rectangle and name is on the rectangle.
Name should be centrally aligned i.e. half of the string name characters should be left side of mid position and half of the characters should be right side of mid position.
For that I used following psuedo code:
int firstPosition = GetMidPosition () - (strLength / 2);
int secondPosition = some points
DrawText( firstPosition , secondPosition );
Now if I print co-ordinates of firstPosition, secondPosition , GetMidPosition() they comes perfectly as I expect.
But, the text does not come properly. I have to manually adjust it.
int firstPosition = GetMidPosition () - 6 *(strLength / 2);
Now it appears centrally. Why this is happening ? Co-ordinates are correct then why I have to manually adjust it.
I want to avoid that adjustment (i.e. 6 * )
If tomorrow, I changed the font size of text, then I will have to
change the multiplication factor 6 into something else. That should
not be happened. How to write general purpose logic for this ?

Related

Qt text size in points

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);
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);
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.
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
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);
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
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:
Next make QRectF overlap:
spacer *=10.0;
Result, letters get less gaps and wide adjacent characters get narrow gap.

JAGPDF in C++ aligning test

Has anyone used jagPDF on c++?
I am trying to learn how to use it from the handbook they provide at http://www.jagpdf.org/doc/index.htm. Unfortunately, all their examples are in Python. Which sometimes is not a big deal to adapt to C++, but sometimes it's confusing.
For example, I am trying to learn how to align text, which in the tutorial is at Text Aligment. Once I find the padding, the function that puts the padding in the line of text is:
canvas.text(txt, [padding / 2.0], [0])
looking in the reference for that function, the translation table is:
[py] text(txt_u, offsets, positions)
[c++] void text(Char const* txt_u, Double const* offsets, UInt offsets_length, Int const* positions, UInt positions_length);
Parameters:
txt_u: zero terminated string.
offsets: glyph offsets expressed in glyph space (i.e. in thousandths of a unit of text space).
offsets_length: number of offsets.
position: associates glyph offsets with glyph indices in txt_u.
positions_length: number of positions.
I have tried several things, but I haven't figure out the two additional input parameters that c++ requires over Python. If someone out there has used jagPDF and knows how to do it in c++, I'd be greatly appreciative.
This is my first touch to JagPDF. There is section Text Alignment, but code is written on Python.
In C++ it should be like:
// line width
pdf::Double line_width = 597.6;
// text to show
pdf::Char *txt = "Everything should be made as simple as possible, but no simpler.";
// calculate text width
pdf::Double text_width = font.advance(txt);
// calculate gap - difference in line_width and text_width
pdf::Double gap = line_width - text_width;
// calculate the text padding
pdf::Double padding = -1000.0 / font.size() * gap;
// create array with paddings - I set all padings, but is used only 1
pdf::Double const pp[] = { padding, padding / 2.0 }; // index 0 for right alignment, 1 for center
// not sure what are positions, but need for function `text()` - set as Doc to zero (0)
pdf::Int const dd[] = { 0};
// get canvas where the text is written
pdf::Canvas canvas = doc.page().canvas();
// start text block
canvas.text_start(0, 480);
// set font
canvas.text_font(font);
// right alignment text
canvas.text(txt, pp, 1, dd, 1);
canvas.text_translate_line(0, font.height());
// center alignment text
canvas.text(txt, &pp[1], 1, dd, 1);
canvas.text_translate_line(0, font.height());
// finish text block
canvas.text_end();
Hope it helps

About class CRect & Rect, Width = right - left

It is a problem about C++ and mfc.
For example, left = 3, right = 8. Doesn't it mean there are 6 pixel from left to right? Why the width = right - left? If I know a rect which represents the image rect, when I allocate memory for the image data, which one should I use? Width = right-left, or Width = right-left+1? I am a beginner of image process. It really confuses me. Thank you for your help!
If we are talking about CRect and RECT the documentation is clear.
By convention, the right and bottom edges of the rectangle are normally considered exclusive. In other words, the pixel whose coordinates are ( right, bottom ) lies immediately outside of the rectangle. For example, when RECT is passed to the FillRect function, the rectangle is filled up to, but not including, the right column and bottom row of pixels. This structure is identical to the RECTL structure.
The principles of "inclusive lower bound, exclusive upper bound" is used here to. So the number of elements is always the difference between the boundaries.
Another way to think about this is that the width of the rectangle is a measure of DISTANCE from left to right. When left equals right (e.g.: left = 1 and right = 1), the distance between them is zero (note that the distance can be negative).
When using a RECT to represent pixel coordinates, we often want to know the count of pixels going from left to right. When left equals right (e.g.: left = 1 and right = 1), we know we have only one pixel in the left/right direction. There isn't a pre-made function to compute this count, so you need take the absolute value of the width and add 1.
In C/C++:
int count = abs(myRect.right - myRect.left) + 1;

Direct2D CreateTextLayout() - How to get caret coordinates

I am rendering Text using Direct2D starting with a text Layout
HRESULT hr = m_spWriteFactory->CreateTextLayout(
m_wsText.c_str( ),
m_wsText.length( ),
m_spWriteTextFormat.Get( ),
m_rect.right - m_rect.left - m_spacing.right - m_spacing.left,
m_rect.bottom - m_rect.top - m_spacing.top - m_spacing.bottom,
&m_spTextLayout
);
and then rendering it to a bitmap which I later use with Direct3D
m_sp2DDeviceContext->DrawTextLayout(
D2D1::Point2F( m_spacing.left, m_spacing.top ),
m_spTextLayout.Get( ),
m_spTextBrush.Get( )
);
I would like to draw a simple thin flashing line as a caret. I know how to draw a line and how to make it appear / disappear.
Question: How do I get the starting point and the end point coordinates for my caret line?
Simplification: If it is much easier to assume that the text consists of one line only, then that's ok. But of course a more general solution is appreciated.
Use IDWriteTextLayout's hit-testing functions to determine these:
HitTestTextPosition for mapping a text position index (relative to the first character) to a rectangle.
HitTestTextRange for getting a whole range of rectangles such as for selection.
HitTestPoint for mapping a mouse coordinate to a text position index.
For carets, this below works for all horizontal reading directions and proportional/monospace fonts:
...
DWRITE_HIT_TEST_METRICS hitTestMetrics;
float caretX, caretY;
bool isTrailingHit = false; // Use the leading character edge for simplicity here.
// Map text position index to caret coordinate and hit-test rectangle.
textLayout->HitTestTextPosition(
textPosition,
isTrailingHit,
OUT &caretX,
OUT &caretY,
OUT &hitTestMetrics
);
// Respect user settings.
DWORD caretWidth = 1;
SystemParametersInfo(SPI_GETCARETWIDTH, 0, OUT &caretWidth, 0);
DWORD halfCaretWidth = caretWidth / 2u;
// Draw a thin rectangle.
D2D1::RectF caretRect = {
layoutOriginX + caretX - halfCaretWidth,
layoutOriginY + hitTestMetrics.top,
layoutOriginX + caretX + (caretWidth - halfCaretWidth),
layoutOriginY + hitTestMetrics.top + hitTestMetrics.height
};
solidColorBrush->SetColor(D2D1::ColorF::AliceBlue);
d2dRenderTarget->FillRectangle(&caretRect, solidColorBrush);
Notes:
The above code as-is doesn't account for vertical reading directions such as in Japanese newspapers. You would need to draw a wide flat caret instead of the tall thin one here when the DWRITE_READING_DIRECTION was either top-to-bottom or bottom-to-top.
IDWriteTextLayout::GetMetrics only gives the overall bounding box to you, not the caret position.
IDWriteTextLayout::HitTestPoint's isInside flag is true if it is over the text itself, not just the layout bounds.
You can get the layout's bounding rectangle via IDWriteTextLayout::GetMetrics.
DWRITE_TEXT_METRICS textMetrics;
textLayout->GetMetrics(&textMetrics);
Your rectangle is
D2D1::RectF( textMetrics.left,
textMetrics.top,
textMetrics.left + textMetrics.width,
textMetrics.top + textMetrics.height );
You can then draw the caret along the right boundary line.

Uneven Circles in Connect 4 Board

I'm in the process of creating a 2P Connect 4 game, but I can't seem to get the circular areas to place tokens spaced evenly.
Here's the code that initializes the positions of each circle:
POINT tilePos;
for (int i = 0; i < Board::Dims::MAXX; ++i)
{
tileXY.push_back (std::vector<POINT> (Board::Dims::MAXY)); //add column
for (int j = 0; j < Board::Dims::MAXY; ++j)
{
tilePos.x = boardPixelDims.left + (i + 1./2) * (boardPixelDims.width / Board::Dims::MAXX);
tilePos.y = boardPixelDims.top + (j + 1./2) * (boardPixelDims.height / Board::Dims::MAXY);
tileXY.at (i).push_back (tilePos); //add circle in column
}
}
I use a 2D vector of POINTs, tileXY, to store the positions. Recall the board is 7 circles wide by 6 circles high.
My logic is such that the first circle starts (for X) at:
left + width / #circles * 0 + width / #circles / 2
and increases by width / #circles each time, which is easy to picture for smaller numbers of circles.
Later, I draw the circles like this:
for (const std::vector<POINT> &col : _tileXY)
{
for (const POINT pos : col)
{
if (g.FillEllipse (&red, (int)(pos.x - CIRCLE_RADIUS), pos.y - CIRCLE_RADIUS, CIRCLE_RADIUS, CIRCLE_RADIUS) != Gdiplus::Status::Ok)
MessageBox (_windows.gameWindow, "FillEllipse failed.", 0, MB_SYSTEMMODAL);
}
}
Those loops iterate through each element of the vector and draws each circle in red (to stand out at the moment). The int conversion is to disambiguate the function call. The first two arguments after the brush are the top-left corner, and CIRCLE_RADIUS is 50.
The problem is that my board looks like this (sorry if it hurts your eyes a bit):
As you can see, the circles are too far up and left. They're also too small, but that's easily fixed. I tried changing some ints to doubles, but ultimately ended up with this being the closest I ever got to the real pattern. The expanded formula (expanding (i + 1./2)) for the positions looks the same as well.
Have I missed a small detail, or is my whole logic behind it off?
Edit:
As requested, types:
tilePos.x: POINT (the windows API one, type used is LONG)
boardPixelDims.*: double
Board::Dims::MAXX/MAXY: enum values (integral, contain 7 and 6 respectively)
Depending on whether CIRCLE_SIZE is intended as radius or diameter, two of your parameters seem to be wrong in the FillEllipse call. If it's a diameter, then you should be setting location to pos.x - CIRCLE_SIZE/2 and pos.y - CIRCLE_SIZE/2. If it's a radius, then the height and width paramters should each be 2*CIRCLE_SIZE rather than CIRCLE_SIZE.
Update - since you changed the variable name to CIRCLE_RADIUS, the latter solution is now obviously the correct one.
The easiest way I remember what arguments the shape related functions take is to always think in rectangles. FillEllipse will just draw an ellipse to fill the rectangle you give it. x, y, width and height.
A simple experiment to practice with is if you change your calls to FillRect, get everything positioned okay, and then change them to FillEllipse.