Platforms to show: All Mac Windows Linux Cross-Platform
/MacCG/CoreText/CoreText VariableFont
Required plugins for this example: MBS MacCF Plugin, MBS MacBase Plugin, MBS MacCocoa Plugin, MBS MacCG Plugin, MBS Main Plugin
Last modified Sun, 14th Feb 2026.
You find this example project in your MBS Xojo Plugin download as a Xojo project file within the examples folder: /MacCG/CoreText/CoreText VariableFont
Download this example: CoreText VariableFont.zip
Project "CoreText VariableFont.xojo_binary_project"
Class App Inherits DesktopApplication
Const kEditClear = "&Delete"
Const kFileQuit = "&Quit"
Const kFileQuitShortcut = ""
EventHandler Sub Opening()
me.AllowAutoQuit = True
End EventHandler
End Class
Class Window1 Inherits DesktopWindow
Control Label1 Inherits DesktopLabel
ControlInstance Label1 Inherits DesktopLabel
End Control
Control LabelFontSize Inherits DesktopLabel
ControlInstance LabelFontSize Inherits DesktopLabel
End Control
Control SliderFontSize Inherits DesktopSlider
ControlInstance SliderFontSize Inherits DesktopSlider
EventHandler Sub ValueChanged()
Var fontSize As Double = Me.Value
' Convert the CTFontDescriptor to an NSFontDescriptor
Var n As NSFontDescriptorMBS = NSFontDescriptorMBS.fontDescriptorWithCTFontDescriptor(Self.CTFontDescriptor)
' Create an NSFont from the descriptor at the specified size
Var theNSFont As NSFontMBS = NSFontMBS.fontWithDescriptor(n, fontSize)
' Apply the font to TextArea
TextArea1.NSTextViewMBS.Font = theNSFont
LabelFontSize.Text = Me.Value.ToString
End EventHandler
End Control
Control PopupMenuFamily Inherits DesktopPopupMenu
ControlInstance PopupMenuFamily Inherits DesktopPopupMenu
EventHandler Sub SelectionChanged(item As DesktopMenuItem)
#Pragma Unused item
/////////////////////////////////////////////
' Make Font Style Menu
PopupMenuStyle.RemoveAllRows
' Get the shared NSFontManager instance and retrieve all members of the selected font family
Var NSFontManager As New NSFontManagerMBS
Var Members() As Variant = NSFontManager.availableMembersOfFontFamily(Me.SelectedRowText)
' Iterate through each font family member
For Each Member As Variant In Members
' Unpack the member info array
Var MemberInfos() As Variant = Member
Var PostScriptName As String = MemberInfos(0)
Var StyleName As String = MemberInfos(1)
' Var Weight As Integer = MemberInfos(2)
' Add the style name as the display label, and store the PostScript name as the row tag
PopupMenuStyle.AddRow StyleName
PopupMenuStyle.RowTagAt(PopupMenuStyle.LastAddedRowIndex) = PostScriptName
Next
PopupMenuStyle.SelectedRowIndex = 0
End EventHandler
End Control
Control PopupMenuStyle Inherits DesktopPopupMenu
ControlInstance PopupMenuStyle Inherits DesktopPopupMenu
EventHandler Sub SelectionChanged(item As DesktopMenuItem)
#Pragma Unused item
/////////////////////////////////////////////
' Make NSFont
Var fontPSName As String = Me.RowTagAt(me.SelectedRowIndex)
Var fontSize As Double = TextArea1.NSTextViewMBS.Font.pointSize
var theCTFont as CTFontMBS = CTFontMBS.CreateWithName(fontPSName, fontSize)
Self.CTFontDescriptor = theCTFont.FontDescriptor
Var n As NSFontDescriptorMBS = NSFontDescriptorMBS.fontDescriptorWithCTFontDescriptor(Self.CTFontDescriptor)
var theNSFont as NSFontMBS = NSFontMBS.fontWithDescriptor(n, fontSize)
/////////////////////////////////////////////
' Set to Listbox
Self.SetListbox(theCTFont)
/////////////////////////////////////////////
' Make Axis Dictionaries
Self.MakeAxisDics(theCTFont)
/////////////////////////////////////////////
' Set Slider
Self.SetSlider()
/////////////////////////////////////////////
' Set to TextArea
TextArea1.NSTextViewMBS.Font = theNSFont
TextArea1.NSTextViewMBS.needsDisplay = True
End EventHandler
End Control
Control TextArea1 Inherits DesktopTextArea
ControlInstance TextArea1 Inherits DesktopTextArea
EventHandler Sub Opening()
Self.SetLineHeight(TextArea1.NSTextViewMBS)
End EventHandler
End Control
Control Label11 Inherits DesktopLabel
ControlInstance Label11 Inherits DesktopLabel
End Control
Control Label12 Inherits DesktopLabel
ControlInstance Label12 Inherits DesktopLabel
End Control
Control ListBox1 Inherits DesktopListBox
ControlInstance ListBox1 Inherits DesktopListBox
EventHandler Sub RowExpanded(row As Integer)
' Retrieve the axis info dictionary stored as the row tag
Var AxisDic As Dictionary = Me.RowTagAt(row)
' Extract each variation axis property from the dictionary
Var AxisName As String = AxisDic.Value(CTFontMBS.kCTFontVariationAxisNameKey)
Var AxisID As Integer = AxisDic.Value(CTFontMBS.kCTFontVariationAxisIdentifierKey)
Var AxisMin As Double = AxisDic.Value(CTFontMBS.kCTFontVariationAxisMinimumValueKey)
Var AxisMax As Double = AxisDic.Value(CTFontMBS.kCTFontVariationAxisMaximumValueKey)
Var AxisDefault As Double = AxisDic.Value(CTFontMBS.kCTFontVariationAxisDefaultValueKey)
' Display all axis properties as key-value rows
Me.AddRow("kCTFontVariationAxisNameKey", AxisName)
Me.AddRow("kCTFontVariationAxisIdentifierKey", AxisID.ToString)
Me.AddRow("kCTFontVariationAxisMinimumValueKey", AxisMin.ToString)
Me.AddRow("kCTFontVariationAxisMaximumValueKey", AxisMax.ToString)
Me.AddRow("kCTFontVariationAxisDefaultValueKey", AxisDefault.ToString)
' Get the current axis values of the style
Var ctFont As CTFontMBS = Me.CellTagAt(row, 0)
Var axisValueDic As Dictionary = ctFont.Variation
' If the font has a value set for this axis, display it
If axisValueDic.HasKey(AxisID) Then
Var axisValue As Double = axisValueDic.Value(AxisID)
Me.AddRow("Axis value of this style", axisValue.ToString)
End If
End EventHandler
End Control
Control LabelAxis Inherits DesktopLabel
ControlInstance LabelAxis(0) Inherits DesktopLabel
ControlInstance LabelAxis(1) Inherits DesktopLabel
ControlInstance LabelAxis(2) Inherits DesktopLabel
ControlInstance LabelAxis(3) Inherits DesktopLabel
End Control
Control AxisValue Inherits DesktopLabel
ControlInstance AxisValue(0) Inherits DesktopLabel
ControlInstance AxisValue(1) Inherits DesktopLabel
ControlInstance AxisValue(2) Inherits DesktopLabel
ControlInstance AxisValue(3) Inherits DesktopLabel
End Control
Control SliderAxis Inherits DecimalSlider
ControlInstance SliderAxis(0) Inherits DecimalSlider
ControlInstance SliderAxis(1) Inherits DecimalSlider
ControlInstance SliderAxis(2) Inherits DecimalSlider
ControlInstance SliderAxis(3) Inherits DecimalSlider
EventHandler Sub ValueChanged(index as Integer)
AxisValue(index).Text = Me.DecimalValue.ToString
' Change Axis Value
Var fontSize As Double = TextArea1.NSTextViewMBS.Font.pointSize
var axisID as Integer = LabelAxisID(index).Text.ToInteger
var axisValue as Double = Me.DecimalValue
Self.CTFontDescriptor = Self.CTFontDescriptor.CopyWithVariation(axisID, axisValue)
Var n As NSFontDescriptorMBS = NSFontDescriptorMBS.fontDescriptorWithCTFontDescriptor(Self.CTFontDescriptor)
Var theNSFont As NSFontMBS = NSFontMBS.fontWithDescriptor(n, fontSize)
TextArea1.NSTextViewMBS.Font = theNSFont
TextArea1.NSTextViewMBS.needsDisplay = True
End EventHandler
End Control
Control LabelAxisID Inherits DesktopLabel
ControlInstance LabelAxisID(0) Inherits DesktopLabel
ControlInstance LabelAxisID(1) Inherits DesktopLabel
ControlInstance LabelAxisID(2) Inherits DesktopLabel
ControlInstance LabelAxisID(3) Inherits DesktopLabel
End Control
EventHandler Sub Opening()
////////////////////////////////////////
' Make Font Family Menu
PopupMenuFamily.RemoveAllRows
' Get the shared NSFontManager instance and retrieve all available font families
Var NSFontManager As New NSFontManagerMBS
Var fontFamilies() As String = NSFontManager.availableFontFamilies
' Iterate through each font family and add only variable fonts to the menu
For Each fontFamilyName As String In fontFamilies
If Self.IsVariableFont(fontFamilyName) Then
PopupMenuFamily.AddRow fontFamilyName
End If
Next
PopupMenuFamily.SelectedRowIndex = 0
End EventHandler
Private Function IsVariableFont(FontFamilyName as String) As Boolean
var NSFontManager as new NSFontManagerMBS
var Members() as variant = NSFontManager.availableMembersOfFontFamily(FontFamilyName)
var MemberInfos() as Variant = Members(0)
Var PostScriptName As String = MemberInfos(0)
Var theCTFont As CTFontMBS = CTFontMBS.CreateWithName(PostScriptName, 10)
Var AxisDics() As Dictionary = theCTFont.VariationAxes
If AxisDics.LastIndex>-1 Then Return True
End Function
Private Sub MakeAxisDics(ctFont as CTFontMBS)
Redim Self.Axis(-1)
Var AxisDics() As Dictionary = ctFont.VariationAxes
Var c As Integer = AxisDics.LastIndex
If c=-1 Then Return
For i As Integer=0 To c
Var AxisDic As Dictionary = AxisDics(i)
Var AxisName As String = AxisDic.Value(CTFontMBS.kCTFontVariationAxisNameKey)
Var AxisID As Integer = AxisDic.Value(CTFontMBS.kCTFontVariationAxisIdentifierKey)
Var AxisMin As Double = AxisDic.Value(CTFontMBS.kCTFontVariationAxisMinimumValueKey)
Var AxisMax As Double = AxisDic.Value(CTFontMBS.kCTFontVariationAxisMaximumValueKey)
Var AxisDefault As Double = AxisDic.Value(CTFontMBS.kCTFontVariationAxisDefaultValueKey)
Var AxisStyleValue As Double
Var axisValueDic As Dictionary = ctFont.Variation
If axisValueDic.HasKey(AxisID) Then
AxisStyleValue = axisValueDic.Value(AxisID)
else
AxisStyleValue = AxisDefault
End If
' make dictionary
Var d As New Dictionary
d.Value("Name") = AxisName
d.Value("ID") = AxisID
d.Value("Min") = AxisMin
d.Value("Max") = AxisMax
d.Value("Default") = AxisDefault
d.Value("StyleValue") = AxisStyleValue
Self.Axis.Add d
Next
End Sub
Private Sub SetLineHeight(t as NSTextViewMBS)
t.layoutManager.usesFontLeading = False
Var n As NSParagraphStyleMBS = t.defaultParagraphStyle
If n Is Nil Then
n = NSParagraphStyleMBS.defaultParagraphStyle
End If
Dim m As NSMutableParagraphStyleMBS = n.mutableCopy
m.setLineHeightMultiple(1.3)
Dim d As New Dictionary
d.Value(t.layoutManager.attributedString.NSParagraphStyleAttributeName) = m
t.typingAttributes = d
t.textStorage.addAttributes(d, NSMakeRangeMBS(0, t.textStorage.length))
End Sub
Private Sub SetListbox(ctFont as CTFontMBS)
Listbox1.RemoveAllRows
Var AxisDics() As Dictionary = ctFont.VariationAxes
Var c As Integer = AxisDics.LastIndex
If c=-1 Then Return
For i As Integer=0 To c
Var AxisDic As Dictionary = AxisDics(i)
' Var AxisName As String = AxisDic.Value(CTFontMBS.kCTFontVariationAxisNameKey)
' Var AxisID As Integer = AxisDic.Value(CTFontMBS.kCTFontVariationAxisIdentifierKey)
' Var AxisMin As Double = AxisDic.Value(CTFontMBS.kCTFontVariationAxisMinimumValueKey)
' Var AxisMax As Double = AxisDic.Value(CTFontMBS.kCTFontVariationAxisMaximumValueKey)
' Var AxisDefault As Double = AxisDic.Value(CTFontMBS.kCTFontVariationAxisDefaultValueKey)
Listbox1.AddExpandableRow "Axis("+i.toString+")"
var row as Integer = Listbox1.LastAddedRowIndex
ListBox1.CellTagAt(row,0) = ctFont
ListBox1.RowTagAt(row) = AxisDic
Listbox1.RowExpandedAt(row) = True
Next
End Sub
Private Sub SetSlider()
Var i As Integer
' invisible all
for i=0 to 3
LabelAxis(i).Visible = False
AxisValue(i).Visible = False
SliderAxis(i).Visible = False
Next
' set slider
Var c As Integer = Self.Axis.LastIndex
If c=-1 Then Return
For i=0 To c
if i>3 then Exit
var d as Dictionary = Self.Axis(i)
var AxisName as String = d.Value("Name")
var AxisID as Integer = d.Value("ID")
var AxisMin as Double = d.Value("Min")
Var AxisMax As Double = d.Value("Max")
var AxisStyleValue as Double = d.Value("StyleValue")
LabelAxis(i).Text = d.Value("Name")
AxisValue(i).Text = AxisStyleValue.ToString
SliderAxis(i).DecimalMinimum = AxisMin
SliderAxis(i).DecimalMaximum = AxisMax
SliderAxis(i).DecimalValue = AxisStyleValue
LabelAxisID(i).Text = AxisID.ToString
LabelAxis(i).Visible = True
AxisValue(i).Visible = True
SliderAxis(i).Visible = True
Next
End Sub
Property Private Axis() As Dictionary
Property Private CTFontDescriptor As CTFontDescriptorMBS
End Class
MenuBar MainMenuBar
MenuItem FileMenu = "&File"
MenuItem FileQuit = "#App.kFileQuit"
MenuItem EditMenu = "&Edit"
MenuItem EditUndo = "&Undo"
MenuItem EditSeparator1 = "-"
MenuItem EditCut = "Cu&t"
MenuItem EditCopy = "&Copy"
MenuItem EditPaste = "&Paste"
MenuItem EditClear = "#App.kEditClear"
MenuItem EditSeparator2 = "-"
MenuItem EditSelectAll = "Select &All"
MenuItem WindowMenu = "Window"
MenuItem HelpMenu = "&Help"
End MenuBar
Sign
End Sign
Class DecimalSlider Inherits DesktopSlider
ComputedProperty DecimalMaximum As Double
Sub Set()
mDecimalMaximum = value
Var scale As Integer = CurrentScale()
Var maxVal As Integer = Round(mDecimalMaximum * scale)
Var minVal As Integer = Round(mDecimalMinimum * scale)
Self.MaximumValue = maxVal
self.MinimumValue = minVal
End Set
Sub Get()
Return mDecimalMaximum
End Get
End ComputedProperty
ComputedProperty DecimalMinimum As Double
Sub Set()
mDecimalMinimum = value
Var scale As Integer = CurrentScale()
Var maxVal As Integer = Round(mDecimalMaximum * scale)
Var minVal As Integer = Round(mDecimalMinimum * scale)
Self.MaximumValue = maxVal
self.MinimumValue = minVal
End Set
Sub Get()
Return mDecimalMinimum
End Get
End ComputedProperty
ComputedProperty DecimalValue As Double
Sub Set()
Var scaled As Integer = Round(value * CurrentScale())
self.Value = scaled
End Set
Sub Get()
Return self.Value / CurrentScale()
End Get
End ComputedProperty
Private Function CurrentScale() As Integer
Var sMax As Integer = DetectScale(mDecimalMaximum)
Var sMin As Integer = DetectScale(mDecimalMinimum)
Return If(sMax > sMin, sMax, sMin)
End Function
Private Function DetectScale(value As Double) As Integer
Var v As Double = Abs(value)
Var scale As Integer = 1
While (v - Floor(v)) > 1e-9 And scale < 1000
v = v * 10
scale = scale * 10
Wend
Return scale
End Function
Property Private mDecimalMaximum As Double = 100.0
Property Private mDecimalMinimum As Double = 0.0
End Class
End Project
See also:
- /MacCG/CoreText/CoreText Dynamic Text Height
- /MacCG/CoreText/CoreText Font Change Notification
- /MacCG/CoreText/CoreText Test
Download this example: CoreText VariableFont.zip
The items on this page are in the following plugins: MBS MacCG Plugin.