I use PPLA language to print labels directly to an Argox OS-214.
How can I word wrap long texts?
Do I have to make the calculations or is there any function that does that for me?
If I have to calculate, do you hav any tips on how to do it?
In the PPLA language manual there are no functions to word wrap long text lines, so you have to calculate yourself where to insert the line breaks before sending the commands to the printer.
How to do it depends by the programming language that you are using; for example in the PHP language there is a wordwrap function that can do it for you (the C source code of the wordwrap function is here, at line 803).
A simple description of a word wrapping algorithm can be found on the wikipedia:
SpaceLeft := LineWidth
for each Word in Text
if (Width(Word) + SpaceWidth) > SpaceLeft
insert line break before Word in Text
SpaceLeft := LineWidth - Width(Word)
else
SpaceLeft := SpaceLeft - (Width(Word) + SpaceWidth)
First of all, you have to split the text in words, but I suppose that this is not a problem for you, so I won't elaborate further.
LineWidth should be calculated as the label width minus the left and right margins width.
Assuming that for your text you're using a single fixed-width (non proportional) font, you can convert the LineWidth to a number of characters by simply dividing it by the character width (or by simply printing a long line of text and then counting the number of characters that gets printed); this way each word length corresponds to the number of its characters.
Otherwise, if you're using a proportional font, calculating each word width is more difficult, unless you know each character width.
An idea could be using an image with the same pixel size of the label, just to simulate the printing and for calculating where to insert the line breaks, but this requires to have a function to calculate the pixel width of each word (for example in PHP there is the imagettfbbox function), and also to use exactly the same font on the printer and the PC.
Be aware that you could also have to handle some words that are longer than the label width; in this case you must simply split the word at LineWidth and continue on the next line.
Related
In my app, I draw custom background under lines of text in UITextView. To do so, I use boundingRect(forGlyphRange:in:) method of NSLayoutManager. It works well for LTR languages (Latin, Cyrillic, Chinese etc):
But if I use it for RTL texts (Hebrew, Farsi etc), the method returns wrong bounding rect (see padding on the left edge):
These additional paddings seem to have some logic. The first character determines the size of the padding for a line:
What the reason for this extra padding and how to get the correct bounding rect?
Here is a demo: https://www.dropbox.com/s/bpsr7bhhd7gqm2z/hebrew.zip?dl=0
UPD: seems this function gives a wrong result for all alphabets that use, I don't know how it's called, mutating characters. Devanagari for example:
A multiline auto typing text box class (which uses an SKNode as the parent) is created using basically 2 elements:
an SKSpriteNode that acts as text box frame & background image/texture holder.
an NSMutableArray containing a set limited amount (rows) of NSStrings that each have a set character length.
After modifying this text box class so that it can be initialized with any frame width & height, I realized I didn't program the NSMutableArray to automatically change its content in a such way that it nicely fits within the background node (with a bit of padding involved as well). So here I am wondering how to do that since NSString's can only return the character count and not the width & height of each string in points (points could have maybe helped me create character constraints in some way).
Right now, the NSMutableArray uses a hardcoded maximum character count per NSString & a maximum row count for the entire array (it's 5 rows right now and when that limit is reached, a new "page"/array is created). This forces me to manually re-adjust these parameters every time I change the background node frame size which defeats the purpose of the class allowing the background frame to change.
Thing is, I'm trying to solve this in such a way that when I post this class on github, I want the solution to take into consideration any fontName & fontSize.
What are my options for solving this problem?
I've done something similar to this. It doesn't work 100% as to what you want, but should be similar enough. It uses a root node and from there, it will build multi-line text using an array of NSString which will in turn be used to build the SKLabelNode.
I'll outline what I did. I should also say I only run this when new text is set. In other words, I do not incur the penalty of deriving the information every frame. Only once.
The generalized steps are:
You will iterate over each character in the text string. Note I do this because my code supports word wrapping as well as other alignment capabilities. So for me, I want that level of control. As this is being done only upon creation, I'm fine with the overhead. If you don't want to word wrap you could always just create an array of words and work from there.
As you iterate over each character, you'll be generating an array of lines. Where each line in the array is a line that will fit in your frame. For now let's not worry about vertical constraints. So here we are primarily worried about width. For the current line, each character you are iterating over will get added to the current line. For this potential line string, you will use NSString's sizeWithAttributes, which is configured for your font. For example in my code it is an NSDictionary which contains: NSFontAttributeName : [UIFont fontWithName:self.fontName size:self.size]. This will be used to check the width, if that width exceeds the frame width, you are overrunning the line.
So the code may look something like:
size = [line sizeWithAttributes:attributes];
if (size.width > maxTextWidth) {
needNewline = YES;
}
If you have overrun a line, you need to determine if you are word wrapping. If you are, you can just add the current line (minus one character) to the lines array. If not you have prune off the last word in the current line and then add that to the array of lines.
The tricky parts are dealing with whitespace and handling non-word wrapped overflow. I have not addressed whitespace but you need to consider this very much in your code. Additionally, you also do want to factor in leading pixels, etc.
Once you have your array of lines, you can then create your children SKLabelNodes. I add them to the root, which allows me to move the group anywhere it needs to be.
The real key here is the lines array generation.
I have seen here people needing to calculate the size of the NSString given a size but I need to do the opposite.
Given a specified rect (or fixed UITextView, or multiline UILabel, no scrolling) I need to know:
if it managed to show all the chars of my NSString
if not, what is the last char shown
So that I can display the remaining text in another UITextView (of course if I could use a single UITextView I would not have this problem).
At first it seems a simple thing to do, but actually I am not finding a way, intuitively I think I could use either UITextView's:
textView.contentSize.height;
or NSString's:
sizeWithFont:constrainedToSize:lineBreakMode:
or a combination of the two, but I need to be precise and those methods do not help me in telling what is the last character that managed to fit the visible area of the UITextView.
Not sure if this is actually possible, but is a requirement of my client who thinks programming iOS is like printing a newspaper and expects to be able to format text around an image....
You could maybe get the maximum height of one line of text from a one character long string.
If you use that with sizeWithFont:constrainedToSize:lineBreakMode: then you should be able to know if your text runs onto more than one line (if the cgsize height is greater than the height of one line of text).
In order to find out the last character (or word) you would have to loop around the length of the string adding characters (or words) as you go and checking for when the cgsize height increases to add a new line, this will give you the character point when to split into multiple strings ( for multiple fields/labels/textviews ) or when to insert line breaks into the text ( if using a single multi-line textview or label ).
I hope you find an easier way...
I have a string that i need to calculate the Rect size (text height) when drawing. My implementation uses the DrawTextW() function with DT_WORDBREAK or DT_CALCRECT flags.
An example of my string:
thisisaverylonglonglonglineoftextthatneedstofitinsideagivenrectwidth
I can see in the MSDN docs, that DrawTextW() method states:
If the largest word is wider than the rectangle, the width is expanded. If the text is less than the width of the rectangle, the width is reduced. If there is only one line of text, DrawText modifies the right side of the rectangle so that it bounds the last character in the line.
however in the MSDN docs, then DrawTextExW() method does not state this.
So i tried to calculate the height using the DrawTextExW() method, however the result is the same as with DrawTextW() function, where it extends the width of the rect to fit the largest line of text.
So how can i correctly calculate the height of the text rect with a given (fixed) width when drawing a large string (with no spaces) where DT_WORDBREAK and DT_CALCRECT are specified?
EDIT:
As a side note, does anyone know how Microsoft Excel does cell text drawing? Is there an API call for this text drawing? This was where my original question stemmed from, however the way it is implemented in Excel is to draw the text and wordbreak/wordwrap on any character (not just a space).
You need to use the DT_WORD_ELLIPSIS flag in uFormat parameter (along with DT_WORDBREAK of course). That will prevent the widening due to long strings with no spaces. It still will not break those long strings though, but your width problem will be solved.
If you also specify DT_MODIFYSTRING, then you could kind of figure out where to break that long string yourself, prior to the final draw.
As for the difference between DrawText(W) and DrawTextEx(W): the latter provides tab formatting, setting margins and returns the actual number of drawn characters. There is no difference in (dimensioning) functionality.
It's a TMemo, not that that should make any difference.
Googling suggests that I can use Canvas->TextWidth() but those are Delphi examples and BCB doesn't seem to offer this property.
I really want something analogous to memo->Font->Height for width.
I realize that not all fonts are fixed width, so a good estimate will do.
All that I need is to take the width of a TMemo in pixels and make a reasonable guess at how many characters of the current font it will hold.
Of course, if I really want to be lazy, I can just google for the average height/width ratio, since height is known. Remember, an approximation is good enough for me if it is tricky to get exact.
http://www.plainlanguagenetwork.org/type/utbo211.htm says, " A width to height ratio of 3:5 (0.6) is recommended for most applications"
Actually your google search is not entirely off. You do need access to a canvas object, or at least a handle to a DC object. In general when searching for help concerning VCL classes it often pays to search for delphi examples since these are more common.
Anyway to calculate the size of a string you could have a look at the TextExtent function, it is a function for the TCanvas class. Simply pass the character which width you want to test, and the return value will be a TSize construct. However there is also a TextWidth function, as well as a TextHeight function. You can use these as well. Actually these call the TextExtent internally.
You have to note one thing though, the functions use the current font of the TCanvas object, more specifically the font bound to the DC the canvas uses. So assign the font you wish to test with first, and then pass the character.
I have some old code that calculates the width of a string like this:
// This canvas could be the form canvas: canvas = Form1->Canvas or the
// memo canvas which will probably be what you want.
canvas->Font->Assign(fontToTest);
int textwidth = TextWidth(textToTest);
If you want more control of what to do, you can also do this using the Windows API, this is essentially what the VCL does for you, in that case the following example would look like this:
// This canvas could be the form canvas: canvas = Form1->Canvas
canvas->Font->Assign(fontToTest);
// The initial size, this is really important if we use wordwrapping. This is
// the text area of the memo control.
TRect rect = ClientRect;
// This is the font format we wish to calculate using, in this example our text
// will be left aligned, at the top of the rectangle.
fontformat = DT_LEFT | DT_TOP;
// Here we calculate the size of the text, both width and height are calculated
// and stored in the rect variable. Also note that we add the DT_CALCRECT to the
// fontformat variable, this makes DrawTextEx calculate the size of the text,
// without drawing it.
::DrawTextEx(canvas->handle,
textToTest.c_str(),
textToTest.Length(),
&rect,
fontformat | DT_CALCRECT,
NULL);
// The width is:
int width = rect.Width();
The fontformat is a parameter that specifies different options for how to align and layout the text, if you plan on drawing text it will be a good idea to check out the different possibilities it offers: DrawTextEx Function [1]
EDIT: Reading through your question again, it struck me that the function you might be searching for is: GetTextExtentExPoint Windows API documentation states the following about this function:
The GetTextExtentExPoint function
retrieves the number of characters in
a specified string that will fit
within a specified space and fills an
array with the text extent for each of
those characters. (A text extent is
the distance between the beginning of
the space and a character that will
fit in the space.) This information is
useful for word-wrapping calculations.
You can find more information about the GetTextExtentExPoint function here: GetTextExtentExPoint Function [2]
[1] http://msdn.microsoft.com/en-us/library/dd162499%28VS.85%29.aspx
[2] http://msdn.microsoft.com/en-us/library/dd144935%28VS.85%29.aspx
What you could do, if you have access to Win32 API functions, is create a RichEdit Window the same size as your TMemo window, place the text in the RichEdit window, send the EM_FORMATRANGE message to the window and from the result determine how many characters it will hold. Of course this method will work with multiple lines etc...