I am having a problem of drawing a character in a CATextLayer such that the layer is exactly the size of the character.
I use the code below to get the size of the glyph corresponding to the character in the string. At the moment I am neglecting diacretics, so that I am assuming that there is a one to one correlation between glyphs and characters.
I also get nice bounding box values for several characters for the Helvetica font at size 128pt:
Character | x | y | width | height |
B | 9.4 | 0.0 | 70.8 | 91.8 |
y | 1.3 | -27.4 | 61.2 | 96.0 |
I am not sure where the origin of the coordinate system is located in which the coordinates are expressed. I am assuming that (0,0) is at the very left and vertically located on the baseline of the font. That is why 'y' has a negative y value.
I am using this code to calculate the size of a capital B and resize its CATextLayer accordingly.
- (CATextLayer *) testTextLayer
{
CATextLayer *l = [CATextLayer layer];
l.string = #"B";
NSUInteger len = [l.string length];
l.fontSize =128.f;
CGColorRef blackColor = CGColorCreateGenericGray(0.f, 1.f);
l.foregroundColor = blackColor;
CGColorRelease(blackColor);
// need to set CGFont explicitly to convert font property to a CGFontRef
CGFontRef layerFont = CGFontCreateWithFontName((CFStringRef)#"Helvetica");
l.font = layerFont;
// get characters from NSString
UniChar *characters = (UniChar *)malloc(sizeof(UniChar)*len);
CFStringGetCharacters((__bridge CFStringRef)l.string, CFRangeMake(0, [l.string length]), characters);
// Get CTFontRef from CGFontRef
CTFontRef coreTextFont = CTFontCreateWithGraphicsFont(layerFont, l.fontSize, NULL, NULL);
// allocate glyphs and bounding box arrays for holding the result
// assuming that each character is only one glyph, which is wrong
CGGlyph *glyphs = (CGGlyph *)malloc(sizeof(CGGlyph)*len);
CTFontGetGlyphsForCharacters(coreTextFont, characters, glyphs, len);
// get bounding boxes for glyphs
CGRect *bb = (CGRect *)malloc(sizeof(CGRect)*len);
CTFontGetBoundingRectsForGlyphs(coreTextFont, kCTFontDefaultOrientation, glyphs, bb, len);
CFRelease(coreTextFont);
l.position = CGPointMake(200.f, 100.f);
l.bounds = bb[0];
l.backgroundColor = CGColorCreateGenericRGB(0.f, .5f, .9f, 1.f);
free(characters);
free(glyphs);
free(bb);
return l;
}
This is the result I am getting from the above code. It seems to me that the size is correct, however there is some kind of padding taking place around the character.
Now my questions
Am I right with the assumption of the origin of the bounding box of the glyph?
How can one draw the letter such that it fits neatly into the layer, without this padding? Or alternatively, how can one control this padding?
Maybe I am missing an obvious point here. Is there now way after setting the size and the font of the layer to shrink wrap the layer around the character in a defined way (meaning with optional padding, a bit like in CSS)?
How about creating a CGPath from a glyph with CTFontCreatePathForGlyph and then getting its bounding box with CGPathGetBoundingBox?
An alternative would be to create a CTRun somehow and use the CTRunGetImageBounds function which also returns a "tight" bounding box, but this probably requires more lines of code than the aforementioned approach and you'd need to have a graphics context.
I assume this has to do with the built-in space around letters. Their bounding box usually includes some amount of space that serves as a general spacing if you assemble glyphs in a line. These are then optimized with kerning tables.
And yes, extracting the actual bezier path is a very good method of getting a tight bounding box. I have been doing that for years, though I have no experience using CATextLayers.
Related
I am trying to render text with Harfbuzz and a signed distance field atlas.
The code is basically this:
void drawText(const std::wstring &str, Vec2 pos)
{
// Init harfbuzz
hb_buffer_t *hbBuf = hb_buffer_create();
hb_buffer_set_direction(hbBuf, HB_DIRECTION_LTR);
hb_buffer_set_script(hbBuf, HB_SCRIPT_LATIN);
hb_buffer_set_language(hbBuf, hb_language_from_string("en", 2));
// Process string
hb_buffer_add_utf32(hbBuf, reinterpret_cast<const uint32_t*>(str.c_str()), -1, 0, -1);
hb_shape(font.hb, hbBuf, nullptr, 0);
// Display string
unsigned int nbGlyphs;
hb_glyph_info_t *glyphInfos = hb_buffer_get_glyph_infos(hbBuf, &nbGlyphs);
hb_glyph_position_t *glyphPos = hb_buffer_get_glyph_positions(hbBuf, &nbGlyphs);
for(unsigned int i = 0; i < nbGlyphs; i++)
{
Vec2 drawPos = pos + Vec2(glyphPos[i].x_offset, glyphPos[i].y_offset) / 64.f;
drawGlyph(glyphInfos[i].codepoint, drawPos);
pos.x += glyphPos[i].x_advance / 64.f;
pos.y += glyphPos[i].y_advance / 64.f;
}
}
The text looks correctly shaped for an English phrase, but when I test it with diacritics, they look misplaced.
I am testing it with aâa aâ̈a bb̂b bb̂̈b bb̧b bb͜b bb︠︡b. The Unicode string does not contain precombined characters. Harfbuzz uses the precombined character â, which makes this one look good. Most other diacritics are off.
Text with diacritics on the left of where they should be
When I multiply x_offset by 0.5, the combining characters are better placed. The accents and the cedilla are at the right x position. The accents do not stack and are too low on the b. The arc under BBB (U+035C) should join the two last letters instead of being centered on the 2nd b.
I also tried with U+FE20 and U+FE21 on the previous group of b. In my tests, U+FE21 is on the 2nd b, but it looks like it should be on the 3rd.
Test with glyphPos[i].x_offset * 0.5f, better but still wrong
I tried with several fonts, but of those fonts, only NotoSansDisplay-Regular.ttf had combining characters. I did not manage to make a program display that string as expected on my Debian system (testing, with HarfBuzz 2.6.4-1).
With Windows, I got better results. Here is what I expect: the accents are stacked, the combining double breve below it at the right place, the cedilla is off.
Text rendering closer to what I expect
Am I doing something wrong with HarfBuzz, or I am testing to niche cases that HarfBuzz does support yet?
EDIT:
The actual problem was not described above.
I loaded a font with FreeType FT_New_Face then created a hb_font_t with hb_ft_font_create.
For every string drawn, I called FT_Set_Pixel_Sizes but kept that hb_font_t.
You should try shaping the same text and font with hb-view / hb-shape. That would help you narrow down where the problem is. I'm making a wild guess that the problem is in how / whether you are accounting for glyph origin in your atlas.
Create a new hb_font_t with hb_ft_font_create every time the font size i changed with FT_Set_Pixel_Sizes.
Is there a way to get the exact size computed by a label before laying it out.
In other words, let us say there is a label with a really long sentence:
Hi how are you doing, my name is bond, I have no name, I live in London, but yet the world is my Home. I am here to play and enjoy.
This is a single line. however if the window is not large enough, then the label wraps into two lines.
I want to find out the final height that it will take up, before laying it out (this is so that I can adjust the font size accordingly so that it displays in a single line).
However if I do label.computeSize(SWT.DEFAULT, SWT.DEFAULT) it only returns the size that it needs without wrapping.
If there is a way to do it that would be great.
Fist of all the Label must be created with the SWT.WRAP style. Otherwise it will never wrap its text.
Then you need to tell computeSize what width the label should have. Otherwise computeSize doesn't know where to wrap and computes the size for the unwrapped text.
If you specify the desired width of the label, computeSize will return the neccesary height.
For example
Label label = new Label( shell, SWT.WRAP );
label.setText( "Snippets are minimal stand-alone programs that demonstrate specific techniques or functionality." );
Point size = label.computeSize( 200, SWT.DEFAULT );
System.out.println( size ); // Point {202, 47}
The returned size is a Point whose x field is (almost) the desired width and the y field denotes the necessary height to display the wrapped text.
On my system, the size is { 202, 47 } (the text spans two lines) but this may vary depending on the platform and font size.
Agree with Rudiger Herrmann's answer.
I have done it previously for my purpose. But it was dependent on OS. I have adjusted size of the font to make label in a line in a Grid. Please find comments in code.
parent.setLayout(new GridLayout(1, false));
label = new Label( parent, SWT.BORDER | SWT.WRAP );
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
label.setTouchEnabled(true);
label.setToolTipText("LABEL");
label.setForeground(SWTResourceManager.getColor(SWT.COLOR_DARK_MAGENTA));
label.setBackground(SWTResourceManager.getColor(SWT.COLOR_GREEN));
label.setText( "Stack Overflow is a question and answer site for professional and enthusiast programmermers. It's built and run by you as part of the Stack Exchange network of Q&A sites." );
Point parentCompositeSize = parent.getShell().getSize();
System.out.println(parentCompositeSize);
// parentCompositeSize.x is width of parent window (Shell) width, if you have your desired width of (window / Composite) then you can give it direct in int
// Calculation of number of lines with default font ::
// Warning:: Calculations have some assumption as below.
//1. Spacing between two lines will be 1 unit.
//2. My Layout used is GRID Layout.
Point estimatedLabelSize = label.computeSize( parentCompositeSize.x, SWT.DEFAULT );
System.out.println( "Estimated Label Height " + estimatedLabelSize.y);
FontData[] fD = label.getFont().getFontData();
int defaultFontSize = fD[0].getHeight(); //Font Height is 11 in my case.
defaultFontSize += 1; //1 is spacing between two lines, According to assumption
System.out.println(defaultFontSize + "default size");
int lines = estimatedLabelSize.y / defaultFontSize;
System.out.println("Default fonts will print " + lines + " lines" );
int suggestedFontSize = defaultFontSize / lines;
System.out.println(suggestedFontSize +" suggested size ");
fD[0].setHeight(suggestedFontSize);
final Font newFont = new Font(parent.getDisplay(), fD);
label.setFont(newFont);
label.addDisposeListener(new DisposeListener() {
#Override
public void widgetDisposed(DisposeEvent e) {
newFont.dispose();
}
});
I am trying to fit a sentence that changes often, in to a few jlabels. Widths of my 3 jlabels stay unchanged all the time. What I am doing is changing the font size so all the characters can fit with out non being out of the display range of the labels. What I do is call below code snippet when ever sentence is changed.
Here is my code
String sentence = "Some long sentence";
int SentenceLength = sentence.length();
int FontSize = 0;
// sum of widths of the three labels
int TotalLblLength=lbl_0ValueInWords.getWidth()+lbl_1ValueInWords.getWidth()+lbl_1ValueInWords.getWidth();
/*decide the font size so that all the characters can be displayed
with out exceeding the display renge(horizontal) of the 3 labels
Inconsolata -> monopace font
font size == width of the font*2 (something I observed, not sure
if this is true always) */
FontSize=(TotalLblLength/SentenceLength)*2;
// max font size is 20 - based on label height
FontSize=(FontSize>20)?20:FontSize;
lbl_0ValueInWords.setFont(new java.awt.Font("Inconsolata", 0,FontSize));
lbl_1ValueInWords.setFont(new java.awt.Font("Inconsolata", 0,FontSize));
lbl_2ValueInWords.setFont(new java.awt.Font("Inconsolata", 0,FontSize));
int CharCount_lbl0 = width_lbl0 / (FontSize / 2);
int CharCount_lbl1 = width_lbl1 / (FontSize / 2);
int CharsCount_lbl2 = width_lbl2 / (FontSize / 2);
/*Set texts of each label
if sentence has more than the number of characters that can fit in the
1st label, excessive characters are moved to the 2nd label. same goes
for the 2nd and 3rd labels*/
if (SentenceLength > CharCount_lbl0) {
lbl_0ValueInWords.setText(sentence.substring(0, CharCount_lbl0));
if (SentenceLength > CharCount_lbl0 + CharCount_lbl1) {
lbl_1ValueInWords.setText(sentence.substring(CharCount_lbl0, CharCount_lbl0 + CharCount_lbl1));
lbl_2ValueInWords.setText(sentence.substring(CharCount_lbl0 + CharCount_lbl1, SentenceLength));
} else {
lbl_1ValueInWords.setText(sentence.substring(CharCount_lbl0, SentenceLength));
}
} else {
lbl_0ValueInWords.setText(sentence);
}
But even after resetting font size sometimes the last character goes out of the display range. I have removed margines from the jlabels that may cause this. This happens for random length sentences. I can solve the problem for the application by reducing label width used for the calculations(hopefully)
Can anyone explain me the reason? Could be because of some defect in the fonts symmetry?
There is no such thing as font symmetry?
There are 2 types of fonts for what you are dealing with. Monospace fonts, and non-monospace fonts. Monospace fonts have the same exact width for every single character you can type. The others do not.
On top of that, fonts are rendered differently across different OS's. Something on windows will be around 10-20% longer on Mac because they space out the fonts differently.
Whatever it is you are trying to do with JLabels, stop. You should not be using 3 JLabels to show 3 lines of text because they dont fit. Scrap them and use a JTextArea. It has text wrap, you can set the font, and remove the margin/border/padding and make it non-editable. You can customize it very easily so it is indistinguishable from a JLabel, but it will save you a ton of work.
Pick the right tool for the right job.
I am using Eclipse & JDE 4.5.0 plug-in. How to align fields vertically. Can we align fields like LEFT_BOTTOM,RIGHT_BOTTOM, LEFT_VCENTER, RIGHT_VCENTER, CENTER(vertically & horizontally), BOTTOM_CENTER, etc...?
BlackBerry UI field managers are notoriously annoying when dealing with field alignment. Managers seem to ignore all style flags (like HCENTER, VCENTER, etc) so the only way you'll be able to do this is to override the sublayout method of your manager and do it yourself.
Here's a little snippet to show you what I mean. This particular code actually does horizontal centering, not vertical centering, but once you get the idea you can implement any styles you need.
VerticalFieldManager mainmanager = new VerticalFieldManager(Field.USE_ALL_WIDTH | Field.USE_ALL_HEIGHT)
{
protected void sublayout( int width, int height ) {
super.sublayout( width, height );
width = getWidth();
height = getHeight();
for (int i = 0;i < this.getFieldCount() - 1; i++)
{
Field field = this.getField(i);
//this positions the item in the middle of the manager
int x = (int)((width - field.getWidth()) * 0.50);
setPositionChild(field, x, field.getTop());
}
}
Please note that the USE_ALL_WIDTH and USE_ALL_HEIGHT style flags are important. If you want to do things like vertical centering, bottom-right aligning, etc. you will need to write the positioning code yourself. For bottom-right alignment for example you could set the x position to the width of the manager minus the width of the field, and the y position to the height of the manager minus the height of the field.
If you want to be able to use one custom manager class to handle multiple different styles (like bottom right, bottom left) you can add some logic in sublayout to check the field's style flag and then position the field appropriately.
Hopefully this all makes sense and helps you. :)
HorizontalFieldManager only accepts Vertical Alignment Styles and VerticalFieldManager only accept Horizontal Alignment. That's it.
Annoying ++
I need to render fonts into a 3d game world, so I use the GetGlyphOutline outline function to get the glyph shapes to render into a texture. However, I want to be able to handle the case where characters are not present in the given font (as is often the case for asian other other international text). Windows text rendering will automatically substitute fonts which have the needed characters. But GetGlyphOutline will not. How can I detect this case, and get the outlines for the substituted glyphs? Mac OS X Core Text has a function to get a matching substitution font for a given font and a string - is there anything similar on windows?
Found out what I needed to know myself: The IMLangFontLink interface, especially the MapFont method contain the needed functionality to find out which substitution fonts should be used on windows.
I too have puzzled with GetGlyphOutline. I'm not sure if you were able to do the same, but I was able to get mixed-script text outlines by using TextOut() in combination with BeginPath(), EndPath() and GetPath().
For example, even with the Arial font, I am able to get the path of the Japanese text 「テスト」 (using C++, but can easily be done in C as well):
SelectObject(hdc, hArialFont);
BeginPath(hdc);
TextOut(hdc, 100, 100, L"\u30c6\u30b9\u30c8"); // auto font subbing
EndPath(hdc);
// get number of points in path
int pc = GetPath(hdc, NULL, NULL, 0);
if (pc > 0)
{
std::vector<POINT> points(pc);
std::vector<BYTE> types(pc); // PT_MOVETO, PT_LINETO, PT_BEZIERTO
GetPath(hdc, &points[0], &types[0], pc);
// it seems the first four points are the bounding rect
// subsequent points match up to their types
for (int i = 4; i < pc; i++)
{
if (types[i] == PT_LINETO)
LineTo(hdc, points[i].x, points[i].y); // etc
}
}