Fonts lineHeight equalization in UITextView - ios

I have a UITextView set with some text with both chinese and latin characters.
As you can see, the lineSpace is different depending if there is a latin character or not in the line above. Obviously, I need to remove this difference and to equalize the lineSpace.
Here are the statements :
I work with an AttributedString
The font is different depending if the character is latin or chinese
The point above needs to stay true
The lineHeight is different between the 2 fonts
I almost sure than the reason of my problem comes from this last point but since the lineHeight of the font is not editable, I'm a bit stuck.
I already try to play with those properties
style.minimumLineHeight = mylineSpacing;
style.maximumLineHeight = mylineSpacing;
style.lineHeightMultiple = mylineSpacing;
style.lineSpacing = 0;
NSDictionary *attributes = #{
NSParagraphStyleAttributeName : style
};
It doesn't work. I'm thinking about using CoreText or even CoreGraphic to redraw all the characters one by one but maybe there is an easier solution. And if not, which Kit should I use ?
PS : I'm not chinese and I used random chinese text for this screenshot and I apologize to an eventual chinese stackoverflow user if my text looks stupid / no sense / or offending in any way.

Related

How to use font to write musical sheet

I am trying to make an iOS app that can make possible to write and edit musical sheet.
I was wondering how can I visualise it.
I did not found any complete framework in swift that can make it possible.
But I found this font bravura, but i did not found any example of use.
This is an OpenType Font.
Reading the documentation I should be able to decide through unicode characters, how to change the position of the next element.
But no it doesn't seem to work. At first I thought it was because of the ligatures as not all editors support it, I'm not a font expert and I could be wrong.
So I used the NSAttributedString class to modify these parameters. But the result is still not what is expected.
This is my code :
textView.font = UIFont(name: "Bravura-Text", size: 40)
let text = "\u{E014}\u{EB99}\u{E0A4} \u{E014}\u{EB91}\u{E0A4}"
let attributes = [
NSAttributedString.Key.font : UIFont(name: "Bravura-Text", size: 40)!,
NSAttributedString.Key.ligature : 2
] as [NSAttributedString.Key : Any]
let attributedString = NSAttributedString(string: text, attributes: attributes)
textView.attributedText = attributedString
E014 = Displaying a 5 line staff
EB99 = Lower by two staff positions
EB91 = Raise by two staff positions
E0A4 = The black notehead
This is the result :
result
Any suggestions to make it possible to use this font and its features on iOS?
I would like to write a musical sheet like this one.
Layout of music notation require higher-level layout rather than just displaying a line of text. (Analogous to Web pages using HTML as high-level layout rather than just lines of text.) Fonts can provide the basic symbols, and all of the basic symbols are encoded in Unicode. But you'll need some library that can read a music-notation markup language and layout the content.
You can probably find the kind of resources you'll need at https://www.w3.org/community/music-notation/

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.

Swift - iOS 8 - NSKernAttributeName character spacing problems

Part of my application facilitates the drawing of stored string values to a graphics context to be viewed by the user. This is being done with an attributed string with the below Attributes.
//adding the attributes to the NSAttributedString
let textStyle = NSMutableParagraphStyle.defaultParagraphStyle().mutableCopy() as NSMutableParagraphStyle
let textColor = UIColor.blackColor()
let boxTextFontAttributes = [
NSFontAttributeName : font!,
NSForegroundColorAttributeName: textColor,
NSParagraphStyleAttributeName: textStyle,
NSKernAttributeName: (7.62),
]
I'm seeing a problem with the NSKernAttributeName character spacing on the output.
I have 2 Strings, both with the same CGRect dimensions (but different y-positions (one below the other)), both upper case, same font & size.
Whenever it comes to a 'thinner' character in one of the strings, such as an "I", it looks to impact the spacing of the following characters.
I basically need to have each character in both strings to be vertically in-line to the string above/below regardless of what character it is? I can only assume that even though it may have a set spacing between the characters, each character still has an individual 'width' of sorts that impacts the rest. Is there a way to standardise this?
Any help would be appreciated - (apologies for not being able to post a picture due to lack of reputation points)
A monospaced font corrected the issue of the character alignment. Some example monospaced fonts available in iOS 8 are Courier & Menlo. Useful post: What is a monospace font in iOS?

iOS: Better justification with CoreText

I'm floating some text around an image using CoreText and kCTJustifiedTextAlignment to show the text justiefied - but with larger images, smaller text-areas and less whitespaces CoreText does not only widen whitespaces but also the margin between the letters.
See here:
This sometimes looks really awful, so I searched for some alternatives or workarounds but only found the advice to do it myself by adding more whitespaces after each whitespace to match the width and using kCTLeftTextAlignment.
It sounds like a lot of snares to deal with, so I thought, I'd ask here, maybe anyone has an idea how to deal with that issue.
The basic question here is "so what do you want?" The other obvious answer is "full justify unless... some rules I'm going to make up like if there's only one word."
If you want that kind of control, then you need to drop down to the CTLine level and create justified lines only when you want them. Assuming you already know a bit about CoreText, this code should hopefully make sense. It justifies the line only if it isn't the last line of the paragraph.
CFIndex lineCharacterCount = CTTypesetterSuggestLineBreak(self.typesetter, startIndex, boundsWidth);
CTLineRef line = CTTypesetterCreateLine(self.typesetter, CFRangeMake(startIndex, lineCharacterCount));
// Fetch the typographic bounds
CTLineGetTypographicBounds(line, &(*ascent), &(*descent), &(*leading));
// Full-justify all but last line of paragraphs
NSString *string = self.attributedString.string;
NSUInteger endingLocation = startIndex + lineCharacterCount;
if (endingLocation >= string.length || [string characterAtIndex:endingLocation] != '\n') {
CTLineRef justifiedLine = CTLineCreateJustifiedLine(line, 1.0, boundsWidth);
CFRelease(line);
line = justifiedLine;
}
So we create a normal CTLine based on the CTTypesetter suggestion. Then we apply some rule (only one word? Not the end of the paragraph? Whatever.) If we pass, then we create a new, justified line. (I'm not certain why CTTypesetter can't create a justified line itself.)
For a full example of this, see PinchText. It's much more complicated than you need, but it shows how to do all the layout with lots of comments.

Line spacing in multi-language layout with Core Text

The attributed string has only one attribute - 17 point Helvetica NeueUI font - covering the whole string. Line 1~3 are purely English, line 4~6 are mixtures of English and Chinese, line 7~8 are purely Chinese.
It is then layouted with CTFramesetter and resultant frame is drawn with CTFrameDraw.
// UIView subclass
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor whiteColor];
CTFontRef font = CTFontCreateWithName(CFSTR("Helvetica NeueUI"), 17.f, NULL);
_text = [[NSAttributedString alloc] initWithString:string attributes:
[NSDictionary dictionaryWithObject:(id)font forKey:(id)kCTFontAttributeName]];
CFRelease(font);
_framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)_text);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.bounds);
_frame = CTFramesetterCreateFrame(_framesetter, CFRangeMake(0, 0), path, NULL);
CGPathRelease(path);
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
// Flip the coordinate system.
CGContextTranslateCTM(context, 0.f, self.bounds.size.height);
CGContextScaleCTM(context, 1.f, -1.f);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CTFrameDraw(_frame, context);
CGContextRestoreGState(context);
}
The problem is that the space between line 7 and 8 (purely Chinese lines) is much smaller than others.
For contrast, I put a UILabel below it, with the same font and text. Ignoring the rendering difference of single characters, you can see the line spaces in UILabel are uniform including the last one between two Chinese lines.
(source: skitch.com)
Notice: this results above is got on a real device. If you run the same code on the simulator, you'll get very different result for the Core Text view — spaces between lines containing Chinese are much larger than others.
Why is the space between Chinese lines smaller(larger)?
How can I layout multi-language text with uniform line height using Core Text?
PS: here is the sample project you can try yourself.
Similarly to the space between each letter in European languages, Chinese writing uses a narrow space between characters, though it does not observe the equivalent to the wider space between words, except in rare occasions. (In this respect, it may somewhat resemble a form of scriptio continua.)
When space is used, it is also fullwidth. One instance of its usage is as an honorific marker. A modern example in Taiwan, is found in the reference to Chiang Kai-shek as 先總統 蔣公 (Former President, Lord Chiang), in which the preceding space serves as an honorific marker for 蔣公. This use is also still current in very formal letters or other old-style documents.
When Chinese is written in transliterated form (as in Hanyu Pinyin), spaces are used to assist in reading. source:http://en.wikipedia.org/wiki/Chinese_punctuation
So its because in chinese as space is used a bit differently than in english.
The reason for the space is depending on the characters, as it could be found when querying parts of the text for its dimension, but this does not declare why a label simply ignores this problem.
However the space is in fact introduced by the FrameSetter in order to achieve even space you could use the FrameSetter to get the line breaks and use a TypeSetter (CTTypesetterCreateWithAttributedString) to produce the lines and point them at a desired line distance, as shown in the Manual Line Break from Apple. This also works for your given sample, as i have briefly checked.
Anyway I could only agree that the behaviour of the label is somehow odd, but not this bad as it looks better.
I finally got an official answer from Apple engineer:
CTLine metrics are based on the maximum of each of {ascent, descent,
leading}; this has not changed, nor will it. Clients may override the
line metrics using paragraph style specifiers or iOS 7 text styles,
for example [UIFont preferredFontForTextStyle:UIFontTextStyleBody].

Resources