Шрифт Segoe UI Symbol в WinRT

107

Помимо символов области для приватного использования, шрифт Segoe UI Symbol также поддерживает коды символов от 0x2600 до 0x26FF, которые в стандарте Юникод отнесены к категории «прочих символов». Некоторые из них могут подходить для вывода на кнопках строки приложения.

Шрифт Segoe UI Symbol также выходит за пределы диапазона 16-разрядных кодов и содержит глифы для кодов символов в диапазоне от 0x1F300 до 0x1F5FF. Это символы пиктограмм «эмодзи», которые появились в Японии, но со временем проникли в Microsoft Windows Phone и Apple iPhone.

Также поддерживаются стандартные «значки настроения» в диапазоне от 0x1F600 до 0x1F64F и диапазон от 0x1F680 до 0x1F6C5, включая транспортные и географически условные знаки.

Чтобы упростить поиск дополнительных знаков для строк приложений, я написал программу SegoeSymbols, которая выводит все символы шрифта Segoe UI Symbol в диапазоне от 0 до 0x1FFFF. Как вы, возможно, знаете, стандарт Юникод начинался как 16-разрядная кодировка с кодами в диапазоне от 0x0000 до 0xFFFF. Когда стало очевидно, что 65 536 кодовых точек недостаточно, были задействованы коды в диапазоне от 0x10000 до 0x10FFFF, а количество символов увеличилось до 1,1 миллиона. В расширение Юникода также вошла система представления этих дополнительных символов парами 16-разрядных значений.

Представление символов Юникода одним 32-разрядным кодом называется UTF-32 (32-bit Unicode Transformation Format). UTF-32 чрезвычайно редко применяется на практике. Многие разработчики даже не рассматривают Юникод как 32-разрядную кодировку, потому что 32-разрядная часть Юникода была фактически «прилеплена» к 16-разрядной.

Соответственно в большинстве современных языков программирования и операционных систем поддерживается UTF-16. Структура Char в Windows Runtime представляет собой 16-разрядное целое, и она же является основой для типа данных char в C#. Для представления дополнительных символов в диапазоне от 0x10000 до 0x10FFFF UTF-16 использует последовательность из двух 16-разрядных символов, которые называются суррогатами (для их использования в Юникоде был зарезервирован специальный диапазон 16-разрядных кодов). Начальный суррогат лежит в диапазоне от 0xD800 до 0xDBFF, а конечный — в диапазоне от 0xDC00 до 0xDFFF. Всего существуют 1024 возможных начальных суррогата и 1024 возможных конечных суррогата, которых достаточно для представления 1 048 576 кодов в диапазоне от 0x10000 до 0x10FFFF (алгоритм будет описан ниже).

Текст на языках, использующих латинский алфавит, в основном ограничивается ASCII-кодами символов в диапазоне от 0x0020 до 0x007E, поэтому многие веб-страницы и другие файлы используют для хранения текста систему, называемую UTF-8. В ней 7-разрядные символы кодируются непосредственно, а для остальных символов Юникода используются от 1 до 3 дополнительных байтов.

Так как я писал программу SegoeSymbols в основном для просмотра знаков, которые могут пригодиться для использования на строках приложений, программа ограничивается кодами до 0x1FFFF. Файл XAML содержит простой заголовок, панель Grid для отображения блока из 256 символов и элемент управления Slider:

<Page ...>

    <Page.Resources>
        <local:DoubleToStringHexByteConverter x:Key="hexByteConverter" />
        <Style x:Key="HeaderTextStyle" TargetType="TextBlock">
            <Setter Property="FontSize" Value="56"/>
            <Setter Property="FontWeight" Value="Light"/>
            <Setter Property="LineHeight" Value="40"/>
            <Setter Property="RenderTransform">
                <Setter.Value>
                    <TranslateTransform X="-2" Y="8"/>
                </Setter.Value>
            </Setter>
        </Style>
    </Page.Resources>

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <TextBlock Name="titleText"
                   Grid.Row="0"
                   Text="Шрифт Segoe UI Symbol"
                   HorizontalAlignment="Center"
                   Style="{StaticResource HeaderTextStyle}" />

        <Grid Name="characterGrid"
              Grid.Row="1"
              HorizontalAlignment="Center"
              VerticalAlignment="Center" />

        <Slider Grid.Row="2"
                Orientation="Horizontal"
                Margin="24 0"
                Minimum="0"
                Maximum="511"
                SmallChange="1"
                LargeChange="16"
                ThumbToolTipValueConverter="{StaticResource hexByteConverter}"
                ValueChanged="OnSliderValueChanged" />
    </Grid>
</Page>

Обратите внимание: у элемента управления Slider свойство Maximum равно 511 - максимальный код символа, который должен отображаться (0x1FFFF), разделенный на 256. Класс DoubleToStringHexByteConverter, упоминаемый в секции Resources похож на класс, который был показан ранее, но он также выводит пару символов подчеркивания для соблюдения единства экранного оформления:

using System;
using Windows.UI.Xaml.Data;

namespace WinRTTestApp
{
    public class DoubleToStringHexByteConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, 
            object parameter, string language)
        {
            return ((int)(double)value).ToString("X2") + "__";
        }

        public object ConvertBack(object value, Type targetType, 
            object parameter, string language)
        {
            return value;
        }
    }
}

Для каждого значения Slider отображаются 256 символов в виде матрицы 16x16. Код построения панели Grid для отображения этих 256 символов получился довольно запутанным, потому что я решил разделить строки и столбцы символов линиями, для которых в Grid создаются собственные строки и столбцы.

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;


namespace WinRTTestApp
{
    public sealed partial class MainPage : Page
    {
        const int CellSize = 36;
        const int LineLength = (CellSize + 1) * 16 + 18;
        FontFamily symbolFont = new FontFamily("Segoe UI Symbol");

        TextBlock[] txtblkColumnHeads = new TextBlock[16];
        TextBlock[,] txtblkCharacters = new TextBlock[16, 16];

        public MainPage()
        {
            this.InitializeComponent();

            for (int row = 0; row < 34; row++)
            {
                RowDefinition rowdef = new RowDefinition();

                if (row == 0 || row % 2 == 1)
                    rowdef.Height = GridLength.Auto;
                else
                    rowdef.Height = new GridLength(CellSize, GridUnitType.Pixel);

                characterGrid.RowDefinitions.Add(rowdef);

                if (row != 0 && row % 2 == 0)
                {
                    TextBlock txtblk = new TextBlock
                    {
                        Text = (row / 2 - 1).ToString("X1"),
                        VerticalAlignment = VerticalAlignment.Center
                    };
                    Grid.SetRow(txtblk, row);
                    Grid.SetColumn(txtblk, 0);
                    characterGrid.Children.Add(txtblk);
                }

                if (row % 2 == 1)
                {
                    Rectangle rectangle = new Rectangle
                    {
                        Stroke = this.Foreground,
                        StrokeThickness = row == 1 || row == 33 ? 1.5 : 0.5,
                        Height = 1
                    };
                    Grid.SetRow(rectangle, row);
                    Grid.SetColumn(rectangle, 0);
                    Grid.SetColumnSpan(rectangle, 34);
                    characterGrid.Children.Add(rectangle);
                }
            }

            for (int col = 0; col < 34; col++)
            {
                ColumnDefinition coldef = new ColumnDefinition();

                if (col == 0 || col % 2 == 1)
                    coldef.Width = GridLength.Auto;
                else
                    coldef.Width = new GridLength(CellSize);

                characterGrid.ColumnDefinitions.Add(coldef);

                if (col != 0 && col % 2 == 0)
                {
                    TextBlock txtblk = new TextBlock
                    {
                        Text = "00" + (col / 2 - 1).ToString("X1") + "_",
                        HorizontalAlignment = HorizontalAlignment.Center
                    };
                    Grid.SetRow(txtblk, 0);
                    Grid.SetColumn(txtblk, col);
                    characterGrid.Children.Add(txtblk);
                    txtblkColumnHeads[col / 2 - 1] = txtblk;
                }

                if (col % 2 == 1)
                {
                    Rectangle rectangle = new Rectangle
                    {
                        Stroke = this.Foreground,
                        StrokeThickness = col == 1 || col == 33 ? 1.5 : 0.5,
                        Width = 1
                    };
                    Grid.SetRow(rectangle, 0);
                    Grid.SetColumn(rectangle, col);
                    Grid.SetRowSpan(rectangle, 34);
                    characterGrid.Children.Add(rectangle);
                }
            }

            for (int col = 0; col < 16; col++)
                for (int row = 0; row < 16; row++)
                {
                    TextBlock txtblk = new TextBlock
                    {
                        Text = ((char)(16 * col + row)).ToString(),
                        FontFamily = symbolFont,
                        FontSize = 24,
                        HorizontalAlignment = HorizontalAlignment.Center,
                        VerticalAlignment = VerticalAlignment.Center
                    };
                    Grid.SetRow(txtblk, 2 * row + 2);
                    Grid.SetColumn(txtblk, 2 * col + 2);
                    characterGrid.Children.Add(txtblk);
                    txtblkCharacters[col, row] = txtblk;
                }
        }

        private void OnSliderValueChanged(object sender, RangeBaseValueChangedEventArgs args)
        {
            // ...
        }
    }
}

Обработчик ValueChanged элемента управления Slider выполняет относительно простую функцию по вставке нужного текста в существующие элементы TextBlock, но задача слегка усложняется необходимостью дополнительной обработки символов с кодами более 0xFFFF:

// ...
   
private void OnSliderValueChanged(object sender, RangeBaseValueChangedEventArgs args)
{
    int baseCode = 256 * (int)args.NewValue;

    for (int col = 0; col < 16; col++)
    {
        txtblkColumnHeads[col].Text = (baseCode / 16 + col).ToString("X3") + "_";

        for (int row = 0; row < 16; row++)
        {
            int code = baseCode + 16 * col + row;
            string strChar = null;

            if (code <= 0x0FFFF)
            {
                strChar = ((char)code).ToString();
            }
            else
            {
                code -= 0x10000;
                int lead = 0xD800 + code / 1024;
                int trail = 0xDC00 + code % 1024;
                strChar = ((char)lead).ToString() + (char)trail;
            }
            txtblkCharacters[col, row].Text = strChar;
        }
    }
}

// ...

Четыре инструкции в конце обработчика демонстрируют разделение кода символа в Юникоде из диапазона от 0x10000 до 0x10FFFF на два 10-разрядных значения для построения начального и конечного суррогатов, последовательность которых определяет один символ.

Если вы предпочитаете держаться подальше от низкоуровневых подробностей, замените эти четыре строки одной:

strChar = Char.ConvertFromUtf32(code);

Для кодов 0xFFFF и ниже метод Char.ConvertFromUtf32() возвращает строку, состоящую из одного символа; для кодов выше 0xFFFF строка состоит из двух символов. При передаче методу кода суррогата (от 0xD800 до 0xDFFF) выдается исключение.

Диапазоны, представляющие наибольший интерес для построения кнопок строки приложения, начинаются с кодов 0x2600 (прочие знаки), 0xE100 (область приватного использования шрифта Seqoe UI Symbol) и 0x1F300 (эмодзи, смайлики, транспортные и географические условные знаки):

Шрифт Seqoe UI Symbol

Символы за пределами 0xFFFF можно задать в XAML следующим образом:

\
<TextBlock FontFamily="Segoe UI Symbol"
    FontSize="24"
    Text="&#x1F3B7;" />

Код соответствует изображению саксофона. Visual Studio иногда выдает предупреждения, но программа компилируется и выполняется абсолютно нормально.

Следующий набор кнопок строки приложения явно предназначен для какого-то музыкального приложения:

...
<AppBarButton Label="Саксофон">
    <AppBarButton.Icon>
        <FontIcon FontFamily="Segoe UI Symbol" Glyph="&#x1F3B7;"/>
    </AppBarButton.Icon>
</AppBarButton>
<AppBarButton Label="Гитара">
    <AppBarButton.Icon>
        <FontIcon FontFamily="Segoe UI Symbol" Glyph="&#x1F3B8;"/>
    </AppBarButton.Icon>
</AppBarButton>
<AppBarButton Label="Пианино">
    <AppBarButton.Icon>
        <FontIcon FontFamily="Segoe UI Symbol" Glyph="&#x1F3B9;"/>
    </AppBarButton.Icon>
</AppBarButton>
<AppBarButton Label="Тромбон">
    <AppBarButton.Icon>
        <FontIcon FontFamily="Segoe UI Symbol" Glyph="&#x1F3BA;"/>
    </AppBarButton.Icon>
</AppBarButton>
<AppBarButton Label="Виолончель">
    <AppBarButton.Icon>
        <FontIcon FontFamily="Segoe UI Symbol" Glyph="&#x1F3BB;"/>
    </AppBarButton.Icon>
</AppBarButton>
...

А вот как выглядит результат:

Несколько кнопок со шрифтом Segoe UI Symbol
Пройди тесты
Лучший чат для C# программистов