I have created a label that displays a high score. I want to add the units ft to the end since the the score is a distance. I am using a custom font and simply adding ft to the end of the end results in FT which is to big due to the font size and font. I am wonder if i can reduce the font size and add it to the end.
This is the code i have so far:
var highscoreLabel:SKLabelNode!
highscoreLabel = SKLabelNode(fontNamed: "Komika Axis")
highscoreLabel.fontSize = 16
highscoreLabel.zPosition = 5
highscoreLabel.fontColor = UIColor.whiteColor()
self.addChild(highscoreLabel)
var highscoreShow = defaults.integerForKey("RegularHighscore")
highscoreLabel.text = "High score: \(highscoreShow)"
// High Score: \(highscoreShow) (ft in a smaller font size)"
I want to add ft in a smaller font size than the rest of the text in the label.
Attributed string will help there:
var ftString = NSMutableAttributedString(string:"ft", attributes:[NSFontAttributeName : UIFont(name: "Komika Axis", size: 10.0)])
highscoreLabel.attributedText = NSMutableAttributedString(string:"\(highscoreShow)").appendAttributedString(ftString)
Related
I want to get an icon from system and show it in Gtk.Image but in a spesific size.
Gtk.Image.icon_size property is an enum, it isn't setting pixel width or height. (Gtk.IconSize)
This is how I get icon from system theme:
var icon = new Gtk.Image.from_icon_name("icon_name", 0);
...
// Something like `icon.set_size(150, 150);` ?
But even it is a .svg file in .icons folder, it is displaying as too small like 16x16.
I want to display it like 150x150.
Thanks!
Set the (poorly named) pixel size
var icon = new Gtk.Image.from_icon_name("icon_name", DIALOG);
// Just use anthing here ^^^^^^^
icon.pixel_size = 150
Here is how I've solved:
// 150 is pixel size:
var icon_theme = IconTheme.get_default();
var pixbuf_icon = icon_theme.load_icon("icon-name", 150, IconLookupFlags.FORCE_SVG);
var final_img = new Image.from_pixbuf(pixbuf_icon);
// Use final_img...
"TRYING OUT AUTO SIZING"
Font size too large, so much so part of the string is cut off (when text is too large, a portion is cut off and replaced with ellipsis).
"test text"
Font size too small. The fontSize is a third of its respective view (estimating).
How to set the fontSize of an Element to the size of the Element (and or containing Views) width? Use the examples above as reference - "test text" would take up much more of the view and "TRYING OUT AUTO SIZING" would take up far less.
NOTE: Black boxes denote other elements.
The desired goal is an element (label and or containing view) that when text is changed at run-time, the fontSize is set according to the text assigned and width available to it (this remains constant after build). So that all text is viewable in the string and it uses the width available to it.
Purpose is to support multiple platforms on a variety of devices with widely different scaling.
Whats been tried? NamedSizes based on the idiom (phone/tablet/etc) and manipulating these with multiplication and division based on OS (platform, i.e., IOS, Android, etC). This can't be best practice and there must be a way to go about accomplish this.
Following Xamarin.Forms guide for "fitting text to available size" or "empirically fitting text" yields results that are not as expected.. "CH5: Dealing with sizes"
Please advise on best practice and/or next steps.
Struct
struct FontCalc
{
public FontCalc(Label label, double fontSize, double containerWidth)
: this()
{
// Save the font size.
FontSize = fontSize;
// Recalculate the Label height.
label.FontSize = fontSize;
SizeRequest sizeRequest =
label.Measure(containerWidth, Double.PositiveInfinity);
// Save that height.
TextHeight = sizeRequest.Request.Height;
}
public double FontSize { private set; get; }
public double TextHeight { private set; get; }
}
Implementation
Label label;
public EmpiricalFontSizePage()
{
label = new Label();
Padding = new Thickness(0, Device.RuntimePlatform == Device.iOS ? 30 : 0, 0, 0);
ContentView contentView = new ContentView
{
Content = label
};
contentView.SizeChanged += OnContentViewSizeChanged;
Content = contentView;
}
void OnContentViewSizeChanged(object sender, EventArgs args)
{
// Get View whose size is changing.
View view = (View)sender;
if (view.Width <= 0 || view.Height <= 0)
return;
label.Text =
"This is text displayed. Does it work?";
// Calculate the height of the rendered text.
FontCalc lowerFontCalc = new FontCalc(label, 10, view.Width);
FontCalc upperFontCalc = new FontCalc(label, 100, view.Width);
while (upperFontCalc.FontSize - lowerFontCalc.FontSize > 1)
{
// Get the average font size of the upper and lower bounds.
double fontSize = (lowerFontCalc.FontSize + upperFontCalc.FontSize) / 2;
// Check the new text height against the container height.
FontCalc newFontCalc = new FontCalc(label, fontSize, view.Width);
if (newFontCalc.TextHeight > view.Height)
{
upperFontCalc = newFontCalc;
}
else
{
lowerFontCalc = newFontCalc;
}
}
// Set the final font size and the text with the embedded value.
label.FontSize = lowerFontCalc.FontSize;
label.Text = label.Text.Replace("??", label.FontSize.ToString("F0"));
}
(implementation code from XF docs linked above)
One solution is to use the NuGet package: Forms9Patch
Using its Lines and AutoFit properties we can achieve our desired result. In our case, we want one line and we want our FontSize set to what is required to make our text fit within one line. Lines = 1, AutoFit = Forms9Patch.AutoFit.Width, and FontSize set to a large value (does not matter if it is well over our desired max as setting AutoFit to Width shrinks font till to what is required to make text fit within our specified number of lines) results in a Label that automatically adjusts its FontSize to the maximum space available to it given the text length.
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 need to get width and height of image with given path.
Here is my detail code
path = UI.openpanel("Open Image File","/","*.jpg;*.png;*.jpeg")
if(path != nil)
#get the original width of image
old_width = ??????
#get the original height of image
old_height = ??????
#get the orginal rate of image
rate = old_width.to_f / old_height_to_f
#then import image into model as a Image entity
point = Geom::Point3d.new 0,10,0
objImage = entities.add_image path, point , 318,318/rate
end
I need a way to get value of old_width and old_height in this code with given path.
Thanks you
You won't need to specify the height, it is optional. If you specify just the width the height will be adjusted automatically based on the proportions of the image.
path = UI.openpanel("Open Image File", "/", "*.jpg;*.png;*.jpeg")
if !path.nil?
point = Geom::Point3d.new(0, 10, 0)
image = entities.add_image(path, point, 318)
end
According to the docs, CTFramesetterSuggestFrameSizeWithConstraints () "determines the frame size needed for a string range".
Unfortunately the size returned by this function is never accurate. Here is what I am doing:
NSAttributedString *string = [[[NSAttributedString alloc] initWithString:#"lorem ipsum" attributes:nil] autorelease];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef) string);
CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0,0), NULL, CGSizeMake(rect.size.width, CGFLOAT_MAX), NULL);
The returned size always has the correct width calculated, however the height is always slightly shorter than what is expected.
Is this the correct way to use this method?
Is there any other way to layout Core Text?
Seems I am not the only one to run into problems with this method. See https://devforums.apple.com/message/181450.
Edit:
I measured the same string with Quartz using sizeWithFont:, supplying the same font to both the attributed string, and to Quartz. Here are the measurements I received:
Core Text: 133.569336 x 16.592285
Quartz: 135.000000 x 31.000000
try this.. seem to work:
+(CGFloat)heightForAttributedString:(NSAttributedString *)attrString forWidth:(CGFloat)inWidth
{
CGFloat H = 0;
// Create the framesetter with the attributed string.
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString( (CFMutableAttributedStringRef) attrString);
CGRect box = CGRectMake(0,0, inWidth, CGFLOAT_MAX);
CFIndex startIndex = 0;
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, box);
// Create a frame for this column and draw it.
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(startIndex, 0), path, NULL);
// Start the next frame at the first character not visible in this frame.
//CFRange frameRange = CTFrameGetVisibleStringRange(frame);
//startIndex += frameRange.length;
CFArrayRef lineArray = CTFrameGetLines(frame);
CFIndex j = 0, lineCount = CFArrayGetCount(lineArray);
CGFloat h, ascent, descent, leading;
for (j=0; j < lineCount; j++)
{
CTLineRef currentLine = (CTLineRef)CFArrayGetValueAtIndex(lineArray, j);
CTLineGetTypographicBounds(currentLine, &ascent, &descent, &leading);
h = ascent + descent + leading;
NSLog(#"%f", h);
H+=h;
}
CFRelease(frame);
CFRelease(path);
CFRelease(framesetter);
return H;
}
For a single line frame, try this:
line = CTLineCreateWithAttributedString((CFAttributedStringRef) string);
CGFloat ascent;
CGFloat descent;
CGFloat width = CTLineGetTypographicBounds(line, &ascent, &descent, NULL);
CGFloat height = ascent+descent;
CGSize textSize = CGSizeMake(width,height);
For multiline frames, you also need to add the line's lead (see a sample code in Core Text Programming Guide)
For some reason, CTFramesetterSuggestFrameSizeWithConstraints() is using the difference in ascent and descent to calculate the height:
CGFloat wrongHeight = ascent-descent;
CGSize textSize = CGSizeMake(width, wrongHeight);
It could be a bug?
I'm having some other problems with the width of the frame; It's worth checking out as it only shows in special cases. See this question for more.
The problem is that you have to apply a paragraph style to the text before you measure it. If you don't then you get the default leading of 0.0. I provided a code sample for how to do this in my answer to a duplicate of this question here https://stackoverflow.com/a/10019378/1313863.
ing.conti's answer but in Swift 4:
var H:CGFloat = 0
// Create the framesetter with the attributed string.
let framesetter = CTFramesetterCreateWithAttributedString(attributedString as! CFMutableAttributedString)
let box:CGRect = CGRect.init(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude)
let startIndex:CFIndex = 0
let path:CGMutablePath = CGMutablePath()
path.addRect(box)
// Create a frame for this column and draw it.
let frame:CTFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(startIndex, 0), path, nil)
// Start the next frame at the first character not visible in this frame.
//CFRange frameRange = CTFrameGetVisibleStringRange(frame);
//startIndex += frameRange.length;
let lineArray:CFArray = CTFrameGetLines(frame)
let lineCount:CFIndex = CFArrayGetCount(lineArray)
var h:CGFloat = 0
var ascent:CGFloat = 0
var descent:CGFloat = 0
var leading:CGFloat = 0
for j in 0..<lineCount {
let currentLine = unsafeBitCast(CFArrayGetValueAtIndex(lineArray, j), to: CTLine.self)
CTLineGetTypographicBounds(currentLine, &ascent, &descent, &leading)
h = ascent + descent + leading;
H+=h;
}
return H;
I did try and keep it as 1:1 with the Objective C code but Swift is not as nice when handling pointers so some changes were required for casting.
I also did some benchmarks comparing this code (and it's ObjC counterpart) to another height methods. As a heads up, I used a HUGE and very complex attributed string as input and also did it on the sim so the times themselves are meaningless however the relative speeds are correct.
Runtime for 1000 iterations (ms) BoundsForRect: 8909.763097763062
Runtime for 1000 iterations (ms) layoutManager: 7727.7010679244995
Runtime for 1000 iterations (ms) CTFramesetterSuggestFrameSizeWithConstraints: 1968.9229726791382
Runtime for 1000 iterations (ms) CTFramesetterCreateFrame ObjC: 1941.6030206680298
Runtime for 1000 iterations (ms) CTFramesetterCreateFrame-Swift: 1912.694974899292
It might seem strange but I found that if you use ceil function first and then add +1 to the height it will always work. Many third party APIs use this trick.
Resurrecting.
When initially determining where lines should be placed within a frame, Core Text seems to massage the ascent+descent for the purposes of line origin calculation. In particular, it seems like 0.2*(ascent+descent) is added to the ascent, and then both the descent and resultant ascent are modified by floor(x + 0.5), and then the baseline positions are calculated based on these adjusted ascents and descents. Both of these steps are affected by certain conditions whose nature I am not sure, and I also already forgot at which point paragraph styles are taken into account, despite only looking into it a few days ago.
I've already resigned to just considering a line to start at its baseline and not trying to figure out what the actual lines land at. Unfortunately, this still does not seem to be enough: paragraph styles are not reflected in CTLineGetTypographicBounds(), and some fonts like Klee that have nonzero leadings wind up crossing the path rect! Not sure what to do about this... probably for another question.
UPDATE
It seems CTLineGetBoundsWithOptions(line, 0) does get the proper line bounds, but not quite fully: there's a gap between lines, and with some fonts (Klee again) the gap is negative and the lines overlap... Not sure what to do about this. :| At least we're slightly closer??
And even then it still does not take paragraph styles into consideration >:|
CTLineGetBoundsWithOptions() is not listed on Apple's documentation site, possibly due to a bug in the current version of their documentation generator. It is a fully documented API, however — you'll find it in the header files and it was discussed at length at WWDC 2012 session 226.
None of the options are relevant to us: they reduce the bounds rect by taking certain font design choices into consideration (or increase the bounds rect randomly, in the case of the new kCTLineBoundsIncludeLanguageExtents). One useful option in general, though, is kCTLineBoundsUseGlyphPathBounds, which is equivalent to CTLineGetImageBounds() but without needing to specify a CGContext (and thus without being subject to an existing text matrix or CTM).
After weeks of trying everything, any combination possible, I made a break through and found something that works. This issue seems to be more prominent on macOS than on iOS, but still appears on both.
What worked for me was to use a CATextLayer instead of a NSTextField (on macOS) or a UILabel (on iOS).
And using boundingRect(with:options:context:) instead of CTFramesetterSuggestFrameSizeWithConstraints. Even though in theory the latter should be more lower level than the former, and I was assuming would be more precise, the game changer turns out to be NSString.DrawingOptions.usesDeviceMetrics.
The frame size suggested fits like a charm.
Example:
let attributedString = NSAttributedString(string: "my string")
let maxWidth = CGFloat(300)
let size = attributedString.boundingRect(
with: .init(width: maxWidth,
height: .greatestFiniteMagnitude),
options: [
.usesFontLeading,
.usesLineFragmentOrigin,
.usesDeviceMetrics])
let textLayer = CATextLayer()
textLayer.frame = .init(origin: .zero, size: size)
textLayer.contentsScale = 2 // for retina
textLayer.isWrapped = true // for multiple lines
textLayer.string = attributedString
Then you can add the CATextLayer to any NSView/UIView.
macOS
let view = NSView()
view.wantsLayer = true
view.layer?.addSublayer(textLayer)
iOS
let view = UIView()
view.layer.addSublayer(textLayer)