I had some
interest over this week looking at being able to show some musical symbols
in a Silverlight / WPF application and had expected this to be a relatively
simple thing but ended up chasing down quite a lot of little dark alleys until
I found a couple of good answers. So, with my New Year's resolution of posting
a lot more of these little thoughts, I'll see how far I can get describing the
journey.
For starters I thought this would be
simple. Everything is now unicode, right, they have Japanese, Korean, Maths notation
and other symbols, there must be a code-page for musical notation. Yes! and
well, no! If you go to the Unicode specification, there's a whole set of musical
symbols defined. http://www.unicode.org/charts/PDF/U1D100.pdf Next bit of
searching was to find a suitable font that had these symbols included. I
finally found 'Symbola.ttf' which included it, but probably not my final font.
Enough to try though. I dutifully installed this and then wanted to have a look
at what symbols were there. The big problem is that these are in UTF-32. Take a
look at charmap (on Windows), this only extends up to UTF-16. So I couldn't see
them. Undeterred and still expecting this not to be a problem I had a long
fight with Silverlight to try to get it to show this unicode encoding, finally
giving up when I realised it only extended to UTF-16 (basically this means you
can only go as far as \UFFFF or  as otherwise written. This is a
problem, as the musical notation is all above that, with the treble-clef at U+1d120.
Hmmpf.
So, where could I
go then? I did have a poke around in some other fonts, and hoped that they
would have musical notation in the private areas. More of which later, but at
this point I didn't find quite what I was looking for and so, I'll continue my
journey as it uncovered some other interesting things on the way to what I got
to solve my problem.
Next step, I took
a look at EasyABC, as I had posted previously I've been using this on my Mac
and am really liking it, so I took a look at the package and the python
scripts. First thing to note that the actual musical rendering is made by
abc2ps, an excellent little project written in C. The musical format in this
case was not quite what I was looking for (need to come back and detail a bit
more when I get a chance).
After that, as
I've been playing with it as well, I took a look at VexFlow. Now, this was
interesting, digging down in the
source on github,
they have the file
gonville.js that appears to have some interesting path-like font descriptions. In trying to
understand this I uncovered quite a
long and interesting thread on pluggable fonts for VexFlow that I must go back and read in more detail.
The thread lead
me on to SMuFL - Standard Music Font Layout (http://www.smufl.org/) - now I was
getting somewhere! I had an interesting read around this and found the Bravura.otf
OpenText format font. This revealed a number of interesting things - firstly it
is available under the
SIL Open Font license and secondly it implements all of the specified unicode musical symbol range
U+1d100 - U+1d1dd, however these are mapped into the unicode Basic Multilingual
Plane in the standard private area at U+e000 so fall within the scope of UTF-16
and hopefully usable by Silverlight! Now we're sucking diesel!
Next step I
returned to the previous bashing of my head against a Silverlight wall and with
a bit more googling, finally got the font in and showing a treble-clef. There's
a quite a few false trails out there and the Silverlight route throws up some
slightly different challenges to a standard WPF app, but the best advice was
from
Apres Pro Silverlight 5 in C# here on
Font Embedding It's important to follow the note on using the # to show the name of the font,
not just the file containing the font.
My code directly
in WPF finally looked like this...
<TextBlock Name="trebleclef" FontSize="52" FontFamily="/AgScore;component/Fonts/Bravura.otf#Bravura"></TextBlock>
In this case I
have the Bravura.otf font file in a sub-directory called Fonts and the
properties have Build Action set to Resource and Copy to Output set to Copy if
newer. The name of my application is 'AgScore'. Nice!
Now, there's an
interesting little note in the Apres book that says if you can't get this
working for you in your app, then turn the fonts into graphics and use the Path
notation. Nice! To backtrack a little. I had tried this unsuccessfully earlier
when I took one of the lines of the gonville.js file path-like notation and had
converted that into the path mini-language. It sort of worked, but I didn't
have the energy to follow that through when I thought there should be a simpler
answer and fond the SMuFL links. The little note in the book points to a
link hich details how to get the text into a path
string from code. I didn't want to fight this from Silverlight itself and the
approach shown in the link uses a backend service by the looks of it, so I decided to bash at this
directly in a C# application and get the path information to see how it worked.
Here's how it looks:
// Set up the Culture
string strCulture = "en-us";
System.Globalization.CultureInfo ci = new System.Globalization.CultureInfo(strCulture);
// Set up the flow direction
System.Windows.FlowDirection fd = FlowDirection.LeftToRight;
// Set up the font family from the parameter
FontFamily ff = new FontFamily("D:\\Users\\gbcollia\\Documents\\Visual Studio 2010\\Projects\\AgScore\\AgScore\\Fonts\\Bravura.otf#Bravura");
// Create the new typeface
System.Windows.Media.Typeface tf = new System.Windows.Media.Typeface(ff,
FontStyles.Normal,
FontWeights.Normal
FontStretches.Normal);
// Create a formatted text object from the text,
// culture, flowdirection, typeface, size and black
FormattedText t = new FormattedText("\uf472", ci, fd, tf, 20, System.Windows.Media.Brushes.Black);
// Build a Geometry out of this
Geometry g = t.BuildGeometry(new Point(0, 0));
// Get the Path info from the geometry
PathGeometry p = g.GetFlattenedPathGeometry();
// Get the path data info
string pathdata = p.ToString();
I then unashamedly pasted this path data into a path in
SIlverlight to see how that looked as follows:
<Path Stroke="Black" Fill="Black" StrokeThickness="0.1" Height="80" Width="30" Margin="0" Data="F1M8.81999969482422,38.7800025939941L9.28749942779541,41.7350006103516
9.67999935150146,44.3600006103516 11.2150001525879,43.2925033569336
11.6999998092651,41.6400032043457 11.6999998092651,41.4200019836426
11.4496870040894,40.2987518310547 10.852499961853,39.4700012207031
8.81999969482422,38.7800025939941z
M6.83999967575073,33.4400024414063L4.96499967575073,34.8790626525879
3.37499976158142,36.4025001525879 2.27250003814697,38.1846885681152
1.85999989509583,40.4000015258789 1.85999989509583,40.560001373291 2.47249984741211,42.6812515258789
3.86499977111816,43.9550018310547 5.6399998664856,44.5762519836426
7.39999961853027,44.7400016784668 8.69999980926514,44.5800018310547
7.67999982833862,38.8800010681152 6.2024998664856,39.4775009155273
5.70843744277954,40.080940246582 5.51999998092651,40.9600028991699
5.51999998092651,41.1599998474121 5.94499969482422,42.2800025939941
6.6399998664856,42.9200019836426 6.94000005722046,43.2800025939941
6.69999980926514,43.4400024414063 6.27999973297119,43.3600006103516 4.96999979019165,42.4300003051758
4.1399998664856,40.7200012207031 4.01999998092651,39.8400001525879
4.26656246185303,38.5371894836426 4.94250011444092,37.3675003051758
5.95218753814697,36.4340629577637 7.19999980926514,35.8400001525879
6.83999967575073,33.4400024414063z
M9.17999935150146,22.7200012207031L8.14781188964844,23.1481266021729
7.41749954223633,24.1650009155273 6.83999967575073,27.0800018310547
6.87999963760376,27.8000030517578 6.92999982833862,28.1550025939941
7.03999996185303,28.7800025939941 8.40062522888184,27.6990642547607
9.34500026702881,26.7775020599365 10.2399997711182,24.7600021362305
10.2799997329712,24.3200016021729 9.97749996185303,23.1150016784668
9.17999935150146,22.7200012207031z
M9.03999996185303,18.8200016021729L9.49499988555908,19.1025009155273
9.96812534332275,19.6721897125244 10.5199995040894,20.7200012207031
11.1599998474121,22.6350021362305 11.3800001144409,24.5200023651123
11.188437461853,26.9756278991699 10.5675001144409,29.1500015258789
9.4478120803833,31.1293754577637 7.75999975204468,33
8.01000022888184,34.2675018310547 8.26000022888184,35.7000007629395
8.51999950408936,35.6600036621094 8.73999977111816,35.6600036621094
10.5046873092651,36.0125045776367 11.9224996566772,36.9800033569336
12.8940620422363,38.4275016784668 13.3199996948242,40.2200012207031
13.3199996948242,40.5 13.0853118896484,42.0806274414063
12.4025001525879,43.4550018310547 11.3034372329712,44.5668754577637
9.81999969482422,45.3600006103516 10.1674995422363,47.5149993896484
10.2784376144409,48.1106262207031 10.3199996948242,48.3199996948242
10.4399995803833,49.7000007629395 10.071249961853,51.6768760681152
9.15499973297119,52.9900016784668 7.97624969482422,53.7331275939941
6.81999969482422,54 6.35999965667725,54.0200004577637
4.38562488555908,53.6465644836426 3.23999977111816,52.7425003051758
2.70937490463257,51.6321868896484 2.57999968528748,50.6399993896484
2.61249971389771,50.1400032043457 2.65999960899353,49.9400024414063
3.48749995231628,48.3775024414063 5.07999992370605,47.7600021362305
5.98000001907349,47.9600028991699 6.86999988555908,48.7550010681152
7.15999984741211,49.9400024414063 7.15999984741211,50.0800018310547
6.97531223297119,50.8746910095215 6.50749969482422,51.4725036621094
5.23999977111816,52.1600036621094 4.73999977111816,52.2800025939941 4.55999994277954,52.4400024414063
4.73999977111816,52.6600036621094 6.39999961853027,53.060001373291
6.77999973297119,53.060001373291 7.296875,53.0031280517578
8.19999980926514,52.6350021362305 9.05812454223633,51.6593780517578
9.4399995803833,49.7800025939941 9.31999969482422,48.4200019836426
8.85999965667725,45.6400032043457 8.76000022888184,45.6400032043457
7.39999961853027,45.7200012207031 4.93343734741211,45.4071884155273
2.63749980926514,44.3575019836426 0.877812504768372,42.4040641784668
0.0200001019984484,39.3800010681152 1.21159985155828E-07,38.7800025939941
0.558125019073486,36.0762519836426 2.00499987602234,33.6650009155273
3.9993748664856,31.5012512207031 6.19999980926514,29.5400009155273
5.96000003814697,28.2800025939941 5.81999969482422,26.2600021362305
6.05593729019165,23.6968765258789 6.71749973297119,21.5050010681152
7.73531246185303,19.8306274414063 9.03999996185303,18.8200016021729z"/>
Very nice. I had a treble-clef, although not quite how I
wanted it. So, after a quick, lazy google, I scaled using this suggestion (http://stackoverflow.com/questions/5109058/wpf-the-right-way-to-scale-a-path).
Omitting the path data for clarity:
<Path Stroke="Black" Fill="Black" StrokeThickness="0.1" Data="..."
Height="80" Width="30" Margin="0" RenderTransformOrigin="0,
0">
<Path.RenderTransform>
<ScaleTransform ScaleX="4.2" ScaleY="4.2"/>
</Path.RenderTransform>
</Path>
This pretty much gets me what I wanted. Now, I just need to
do a simple routine to export all of the fonts I need into paths, put them into
a separate XML file (or similar) and I can use them in Silverlight programmatically
to render some nice graphics.
Hope this is of use to someone else struggling with the same
questions.