This may just be related to Desktop & macOS (Sequoia).
Some fonts will be returned by System.FontAt(pos) that are not useable, such as “Al Banyan” in my case. These will be converted to Helvetica.
This happens in Xojo StyledTextAreas as well as Apple’s TextEdit.
Using “Show Fonts” after right-clicking in a Xojo StyledTextArea will give the options for All Fonts and English Fonts. The problem fonts do not appear in the English Fonts list, which I imagine, is a list localized for the computer.
Is there a way to access these special lists to avoid unwanted fonts?
Thanks
(A demo of the behavior is attached) SRTest.zip (5.9 KB)
I assumed there would be some OS-level support for doing what you’re describing – only show fonts that support a particular language. There doesn’t appear to be such a convenience, at least on macOS.
What’s actually happening here is font substitution. When a requested glyph isn’t found in the selected font, the OS automatically finds a font that can supply the character and uses it just for the missing characters. For fonts that are intended for non-Latin languages, a lot of characters are going to be substituted just to display most Western texts.
The only helpful thing I found is to use a Cocoa call to determine whether a font has a particular set of characters. You’ll have to dig pretty deep into Declares to make it work in Xojo:
Thanks. You got me looking at the Apple Developer docs and I saw this too: showFonts | Apple Developer Documentation. However, how to employ this kind of information is beyond me at this point.
I could not find it in the Xojo docs, but was hoping Xojo provided more information about a font than its name or provided access to the lists displayed in the Font Picker called from the DesktopTextArea’s pop-up menu.
It’s also worth mentioning that this same information is also available via the macOSLib repo where it uses Declares to access the NSFontManager in much the same manner as the MBS example.
Note: The class and method names in the macOSLib repo are mostly the same as the MBS ones, except of course without the MBS suffix.
That’s close. “com.Apple.UseFonts” is an alias for the primary language (Settings > General > Language & Region). So if the user’s primary language is Russian, for example, that font collection will list fonts that support that language.
When you add more languages to the operating system, you’ll see them in the Font Panel, but there doesn’t seem to be any way to access them via the NSFontCollection system.
I would like to provide a font menu for DesktopTextAreas that doesn’t include unusable fonts. These revert to Helvetica on the screen, but the StyleRuns still contain the unusable font name. That should have been reason enough to remove them from the menu, but I ignored the problem since other Mac apps seem to ignore it too.
After introducing some additional processing on the style runs, more problems started to appear. For instance, in StyledText formatted with Al Banyan, the spaces would remain Al Banyan while the other characters converted to Helvetica. This introduces an additional StyleRun for each space and word. Since my code doesn’t do any conversions like this (intentionally), I thought it woUld be easier to just avoid the offending fonts.
In your response, I don’t really see “a user complained” or “processing takes too much time”, etc. In other words - are you just concerned about the complexity of the StyleRuns, or is there some other side effect is problematic?
Yes. This would affect a user’s experience. There is another menu populated with the font names, font sizes, and text colors. When a user selects a font name for instance, the selection jumps to the next occurrence of that font.
This is done by processing StyleRuns and it works fine for some fonts, but problems appear when using the fonts that the OS does not display as they were designed to be displayed. It looked like something outside of my code was affecting this. The demo project makes me think maybe that these fonts are not just being displayed by the OS as Helvetica, but are being defined as Helvetica somewhere and I am using a Xojo command that is dependent on that somewhere’s definiton.
It’s that side effect I’d like to avoid by avoiding the fonts.
I see. I apologize; I didn’t notice that your first post contained a sample project.
I played around with your sample project and it does effectively demonstrate that StyledText/StyleRuns do not preserve the font you assign if that font does not support the characters being displayed. As you noted, it irreversibly translates the font name to whatever substitute font the OS has decided on.
It appears that this behavior is due to how Cocoa’s styled text processing operates, because TextEdit and macOSLib’s TextArea Example both behave the same way. So this is not likely to be a Xojo bug in how StyledText/StyleRuns are handled.
I think you have three paths forward:
Put together a system that only lets the user pick a font you approve. This is achievable for a font menu that your app builds and displays – however, I see no way for you to control macOS’s Font Panel, so this may not be practical or sufficiently protective.
Process the style runs so that a space that is a different font from the non-space characters surrounding it is modified to share their font.
Just not worry about this, as it sounds like an edge case. Wait for user complaints to manifest, if they ever do.
I think I’ll settle on option #3 for now, look into Scott’s suggestion, and submit a feature request for System.FontAtInfo(index As Integer) As String (JSON String) or As Dictionary.
Another factor: a user might paste text copied from another source that contains this kind of oddball text.
Option 2 might be worth looking at, since it is more of a defensive approach rather than a proactive approach (which I don’t think could ever be comprehensive).
If this is for macOS only I have a method that uses API calls to identify what scripts are supported by a given font. That could be adapted to check if characters you require are supported by the font and exclude it if not. Here is an example font selector dialog I put together that shows the supported scripts for the selected font, In the example KufiStandardGK does not support the normal western language script and and thus may be one you don’t want to include. As you can see from the second example a some fonts support a lot of scripts.
The following method uses it to test each known script against the font and builds a string to be used in the lower preview:
#Pragma Unused areas
If g <> Nil Then
g.DrawingColor = App.GridBackgroundColor
g.FillRectangle( 0, 0, Width, Height )
g.DrawingColor = New ColorGroup( Color.DarkBevelColor, &c414141 )
g.DrawRectangle( 0, 0, g.Width, g.Height )
If popFontVariant.SelectedRowIndex <> -1 Then
g.DrawingColor = Color.TextColor
g.FontName = popFontVariant.RowTagAt( popFontVariant.SelectedRowIndex )
g.FontSize = 24
Var cFontFamily As String = g.FontName
Var oFontFamily As FontFamily
Var cMessage As String = ""
Var nLineWidth As Integer = 0
Var nSpaceWidth As Integer = g.TextWidth( " " )
// Now we have the font name from the popups on the dialog test for supported scripts
Var oFontScripts As New FontScripts( g.FontName )
Var Scripts() As String = oFontScripts.KnownScripts
For Each Script As String In Scripts
If oFontScripts.SupportsScript( Script ) Then
// and build the string to display
If nLineWidth + g.TextWidth( " " + Script ) >= Me.Width - 20 Then
cMessage = cMessage + EndOfLine
nLineWidth = 0
End If
// Deal with line wrapping
If nLineWidth = 0 Then
cMessage = cMessage + Script
nLineWidth = nLineWidth + g.TextWidth( Script )
Else
cMessage = cMessage + " " + Script
nLineWidth = nLineWidth + g.TextWidth( " " + Script )
End If
End If
Next
// and Symbols
Var nFound As Integer = 0
Var cExtra As String = oFontScripts.Symbols
If cExtra <> "" Then
If nLineWidth + g.TextWidth( " " + cExtra ) >= Me.Width - 10 Then
cMessage = cMessage + EndOfLine
nLineWidth = 0
End If
If nLineWidth = 0 Then
cMessage = cMessage + cExtra
nLineWidth = nLineWidth + g.TextWidth( cExtra )
Else
cMessage = cMessage + " " + cExtra
nLineWidth = nLineWidth + g.TextWidth( " " + cExtra )
End If
End If
Var textX As Double = ( g.Width - g.TextWidth( cMessage ) ) / 2
Var textY As Double = ( ( g.Height - g.TextHeight( cMessage, Self.Width ) ) / 2 ) + g.FontAscent
g.DrawText cMessage, textX, textY
End If
End If
Pardon me for saying so, but that is a crazy way to do that - your validation actually relies on rendering the glyph and checking to see if any pixels were changed. And you only check one or two characters per script…?
The whole problem here is that a font may not contain all the characters necessary to render a piece of text. I don’t see how your code answers that question in a way that I would care to rely on. Fonts routinely contain glyph sets that are insufficient to render even the simpler scripts that represent English text, even though they seem like they may at first glance (for example, they may be missing certain punctuation).
On the other hand, I applaud you for creating such a unique piece of code. It’s clever, and I admire clever.
it isn’t in any way attempting to perform the task requested. It is a font selector. That checks to see what scripts a font supports. In order to do that I’ve used minimum number of characters for speed. For example how could a font support western script if it doesn’t support the letter A.
I then discovered that some fonts reported support for a given character but had an invisible or blank glyph, I had to check the glyph actually contained something before I could trust the results. It was rare but it occurred.
As I said the underlying mechanisms and calls could perhaps be used to validate a string was contained within a given font.