How to override tab width in qt? - c++

I just need to know how to change the tab size in Qt in a QTextEdit. My Google and stackoverflow search returned me null. Thanks in advance.

If you want to create a source code editor using QTextEdit, you should first assign a fixed-width (monospace) font. This ensures that all characters have the same width:
QFont font;
font.setFamily("Courier");
font.setStyleHint(QFont::Monospace);
font.setFixedPitch(true);
font.setPointSize(10);
QTextEdit* editor = new QTextEdit();
editor->setFont(font);
If you want to set a tab width to certain amount of spaces, as it is typically done in text editors, use QFontMetrics to compute the size of one space in pixels:
const int tabStop = 4; // 4 characters
QFontMetrics metrics(font);
editor->setTabStopWidth(tabStop * metrics.width(' '));

The QTextEdit::tabStopWidth property might solve your problem (see here for Documentation...)

While the question about how to set tab stop width has been answered already; computing the correct tab width in pixels is still (or again?) an open question.
Since Qt 5.10, QTextEdit::tabStopWidth is marked as obsolete and QTextEdit::tabStopDistance was introduced. tabStopWidth was integer, tabStopDistance is double.
Why so complicated?
Setting n * QFontMetrics::width(' ') as tab stop width makes troubles because font_metrics.width returns an integer. Even if you have a monospace standard font, the width of a single character is actually not an integer, so QFontMetrics::width returns an inaccurate measure.
If you compare the appearance of the strings ........| and \t\t\t\t| (\t = tab, n=2), you will see that the pipes are not aligned properly. It will become worse the more tabs you insert.
Solution
You could do what #Ferdinand Beyer proposed, but it will slightly change the typeface. I also had to adapt his method to make it work. However, there's a much simpler approach which exploits that you can set tabStopDistance now with double precision:
static constexpr int tab_width_char = 2;
m_text_edit->setFont(QFont("Courier", 12));
const auto font_metrics = m_text_edit->fontMetrics();
static constexpr int big_number = 1000; // arbitrary big number.
const QString test_string(" ");
// compute the size of a char in double-precision
const int single_char_width = font_metrics.width(test_string);
const int many_char_width = font_metrics.width(test_string.repeated(big_number));
const double single_char_width_double = many_char_width / double(big_number);
// set the tab stop with double precision
m_text_edit->setTabStopDistance(tab_width_char * single_char_width_double);
This would be so much simpler if Qt offered a way to get the width of a single character as double.

While #Ferdinand Beyer's solution will work on some systems, generally fonts are not guaranteed to have integer metrics. e.g 12pt DejaVu Sans Mono on my Linux setup has character width of 9.625. The best solution I've found is add some letter spacing to get pixel-perfect alignment.
int tabstop = 4;
QFontMetricsF fm (ui->textEdit->font());
auto stopWidth = tabstop * fm.width(' ');
auto letterSpacing = (ceil(stopWidth) - stopWidth) / tabstop;
auto font = ui->textEdit->font();
font.setLetterSpacing(QFont::AbsoluteSpacing, letterSpacing);
ui->textEdit->setFont(font);
ui->textEdit->setTabStopWidth(ceil(stopWidth));

Computing a product of a size of one space and num spaces is not always precise (tested under macOS, Monaco font), presumably due to some gaps in between the characters in the real string.
A better solution would be to measure the length of the string containing tabStop spaces:
const int tabStop = 4; // 4 characters
QString spaces;
for (int i = 0; i < tabStop; ++i) {
spaces += " ";
}
QFontMetrics metrics(font);
editor->setTabStopWidth(metrics.width(spaces));

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

Calculate max length of QLineEdit to suit its width

I created some QLineEdit, which have different sizes.
I want to set the max length of those to suit the width of them.
To be more specific: for example, with a width of 50, I will only allow to enter about 7 characters, because the size of each character is different.
How can I exactly calculate the maximum length needed?
You can set a limit based on the width of myLineEdit such that it will be able to fit all characters up to that limit by doing this:
#include <QFontMetrics>
#include <QLineEdit>
QLineEdit myLineEdit;
// get max character width of the line edit's font
int maxWidth = QFontMetrics(myLineEdit->font()).maxWidth();
// find the character limit based on the max width
int charlimit = myLineEdit.width() / maxWidth;
// set the character limit on the line edit
myLineEdit->setMaxLength(charlimit);
However, here are some reasons you probably don't want to in production code:
Stylesheets - what happenes when your designer gives the edit a 14px border?
Resizing - you'll have to adjust this on every resize, and I've found that in practice it's a very difficult thing to keep track of. It's not a useful feature if it breaks whenever the GUI gets resized.
Content - the width of the line edit, especially one that a user can enter text into, is not logically related to the length of text the user may need to enter. This could become an arbitrary constraint that frustrates your users. No one likes horizontal scrolling, but sometimes there are use cases.
Use a QFontMetrics:
QFontMetrics fm(QApplication::font()); // You can use any font here.
int stringWidth = fm.width("Some string");
EDIT:
At first you have to know when your QLineEdit is resized. So you can either derive your own class and reimplement the resizeEvent() method, or you can use an event filter.
Then create a special validator:
class CLengthValidator : public QValidator
{
public:
CLenghtValidator(QObject* parent, const QFont & font)
: QValidator(parent), m_maxw(0), m_fm(font)
{}
void setMaxValidWidth(int width)
{
m_maxw = width;
}
State validate(QString & input, int & pos) const override
{
int stringWidth = m_fm.width(input);
if (stringWidth <= m_maxw)
return Acceptable;
else
return Invalid;
}
private:
int m_maxw;
QFontMetrics m_fm;
}
Set the validator to your line edit (using the QLineEdit::setValidator() method):
lineEdit->setValidator(new CLengthValidator(lineEdit, lineEdit->font()));
Now, every time the line edit is resized, you have to call CLengthValidator::setMaxValidWidth() method with the current line edit width as a parameter. You will do that in your reimplemented QLineEdit::resizeEvent() method or in your event filter.
This way you will get a line edit accepting only string which width is not greater than width of actual line edit.

Firemonkey: Shrink text font to fit in TLabel

I am attempting to lower the font size of a TLabel if its text is to large to fit in the confines of the label. I didn't see any properties I could set on the label to achieve this, so I have tried writing my own method. My method works by using TCanvas.TextWidth to measure the width of the text in a label, and shrink the font until the width of the text fits within the width of the label.
void __fastcall ShrinkFontToFitLabel( TCanvas * Canvas, TLabel * Label )
{
float NewFontSize = Label->Font->Size;
Canvas->Font->Family = Label->Font->Family;
Canvas->Font->Size = NewFontSize;
while( Canvas->TextWidth( Label->Text ) > Label->Width && NewFontSize > MinimumFontSize )
{
NewFontSize -= FontSizeDecrement;
Canvas->Font->Size = NewFontSize;
}
Label->Font->Size = NewFontSize;
}
This works some of the time, however other times it does not shrink the font near enough. It seems as if the value I get from calling Canvas->TextWidth is a lot of times, much smaller than the number of pixels wide the label actually needs to be in order to fit the text.
Am I using Canvas->TextWidth incorrectly? Is there a better way to calculate the width of a string, or to re-size the font of a TLabel so its text fits within its demensions?
Edit:
In this case, I am passing in to my function, the TCanvas that my label is sitting in. I have tried using that TCanvas as well as Label->Canvas. Both give me the same number for text width, and both are short of the actual value in pixels needed to display the whole string.
The following code is taken from code that works in an FMX application, modified slightly to remove arrays that are being iterated through and declaring a variable locally to the function. It is being run in a TForm method. Canvas here is the Form's Canvas. You can see that I'm using "- 35" at one point - this might be because the numbers weren't quite right.
double InitialFontSize = 30;
Canvas->Font->Size = InitialFontSize;
StoryHeadlineLabel->Font->Size = InitialFontSize;
bool fits = false;
do
{
double widthA = Canvas->TextWidth (StoryHeadlineLabel->Text);
if (widthA > StoryHeadlineLabel->Width - 35)
{
StoryHeadlineLabel->Font->Size --;
Canvas->Font->Size --;
}
else
fits = true;
if (StoryHeadlineLabel->Font->Size < 6)
fits = true;
} while (!fits);

Cannot get text with Unicode (including Chinese) characters to line up with MFC using a CRichEditCtrl

I have a CRichEditCtrl (actually I have a class that is a subclass of a CRichEditCtrl, a class that I defined) that is populated by many lines of text with both horizontal and vertical scroll bars. The purpose of this control is to display a string that is searched for in a larger text along with n characters to the right and left (e.g. if the user searches for "the" then they would get a list of all the instances of "the" in the text with (if n = 100) 100 characters to the left and right of each found instance to provide context).
The query string needs to be lined up between each row. Before this program had Unicode support, just setting the font to Courier did the trick, but now that I've enabled Unicode support, this no longer works.
I've tried using monospaced fonts, but as far as I can tell, there aren't any that are for all characters. It seems to me that the latin characters all have one size, and the Chinese characters have another (I've noticed lines of text with all latin characters line up and ones with all Chinese characters line up, but ones with both do not line up).
I've also tried center aligning the text. Since the query string in each line is in the exact center, they should all line up, but I cannot seem to get this to work, the SetParaFormat call seems to just get ignored. Here's the code I used for that:
long spos, epos;
GetSel(spos, epos);
PARAFORMAT Pfm;
GetParaFormat(Pfm);
Pfm.dwMask = (Pfm.dwMask | PFM_ALIGNMENT);
Pfm.wAlignment = PFA_CENTER;
SetSel(0, -1);
SetParaFormat(Pfm);
SetSel(spos, epos);
I do this everytime text is inserted in the ctrl, but it has no affect on the program.
Is there anyway to get the query word in each line of text to line up even when there are interspersed Chinese and latin characters? (and possibly any other character set)
See http://msdn.microsoft.com/en-us/library/bb787940(v=vs.85).aspx, in particular the cTabCount and rgxTabs members of the PARAFORMAT (or PARAFORMAT2) structure, which allow you to set tabstops.
Okay so I managed to solve it. For future reference, here's what I did:
First, I tried to find a monospaced font, but I was unable to find any that were truly monospaced (had latin and chinese characters as the same width).
Next, I tried to center the text in the window. I was unable to do this until I realized that having Auto HScroll set to true (ES_AUTOHSCROLL defined for the rich edit control) caused setParaFormat to ignore me trying to center the text. After I disabled it and manually set the size of the drawable text area, I was able to center the text. Just in case anyone is curious, here's the code I used to set the width of the drawable area in the rich edit box:
CDC* pDC = GetDC();
long lw = 99999999;
SetTargetDevice(*GetDC(), lw);
I just set lw arbitrarily large so I could test to see if centering the text worked. It did not. As it turns out, when the rich edit control centers the text, it bases it off the draw width of the text, not off the number of characters. I assumed that since there were the same number of characters on either side of the query string then that would cause the string to be centered, but this was not the case.
The final solution I tried was the one suggested by ymett. After some tweaking I came up with a function called alignText() that's called after all the text has been inserted into the rich edit control. Here's the function (Note: each line in the control had tabs inserted before this function was called: one at the beginning of the line and one after the query string e.g. "string1-query-string2" becomes "\tstring1-query\t-string2")
void CFormViewConcordanceRichEditCtrl::alignText()
{
long maxSize = 0;
CFont font;
LOGFONT lf = {0};
CHARFORMAT cf = {0};
this->GetDefaultCharFormat(cf);
//convert a CHARFORMAT struct into a LOGFONT struct
if (cf.dwEffects & CFE_BOLD)
lf.lfWeight = FW_BOLD;
else
lf.lfWeight = FW_NORMAL;
if (cf.dwEffects & CFE_ITALIC)
lf.lfItalic = true;
if (cf.dwEffects & CFE_UNDERLINE)
lf.lfUnderline = true;
if (cf.dwEffects & CFE_STRIKEOUT)
lf.lfStrikeOut = true;
lf.lfHeight = cf.yHeight;
_stprintf(lf.lfFaceName, _T("%s"), cf.szFaceName);
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
font.CreateFontIndirect(&lf);
//create a display context
CClientDC dc(this);
dc.SetMapMode(MM_TWIPS);
CFont *pOldFont = dc.SelectObject(&font);
//find the line that as the longest preceding string when drawn
for(int i = 0; i < m_pParent->m_nDataArray; i++)
{
CString text = m_pParent->DataArray[i].text.Left(BUFFER_LENGTH + m_pParent->generateText.GetLength());
text.Replace(_T("\r"), _T(" "));
text.Replace(_T("\n"), _T(" "));
text.Replace(_T("\t"), _T(" "));
CRect rc(0,0,0,0);
dc.DrawText(text, &rc, DT_CALCRECT);
int width = 1.0*cf.yHeight/fabs((double)rc.bottom - rc.top)*(rc.right - rc.left);
width = dc.GetTextExtent(text).cx;
if(width > maxSize)
maxSize = width;
}
dc.SelectObject(pOldFont);
//this calulates where to place the first tab. The 0.8 is a rought constant calculated by guess & check, it may be innacurate.
long tab = maxSize*0.8;
PARAFORMAT pf;
pf.cbSize = sizeof(PARAFORMAT);
pf.dwMask = PFM_TABSTOPS;
pf.cTabCount = 2;
pf.rgxTabs[0] = tab + (2 << 24); //make the first tab right-aligned
pf.rgxTabs[1] = tab + 25;
//this is to preserve the user's selection and scroll positions when the selection is changed
int vScroll = GetScrollPos(SB_VERT);
int hScroll = GetScrollPos(SB_HORZ);
long spos, epos;
GetSel(spos, epos);
//select all the text
SetSel(0, -1);
//this call is very important, but I'm not sure why
::SendMessage(GetSafeHwnd(), EM_SETTYPOGRAPHYOPTIONS, TO_ADVANCEDTYPOGRAPHY, TO_ADVANCEDTYPOGRAPHY);
this->SetParaFormat(pf);
//now reset the user's selection and scroll positions
SetSel(spos, epos);
::SendMessage(GetSafeHwnd(),
WM_HSCROLL,
(WPARAM) ((hScroll) << 16) + SB_THUMBPOSITION,
(LPARAM) NULL);
::SendMessage(GetSafeHwnd(),
WM_VSCROLL,
(WPARAM) ((vScroll) << 16) + SB_THUMBPOSITION,
(LPARAM) NULL);
}
Essentially what this function does is make the first tab stop right-aligned and set it at some point x to the right in the control. It then makes the second tab stop a small distance to the right of that, and makes it left-aligned. So all the text from the beginning of each line to the end of the query string (from the first \t to the second \t) is pushed toward the right against the first tab stop, and all the remaining text is pushed toward the left against the second tab stop, causing the query string to be aligned between all the lines in the control. The first part of the function finds out x (by finding out how long each line will be drawn and taking the max) and the second part sets the tab stops.
Thanks again to ymett for the solution.