FormattedText
не может сделать индекс / верхний индекс - но TextFormatter
может.
TextFormatter
- это API низкого уровня, вам нужно написать много кода для его использования - но большая часть кода просто подклассифицирует все классы, используемые для передачи параметров форматирования int TextFormatter
.
Как использовать TextFormatter
TextFormatter
принимает объект TextSource
и создает несколько объектов TextLine
(по одному для каждой строки), затем можно использовать метод TextLine.Draw
для рисования линии в контексте рисования.
Класс TextSource
является абстрактным, вы должны создать его подкласс и переопределить метод GetTextRun
, который просто возвращает объект TextRun
, который находится в предоставленной позиции символа.
TextRun
также является абстрактным - но у него есть подклассы, которые вы можете использовать - интересный класс - TextCharacters
, который содержит строку и информацию о форматировании.
Информация о форматировании находится в TextRunProperties
объекте, к сожалению, это еще один абстрактный класс, который вы должны подклассировать.
TextRunProperties
имеет свойство TypographyProperties
типа TextRunTypographyProperties
.
TextRunTypographyProperties
- это еще один абстрактный класс, который вам нужен для подкласса.
И, наконец, TextRunTypographyProperties
имеет свойство Variants
, которое вы можете использовать, как в примере с TextBlock.
Пример кода
Вот минимальный код, который я мог бы написать, чтобы нарисовать надстрочный текст:
Во-первых, наши TextRunProperties и TextRunTypographyProperties, которые могут возвращать вариант шрифта верхнего индекса:
class CustomTextRunProperties : TextRunProperties
{
private bool _superscript;
public CustomTextRunProperties(bool superscript)
{
_superscript = superscript;
}
public override System.Windows.Media.Brush BackgroundBrush
{
get { return null; }
}
public override CultureInfo CultureInfo
{
get { return CultureInfo.CurrentCulture; }
}
public override double FontHintingEmSize
{
get { return 22; }
}
public override double FontRenderingEmSize
{
get { return 22; }
}
public override Brush ForegroundBrush
{
get { return Brushes.Black; }
}
public override System.Windows.TextDecorationCollection TextDecorations
{
get { return new System.Windows.TextDecorationCollection(); }
}
public override System.Windows.Media.TextEffectCollection TextEffects
{
get { return new TextEffectCollection(); }
}
public override System.Windows.Media.Typeface Typeface
{
get { return new Typeface("Calibri"); }
}
public override TextRunTypographyProperties TypographyProperties
{
get
{
return new CustomTextRunTypographyProperties(_superscript);
}
}
}
class CustomTextRunTypographyProperties : TextRunTypographyProperties
{
private bool _superscript;
public CustomTextRunTypographyProperties(bool superscript)
{
_superscript = superscript;
}
public override int AnnotationAlternates
{
get { return 0; }
}
public override bool CapitalSpacing
{
get { return false; }
}
public override System.Windows.FontCapitals Capitals
{
get { return FontCapitals.Normal; }
}
public override bool CaseSensitiveForms
{
get { return false; }
}
public override bool ContextualAlternates
{
get { return false; }
}
public override bool ContextualLigatures
{
get { return false; }
}
public override int ContextualSwashes
{
get { return 0; }
}
public override bool DiscretionaryLigatures
{
get { return false; }
}
public override bool EastAsianExpertForms
{
get { return false; }
}
public override System.Windows.FontEastAsianLanguage EastAsianLanguage
{
get { return FontEastAsianLanguage.Normal; }
}
public override System.Windows.FontEastAsianWidths EastAsianWidths
{
get { return FontEastAsianWidths.Normal; }
}
public override System.Windows.FontFraction Fraction
{
get { return FontFraction.Normal; }
}
public override bool HistoricalForms
{
get { return false; }
}
public override bool HistoricalLigatures
{
get { return false; }
}
public override bool Kerning
{
get { return true; }
}
public override bool MathematicalGreek
{
get { return false; }
}
public override System.Windows.FontNumeralAlignment NumeralAlignment
{
get { return FontNumeralAlignment.Normal; }
}
public override System.Windows.FontNumeralStyle NumeralStyle
{
get { return FontNumeralStyle.Normal; }
}
public override bool SlashedZero
{
get { return false; }
}
public override bool StandardLigatures
{
get { return false; }
}
public override int StandardSwashes
{
get { return 0; }
}
public override int StylisticAlternates
{
get { return 0; }
}
public override bool StylisticSet1
{
get { return false; }
}
public override bool StylisticSet10
{
get { return false; }
}
public override bool StylisticSet11
{
get { return false; }
}
public override bool StylisticSet12
{
get { return false; }
}
public override bool StylisticSet13
{
get { return false; }
}
public override bool StylisticSet14
{
get { return false; }
}
public override bool StylisticSet15
{
get { return false; }
}
public override bool StylisticSet16
{
get { return false; }
}
public override bool StylisticSet17
{
get { return false; }
}
public override bool StylisticSet18
{
get { return false; }
}
public override bool StylisticSet19
{
get { return false; }
}
public override bool StylisticSet2
{
get { return false; }
}
public override bool StylisticSet20
{
get { return false; }
}
public override bool StylisticSet3
{
get { return false; }
}
public override bool StylisticSet4
{
get { return false; }
}
public override bool StylisticSet5
{
get { return false; }
}
public override bool StylisticSet6
{
get { return false; }
}
public override bool StylisticSet7
{
get { return false; }
}
public override bool StylisticSet8
{
get { return false; }
}
public override bool StylisticSet9
{
get { return false; }
}
public override FontVariants Variants
{
get { return _superscript ? FontVariants.Superscript: FontVariants.Normal; }
}
}
И аналогичный класс для форматирования абзацев (взят из примера MSDN TextFormatter):
class GenericTextParagraphProperties : TextParagraphProperties
{
public GenericTextParagraphProperties(
FlowDirection flowDirection,
TextAlignment textAlignment,
bool firstLineInParagraph,
bool alwaysCollapsible,
TextRunProperties defaultTextRunProperties,
TextWrapping textWrap,
double lineHeight,
double indent)
{
_flowDirection = flowDirection;
_textAlignment = textAlignment;
_firstLineInParagraph = firstLineInParagraph;
_alwaysCollapsible = alwaysCollapsible;
_defaultTextRunProperties = defaultTextRunProperties;
_textWrap = textWrap;
_lineHeight = lineHeight;
_indent = indent;
}
public override FlowDirection FlowDirection
{
get { return _flowDirection; }
}
public override TextAlignment TextAlignment
{
get { return _textAlignment; }
}
public override bool FirstLineInParagraph
{
get { return _firstLineInParagraph; }
}
public override bool AlwaysCollapsible
{
get { return _alwaysCollapsible; }
}
public override TextRunProperties DefaultTextRunProperties
{
get { return _defaultTextRunProperties; }
}
public override TextWrapping TextWrapping
{
get { return _textWrap; }
}
public override double LineHeight
{
get { return _lineHeight; }
}
public override double Indent
{
get { return _indent; }
}
public override TextMarkerProperties TextMarkerProperties
{
get { return null; }
}
public override double ParagraphIndent
{
get { return _paragraphIndent; }
}
private FlowDirection _flowDirection;
private TextAlignment _textAlignment;
private bool _firstLineInParagraph;
private bool _alwaysCollapsible;
private TextRunProperties _defaultTextRunProperties;
private TextWrapping _textWrap;
private double _indent;
private double _paragraphIndent;
private double _lineHeight;
}
Теперь реализация TextSource:
public class CustomTextSourceRun
{
public string Text;
public bool IsSuperscript;
public bool IsEndParagraph;
public int Length { get { return IsEndParagraph ? 1 : Text.Length; } }
}
class CustomTextSource : TextSource
{
public List<CustomTextSourceRun> Runs = new List<CustomTextSourceRun>();
public override TextRun GetTextRun(int textSourceCharacterIndex)
{
int pos = 0;
foreach (var currentRun in Runs)
{
if (textSourceCharacterIndex < pos + currentRun.Length)
{
if (currentRun.IsEndParagraph)
{
return new TextEndOfParagraph(1);
}
var props =
new CustomTextRunProperties(currentRun.IsSuperscript);
return new TextCharacters(
currentRun.Text,
textSourceCharacterIndex - pos,
currentRun.Length - (textSourceCharacterIndex - pos),
props);
}
pos += currentRun.Length;
}
// Return an end-of-paragraph if no more text source.
return new TextEndOfParagraph(1);
}
public override TextSpan<CultureSpecificCharacterBufferRange> GetPrecedingText(int textSourceCharacterIndexLimit)
{
throw new Exception("The method or operation is not implemented.");
}
public override int GetTextEffectCharacterIndexFromTextSourceCharacterIndex(int textSourceCharacterIndex)
{
throw new Exception("The method or operation is not implemented.");
}
public int Length
{
get
{
int r = 0;
foreach (var currentRun in Runs)
{
r += currentRun.Length;
}
return r;
}
}
}
И все, что осталось сделать, это инициализировать CustomTextSource и нарисовать текст:
var textStore = new CustomTextSource();
textStore.Runs.Add(new CustomTextSourceRun() { Text = "3" });
textStore.Runs.Add(new CustomTextSourceRun() { Text = "rd", IsSuperscript = true });
textStore.Runs.Add(new CustomTextSourceRun() { IsEndParagraph = true });
textStore.Runs.Add(new CustomTextSourceRun() { Text = "4" });
textStore.Runs.Add(new CustomTextSourceRun() { Text = "th", IsSuperscript = true });
int textStorePosition = 0;
System.Windows.Point linePosition = new System.Windows.Point(0, 0);
textDest = new DrawingGroup();
DrawingContext dc = textDest.Open();
TextFormatter formatter = TextFormatter.Create();
while (textStorePosition < textStore.Length)
{
using (TextLine myTextLine = formatter.FormatLine(
textStore,
textStorePosition,
96*6,
new GenericTextParagraphProperties(FlowDirection.LeftToRight,
TextAlignment.Left,true,false, new CustomTextRunProperties(false), TextWrapping.Wrap,
30,0), null))
{
myTextLine.Draw(dc, linePosition, InvertAxes.None);
textStorePosition += myTextLine.Length;
linePosition.Y += myTextLine.Height;
}
}
dc.Close();
И это все - у нас есть текст надстрочного индекса в контексте рисования.