Cocos2d -> Swift SpriteKit - CCLabelAtlas charmap functionality - ios

Firstly, I apologise if this has been asked before.. I'm still very finding my feet with coding and im not entirely sure I'd be looking at the right answer if it was right in front me, hence me giving in and posting here for help!
A few years ago, I stuck my feet into cocos2d for a while and there was a very useful way of adding your own fonts to labels and I wondered if the same thing could be done in sprite kit?
Looking here, this is how it would be setup in my cocos2d project from years ago:
NSString *myString = [NSString stringWithFormat:#"Hello World",;
myLabelAtlas = [CCLabelAtlas labelWithString:myString charMapFile:#"My_Font.png" itemWidth:16/2 itemHeight:24/2 startCharMap:'.'];
I'm trying to repeat this process in swift with Sprite Kit, using the same 'My_Font.png' file 1
Is this even possible??
(Many thanks in advance and sorry again for the n00b question!)

You are looking for a SKLabelNode to add a label to a scene. The link Wang Yudong gave you here shows you how to add your fonts to your project so that you can get them as a UIFont. At the top of your GameScene.swift you want to put this code (class given so you know where I'm talking about):
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var label = SKLabelNode(text: "Hello World")
Now the SKLabelNode takes a String! as the font name to use. So we do something like this in didMoveToView (replace <your font name> with your font's name):
label.fontName = "<your font name>" // The font's name
label.fontSize = 18 // The size of your font
label.fontColor = UIColor.blackColor() // The color to use for your font
self.addChild(label) // Add the label to the scene
Keep in mind that you need the font's name after it is in the system. Sometimes font names aren't what they appear to be. To scan through all of the font names (and find the name to use for your font), use this code (this is from the link, just in case the link goes away at some point):
for family: String in UIFont.familyNames()
{
print("\(family)")
for names: String in UIFont.fontNamesForFamilyName(family)
{
print("== \(names)")
}
}
I'm not quite sure how to use a .png for your font (I also don't know if your are still using that font). There may be an online generator somewhere that converts .png font's into a more common format. But once you have it converted (into something like .otf or .ttf), you can drop your font into your project (on the left side where your files are listed). From there, you add it to a list of fonts provided by the application in the info.plist file (should be buried in Supporting Files).
Now your application should have your font in, and have a SKLabelNode. If you need to, you can set the text of it later on.

Related

iOS UILabel and avoiding clipping of diacritics with custom font

First of all, there are many questions on StackOverflow, but none that fully answer this question.
The problem is mainly, but most likely not limited to, Thai and Arabic diacritics when rendered with a custom Latin-only font, using the text property of a UILabel. Which is also intrinsically sized in an auto-layout. I've already done everything Apple suggests, playing with the settings mentioned in their documentation, WWDC videos, as well as questions on StackOverflow (e.g. clipsToBounds = NO, etc.). Keep in mind, only my custom font setup clips in my scenario, not the iOS system font (.SF-UIDisplay), and not even the iOS system provided Helvetica or Helvetic Neue. The custom font has been checked and rechecked, and at this point the conclusion, iOS is the anomaly across all platforms, even macOS. To be even clearer, the same clipping behavior as the custom font can be seen with SF Pro, a font provided by Apple themselves here: https://developer.apple.com/fonts/
This question is about the most proper, least intrusive, and most complete way to do what is necessary to not clip diacritics. Meaning, how would you do this, ideally, from scratch.
All of my font research and test runs have led all those involved in this problem to believe that Apple has implemented special treatment specifically for their system fonts in UILabel, to avoid diacritic clipping. So making that an assumption, I'm also assuming the font is ok, and I'm looking for solutions that do not involve editing the font.
In my tries to use the font, the first thing to go wrong was vertical clipping of the ascender diacritics of Thai glyphs:
นื้ทั้มูHello
This means the glyphs of the font Thonburi when they cascade from the custom Latin-only font. The fix from this point, was to use a custom font only for Thai without any Latin characters, so it could be defined as the primary font, and cascade to the previously mentioned Latin-only custom font. After all this, the custom Thai font still has horizontal clipping issues on diacritics that come at the end of the text:
Worldฟล์
So now I am at a loss for anything further that font management puppetry can do (though still open to suggestions), and I am moving on to more code-centric fixes. I've seen quite a few questions and answers mentioning subclassing UILabel, but I'd like to know what this would look like that could accomplish what I've described.
I'd also like to know if just opting out of UILabel would be an option for anyone. Meaning would writing something from the ground up with TextKit be worth it to avoid all these bugs that seem to only plague iOS, and specifically UILabel.
At first I thought this was a problem with the framework but it's not, it's just a strict enforcement of a font's metrics. And in probably everything but web/app development, fonts are not rendered so strictly, which is why this problem rarely comes up. Fonts have a number of metrics that tell the program rendering it onto the screen how to render it, most importantly how to handle padding. And UILabel (and UITextField, and likely others) applies these metrics strictly. And the problem for us is that some fonts are very decorative and are often too thick or oblique to fit perfectly into the square canvas that each character must fit into (this is especially the case with accents, like umlauts). This isn't a problem outside of web/app development because when a character doesn't fit into its canvas, like a very thick, wide, and oblique W, the program just shows it anyway, and that's why a low-hanging g might spill into the line below it. But if that g was rendered in a single-line UILabel, because of how strict the font-metric enforcement is in iOS, that low-handing g is clipped.
Subclassing UILabel (in the case of UILabel) and overriding its intrinsicContentSize to add some extra padding is not a good idea, on further research. For one, it's kind of hacky, but more importantly, it produces constraint warnings in the debugger. The true fix, and the only acceptable fix AFAIK, is to edit the font's metrics.
Download a program like Glyphs (https://glyphsapp.com/), open the font, open the Font's Info, and in the Masters tab, give the font the proper ascender and descender values. To best understand how these values work, open the San Francisco font in the program and see how Apple did it (it's the font they made specifically for macOS and iOS development). As a side note, if you use this app, when you're editing the font's info, go into the Features tab as well, delete all of the features (using the minus icon in the lower left) and hit Update to let the program manage the font's features for you.
The last hurdle is clipping at the leading edge (not the top and bottom) which the ascender and descender metrics don't address. You can use the Glyphs program to edit the canvas size of individual characters to make sure they all fit but that changes the complexion of the font because it changes the character spacing too noticeably. For this problem, I think the best solution is to simply use attributed strings for your labels and text fields. And that's because attributed strings let you safely edit padding without hacking into intrinsic sizes. An example:
someLabel.attributedText = NSAttributedString(string: "Done", attributes: [NSAttributedString.Key.font: UIFont.blackItalic(size: 26), NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle.kItalicCenter])
For convenience, I extended NSMutableParagraphStyle since I use this font all over:
extension NSMutableParagraphStyle {
static var kItalicCenter: NSMutableParagraphStyle {
let s = NSMutableParagraphStyle()
s.alignment = .center
s.firstLineHeadIndent = 2
s.headIndent = 2
return s
}
}
This label will push the font forward a couple of points to prevent clipping.
I was trying to solve similar problem with diacritics in Arabic and found workaround:
I have a UITableViewCell with UILabel with arabic text, it's diacritics were cut sometimes
I overrided - (void)drawRect:(CGRect)frame to directly draw NSAttributedString on UITableViewCell
Also I decreased alpha self.arabicLabel.alpha = 0.1; to draw manually on top of label position, I still keep it to calculate cell's height
- (void)drawRect:(CGRect)frame {
[super drawRect:frame];
if (self.viewModel == nil) return;
NSAttributedString *string = [self.viewModel arabicStringWithTajweed];
CGRect originalRect = [self convertRect:self.arabicLabel.frame fromView:self.arabicLabel];
[string drawInRect:originalRect];
}
The core problem on iOS is font substitution. You are specifying a latin font, the font does not contain glyphs for the characters that will be rendered, the system uses a different font to draw the glyphs, but it is still measuring based on the original font.
Option 1, the most robust option, is to manually choose fonts that include glyphs for the characters you will render. When the font assigned to UILabel, or the attributed string it is rendering, contains all the glyphs that will be rendered, and that font has good metrics as most system fonts do, then nothing will be clipped.
Option 2, manually measure the string using glyph bounds. Make a subclass of UILabel and override textRectForBounds and possibly drawText. Measure the string with .usesDeviceMetrics. This is slower that measuring by font metrics and produces different results. For example, the strings "a" and "A" will measure differently.
Option 3, use baseline offset and line height multiple to make room for the diacritics that are being clipped. Choose or compute constant values for each font for each language, and apply those to the attributed string of the UILabel. This can compensate for the different in font metrics between the font you chose and the font that is actually rendering glyphs. We had localized strings with the worst case clipped characters for each language, and used those to compute the offset and height. Different fonts have different worst case clipping characters.

Optimizing SKLabelNode to reduce performance issues

Creating an RPG with SpriteKit has me creating SKLabelNode's for displaying all game text. I'm suffering performance issues (FPS dropping & the game lagging) every time my multi-line text box begins auto typing inputted text which uses SKLabelNode's. I also see performance hits when "summoning" UI elements, such as character stat panels (with lots of SKLabelNode's for skill levels, skills names, etc.).
Veteran developers say pre-loading data always helps, thus I did the following (which has reduced the performance hit but hasn't eliminated it & the text box still lags):
// Preloaded model label (private property that is initialized in the class init method).
_modelLabel = [[SKLabelNode alloc] initWithFontNamed:_fontName];
_modelLabel.fontColor = _fontColor;
_modelLabel.fontSize = _fontSize;
_modelLabel.text = #"T";
// Constantly created label.
SKLabelNode *rowText = [[SKLabelNode alloc] initWithFontNamed:self.modelLabel.fontName];
rowText.fontColor = self.modelLabel.fontColor;
rowText.fontSize = self.modelLabel.fontSize;
rowText.text = row;
...
[self addChild:rowText];
I would be grateful for getting some more optimizations tricks when using SKLabelNode's.
Here are the tips I have collected thus far:
Preloading is only needed when using a font that is not available via iOS.
To get the font to actually load you need to also set the text for the preload to work.
Try to preload SKLabelNode with all chars you need. Maybe, SpriteKit renders only required glyphs into texture.
// Preloaded model label (private property that is initialized in the class init method).
_modelLabel = [[SKLabelNode alloc] initWithFontNamed:_fontName];
_modelLabel.fontColor = _fontColor;
_modelLabel.fontSize = _fontSize;
_modelLabel.text = #"ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz0123456789-+=_()";
And then add your model into scene with minimal alpha (but no zero, because if node has 0 alpha, it will not drawn); Try to keep that node in scene
_modelLabel.alpha = CGFLOAT_MIN;
[self addChild:_modelLabel];
If not helps:
Organize nodes by aligning their zPosition (aka z-index). You should disable ignoresSiblingOrder for having control over zPosition.
Make your all SKLabelNode be drawn at one z level.
Switch to bitmap fonts. Writing custom bitmap font renderer is super easy. The only thing you should remember - Make all glyphs be in only texture atlas, so you can draw your text in one draw call
Use native labels. I'm not sure, how much does it help. If you're having heavy FPS drops (~5..10FPS) This may help you
It seems to me you are creating new labels every time you need text. If I were you only create the label once, and just alter the text when needed as well as just hide the lines you are not using.
Another thing you can do is fill the text in on your label one time, then assign it to an SKCropNode. Then you can use the SKCropNode to bring the font characters back in, which in turn should give you a nicer scrolling effect since you are not doing it at one character at a time.

Custom font causes delay in SpriteKit

I tried to change a label font to bodoni 72 but every time I test the app, it freezes for about 2 seconds before it starts. The default font is verdana and that works fine but as soon as I switch it, the app goes nuts. I even put the font file in the supporting files folder.
scoreLabel.fontName = "Bodoni 72"
That's the code but doesn't work. But the code below does...
scoreLabel.fontName = "Verdana"
If you use the incorrect font name in SpriteKit it causes a delay while it tries to find a match.
In this case "Bodoni 72" is the font family name, but it isn't the font name. If you open the Font Book application on your Mac and have a look at Bodoni 72 you will see that you can expand it to see the three actual fonts in this family - 'Book', 'Book italic' and 'Bold'.
If you change your code to read
scoreLabel.fontName = "Bodoni 72 Book"
then the delay will be gone.
That's not an issue with Xcode or Swift. That's an issue in your code. Instead of that, try setting the fontName of your label to "Bodoni", and your fontSize (the size of your font as a float) to 72.0.
See below:
scoreLabel.fontName = "Bodoni"
scoreLabel.fontSize = 72
If you want to set the color of a label (this might only be in SpriteKit's SKLabelNodes), you can use this:
scoreLabel.fontColor = UIColor.whiteColor //replace with your color

Custom installed font not displayed correctly in UILabel

I'm trying to use a Helvetica Neue Condensed font which I got from the Adobe Font Collection Pro Package. Unfortunately, it seems to draw incorrectly when I use it within a UILabel.
The line height seems to be calculated correctly (I think), but when the font is displayed, it is aligned to the very top of the bounding box. I called [myLabel sizeToFit] and only adjusted the width to produce this screen capture:
I had the same problem with both the bold and regular version of the font. I was able to pull a version of Helvetica Neue Bold from OSX and put it on my device and it displays fine (green background in above picture).
What could be wrong with the either the font file or my code that would cause it to draw this way?
I posted a solution that involves patching ttf font file here:
Here's the solution that worked for my custom font which had the same issue in UILabel, UIButton and such. The problem with the font turned out to be the fact that its ascender property was too small compared to the value of system fonts. Ascender is a vertical whitespace above font's characters. To fix your font you will have to download Apple Font Tool Suite command line utilities. Then take your font and do the following:
~$ ftxdumperfuser -t hhea -A d Bold.ttf
This will create Bold.hhea.xml. Open it with a text editor and increase the value of ascender attribute. You will have to experiment a little to find out the exact value that works best for you. In my case I changed it from 750 to 1200. Then run the utility again with the following command line to merge your changes back into the ttf file:
~$ ftxdumperfuser -t hhea -A f Bold.ttf
Then just use the resulting ttf font in your app.
So this is a modified version of kolyuchiy's answer.
I opened my font with Glyphs, and then exported it without modifying anything. Somehow, magically, the vertical alignment issue was gone!
What's better is that the new font plays nicely with methods like sizeWithFont:, so it doesn't have the issues mentioned by Joshua.
I took a look at the HHEA table with the command kolyuchiy mentioned, and noticed that Glyphs modified not just the ascender, but also lineGap and numberOfHMetrics for me.
Here's the raw data, before:
versionMajor="1"
versionMinor="0"
ascender="780"
descender="-220"
lineGap="200"
advanceWidthMax="1371"
minLeftSideBearing="-73"
minRightSideBearing="-52"
xMaxExtent="1343"
caretSlopeRise="1"
caretSlopeRun="0"
caretOffset="0"
metricDataFormat="0"
numberOfHMetrics="751"
and after:
versionMajor="1"
versionMinor="0"
ascender="980"
descender="-220"
lineGap="0"
advanceWidthMax="1371"
minLeftSideBearing="-73"
minRightSideBearing="-52"
xMaxExtent="1343"
caretSlopeRise="1"
caretSlopeRun="0"
caretOffset="0"
metricDataFormat="0"
numberOfHMetrics="748"
So the moral of the story- don't just increase the ascender, but modify other related values as well.
I'm no typography expert so I can't really explain the why and how. If anyone can provide a better explanation it'd be greatly appreciated! :)
iOS 6 honors the font's lineGap property, while iOS 7 ignores it. So only custom fonts with a line gap of 0 will work correctly across both operating systems.
The solution is to make the lineGap 0 and make the ascender correspondingly larger. Per the answer above, one solution is to import and export from Glyphs. However, note that a future version of the app might fix this "bug".
A more robust solution is to edit the font yourself, per this post. Specifically,
Install OS X Font Tools.
Dump the font metrics to a file: ftxdumperfuser -t hhea -A d YOUR_FONT.ttf
Open the dumped file in an editor.
Edit the ascender property by adding the value of the lineGap property to it. For example, if the lineGap is 200 and the ascender is 750, make the ascender 950.
Set the lineGap to 0.
Merge the changes into the font: ftxdumperfuser -t hhea -A f YOUR_FONT.ttf
Once you do this, you might have to adjust your UI accordingly.
For those running OS X El Capitan and coming to this thread, you might have noticed that the Apple Font Tool Suite is no longer compatible (at least for now).
But you can still perform the changes described by kolyuchiy and Joseph Lin with free font editing software FontForge.
Open the font with FontForge and select Element in the top menu, then go to Font Info > OS/2 > Metrics. There you want to edit the HHEad Line Gap and HHead Ascent Offset values.
Once you've done the necessary edits you can just export the font in File > Generate Fonts and select the right font format
Download and Install Apple's Font Tools here: https://developer.apple.com/downloads/index.action?q=font (the download link is in the bottom)
Open the terminal and cd your way to where your font is
Run this command: ftxdumperfuser -t hhea -A d MY_FONT_NAME.ttf
Now you have an xml file with some of the font's properties, edit it in your text editor
Search for the "lineGap" property and add 200 to its value
Save the xml file
Run this command: ftxdumperfuser -t hhea -A f MY_FONT_NAME.ttf
Delete the xml file
Try the configured font on iOS 6 and see if it looks better.
If you need, you can go back to step 3 and add/subtract to the "lineGap" property. (I ended up adding 250 to my configuration)
We had the same issue with one of our custom fonts. We also "fixed" the problem by editing the font ascender property. However, we found that this created other problems and layout issues. For example dynamically setting cell height based on label height would blow up when using our ascender edited font.
What we ended up doing was changing the UIButton contentEdgetInsets property.
yourButton.contentEdgeInsets = UIEdgeInsetsMake(-10, 0, 0, 0);
Not sure which method is better, but just wanted to share another way to fix the problem.
Thanks to the this answer I fixed my problem with Glyphs, but a little bit differently.
I opened my font with Glyphs (also works with Glyphs mini) and found this section there (this from Glyphs mini, to get there push i button in the right top corner):
Just delete all of this alignment zones (or some of them) and it will fix this problem.
Worked perfectly for me.
Creating attributed text from your labels text was the fix for me. Heres an extension:
extension UILabel {
/// You can call with or without param values; without will use default 2.0
func setLineSpacing(lineSpacing: CGFloat = 2.0, lineHeightMultiple: CGFloat = 2.0) {
guard let labelText = self.text else { return }
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpacing
paragraphStyle.lineHeightMultiple = lineHeightMultiple
let attributedString:NSMutableAttributedString
if let labelattributedText = self.attributedText {
attributedString = NSMutableAttributedString(attributedString: labelattributedText)
} else {
attributedString = NSMutableAttributedString(string: labelText)
}
// (Swift 4.2 and above) Line spacing attribute
attributedString.addAttribute(NSAttributedString.Key.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
self.attributedText = attributedString
}
}
For my custom font I got the result I need from:
self.myLabel.setLineSpacing(lineSpacing: 1.2, lineHeightMultiple: 1.2)
This works by using the native provided NSMutableParagraphStyle() which contains line height and spacing properties (which are accessible as #IBOutlet properties in the Storyboard too if you are not programming your labels).
Have you tried Core Text? I've had some success rendering custom fonts through Core Text, but I don't know if it would fit your situation.
I used https://github.com/fonttools/fonttools - very easy to use and free. In my case, the change of 'ascender'=1000 and 'lineGap'=0 in 'hhea' table did the trick.
Based on article from Trevor Harmon https://medium.com/#thetrevorharmon/how-to-use-apples-font-tools-to-tweak-a-font-a386600255ae
If your are having trouble with these command line utilities then try fontcreator on window. and change font assender from its setting menu.
For anyone who are struggling to use ftxdumperfuser (kolyuchiy answer) on Mac OS Mojave because of command not found error:
Download the font tools package from Apple. Found them at
https://developer.apple.com/download/more/?q=font, picked the one for
XCode 11.
Mount the dmg file
Enter the disk image cd /Volumes/macOS\
Font\ Tools
Extract the package to a folder of your choosing: pkgutil
--expand-full macOS\ Font\ Tools.pkg ~/font-tools
The CLI tools are now available in ~/font-tools/FontCommandLineTools.pkg/Payload, you
may add the folder to your path (export PATH="$PATH:$HOME/font-tools/FontCommandLineTools.pkg/Payload"), or copy the utils to your bin
folder.
I had a similar issue with iconic "FontAwesome" font in my Sprite Kit game.
Setting the SKLabelNode's SKLabelVerticalAlignmentMode property to .Center worked for me.
myLabel.verticalAlignmentMode = SKLabelVerticalAlignmentMode.Center
Just wanted to share in case somebody would be struggling with the same problem.

Font wont export out

I can get sIFR to work but it will only display the bold version of the font I have exported. I checked the report and it seems to be stripping out any font weight that is not bolded in flash. I exported two different files to test, the first with regular and bold and the second with medium and semibold in both cases only the bolded weights were exported. Any ideas on how to fix this?
When exporting the Flash movie, make sure at least one character in the text field has the styles applied to it. Then it should export fine.
I had a similar problem - could not make the font 'bold' style work, and I even whanted my fonts BlackItalic to work. Messing around and reading your post and Marks answer made me try to apply the BlackItalic style to the very first word in the provided fla file. Keeping the css style 'font-weight: normal;' in the config java code was successful!
Until I read some new posts about this I will follow the work flow making individual swf files for every font style - normally I only want to use one or to styles anyway.

Resources