|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
// Description: Glyphs element for fixed text rendering.
//
// Spec: Glyphs element and GlyphRun object.htm
//
using System.Globalization;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Markup;
using System.ComponentModel;
using MS.Internal.Utility;
using BuildInfo = MS.Internal.PresentationFramework.BuildInfo;
namespace System.Windows.Documents
{
/// <summary>
/// Glyphs shape represents GlyphRun in markup
/// </summary>
public sealed class Glyphs : FrameworkElement, IUriContext
{
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
#region Constructors
/// <summary>
/// Constructor
/// </summary>
public Glyphs()
{
}
#endregion Constructors
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
#region Public Methods
/// <summary>
/// Creates a GlyphRun object from the properties on a Glyphs object.
/// </summary>
/// <returns>GlyphRun object that corresponds to the properties set on this Glyphs object.</returns>
public GlyphRun ToGlyphRun()
{
ComputeMeasurementGlyphRunAndOrigin();
if (_measurementGlyphRun == null)
return null;
Debug.Assert(_glyphRunProperties != null);
return _measurementGlyphRun;
}
#endregion Public Methods
#region IUriContext implementation
/// <summary>
/// IUriContext interface is implemented by Glyphs element so that it
/// can hold on to the base URI used by parser.
/// The base URI is needed to resolve FontUri property.
/// </summary>
/// <value>Base Uri</value>
Uri IUriContext.BaseUri
{
get
{
return (Uri)GetValue(BaseUriHelper.BaseUriProperty);
}
set
{
SetValue(BaseUriHelper.BaseUriProperty, value);
}
}
#endregion IUriContext implementation
#region Layout and rendering
/// <summary>
/// ArrangeOverride sets the "shapeBounds" in for the shape.
/// </summary>
protected override Size ArrangeOverride(Size finalSize)
{
base.ArrangeOverride(finalSize);
if (_measurementGlyphRun != null)
_measurementGlyphRun.ComputeInkBoundingBox();
return finalSize;
}
/// <summary>
/// Renders GlyphRun into a drawing context
/// </summary>
/// <param name="context">Drawing context</param>
protected override void OnRender(DrawingContext context)
{
if (_glyphRunProperties == null || _measurementGlyphRun == null)
return;
context.PushGuidelineY1(_glyphRunOrigin.Y);
try
{
context.DrawGlyphRun(Fill, _measurementGlyphRun);
}
finally
{
context.Pop();
}
}
/// <summary>
/// Measurement override for Glyphs
/// </summary>
/// <param name="constraint">Input constraint</param>
/// <returns></returns>
protected override Size MeasureOverride(Size constraint)
{
ComputeMeasurementGlyphRunAndOrigin();
if (_measurementGlyphRun == null)
return new Size();
Rect designRect = _measurementGlyphRun.ComputeAlignmentBox();
designRect.Offset(_glyphRunOrigin.X, _glyphRunOrigin.Y);
return new Size(
Math.Max(0, designRect.Right),
Math.Max(0, designRect.Bottom)
);
}
#endregion Layout and rendering
#region Parsing and GlyphRun creation
private void ComputeMeasurementGlyphRunAndOrigin()
{
if (_glyphRunProperties == null)
{
_measurementGlyphRun = null;
ParseGlyphRunProperties();
if (_glyphRunProperties == null)
{
return;
}
}
else if (_measurementGlyphRun != null)
{
return;
}
bool leftToRight = ((BidiLevel & 1) == 0);
bool haveOriginX = !double.IsNaN(OriginX);
bool haveOriginY = !double.IsNaN(OriginY);
bool measurementGlyphRunOriginValid = false;
Rect alignmentRect = new Rect();
if (haveOriginX && haveOriginY && leftToRight)
{
_measurementGlyphRun = _glyphRunProperties.CreateGlyphRun(new Point(OriginX,OriginY), Language);
measurementGlyphRunOriginValid = true;
}
else
{
_measurementGlyphRun = _glyphRunProperties.CreateGlyphRun(new Point(), Language);
// compute alignment box for origins
alignmentRect = _measurementGlyphRun.ComputeAlignmentBox();
}
if (haveOriginX)
_glyphRunOrigin.X = OriginX;
else
_glyphRunOrigin.X = leftToRight ? 0 : alignmentRect.Width;
if (haveOriginY)
_glyphRunOrigin.Y = OriginY;
else
_glyphRunOrigin.Y = -alignmentRect.Y;
if (!measurementGlyphRunOriginValid)
{
_measurementGlyphRun = _glyphRunProperties.CreateGlyphRun(_glyphRunOrigin, Language);
}
}
private void ParseCaretStops(LayoutDependentGlyphRunProperties glyphRunProperties)
{
string caretStopsString = CaretStops;
if (String.IsNullOrEmpty(caretStopsString))
{
glyphRunProperties.caretStops = null;
return;
}
// Caret stop count should be equal to the number of UTF16 code points in the glyph run plus one.
// Logic below is similar to GlyphRun.CodepointCount property.
int caretStopCount;
if (!String.IsNullOrEmpty(glyphRunProperties.unicodeString))
caretStopCount = glyphRunProperties.unicodeString.Length + 1;
else
{
if (glyphRunProperties.clusterMap != null && glyphRunProperties.clusterMap.Length != 0)
caretStopCount = glyphRunProperties.clusterMap.Length + 1;
else
{
Debug.Assert(glyphRunProperties.glyphIndices != null);
caretStopCount = glyphRunProperties.glyphIndices.Length + 1;
}
}
bool[] caretStops = new bool[caretStopCount];
int i = 0;
foreach (char c in caretStopsString)
{
if (Char.IsWhiteSpace(c))
continue;
int nibble;
if ('0' <= c && c <= '9')
nibble = c - '0';
else if ('a' <= c && c <= 'f')
nibble = c - 'a' + 10;
else if ('A' <= c && c <= 'F')
nibble = c - 'A' + 10;
else
throw new ArgumentException(SR.GlyphsCaretStopsContainsHexDigits, "CaretStops");
Debug.Assert(0 <= nibble && nibble <= 15);
if ((nibble & 8) != 0)
{
if (i >= caretStops.Length)
throw new ArgumentException(SR.GlyphsCaretStopsLengthCorrespondsToUnicodeString, "CaretStops");
caretStops[i] = true;
}
++i;
if ((nibble & 4) != 0)
{
if (i >= caretStops.Length)
throw new ArgumentException(SR.GlyphsCaretStopsLengthCorrespondsToUnicodeString, "CaretStops");
caretStops[i] = true;
}
++i;
if ((nibble & 2) != 0)
{
if (i >= caretStops.Length)
throw new ArgumentException(SR.GlyphsCaretStopsLengthCorrespondsToUnicodeString, "CaretStops");
caretStops[i] = true;
}
++i;
if ((nibble & 1) != 0)
{
if (i >= caretStops.Length)
throw new ArgumentException(SR.GlyphsCaretStopsLengthCorrespondsToUnicodeString, "CaretStops");
caretStops[i] = true;
}
++i;
}
// If the number of entries in the caret stop specification string is less than the number of code points,
// set the remaining caret stop values to true.
while (i < caretStops.Length)
{
caretStops[i++] = true;
}
glyphRunProperties.caretStops = caretStops;
}
private void ParseGlyphRunProperties()
{
LayoutDependentGlyphRunProperties glyphRunProperties = null;
Uri uri = FontUri;
if (uri != null)
{
// Indices and UnicodeString cannot both be empty.
if (String.IsNullOrEmpty(UnicodeString) && String.IsNullOrEmpty(Indices))
throw new ArgumentException(SR.GlyphsUnicodeStringAndIndicesCannotBothBeEmpty);
glyphRunProperties = new LayoutDependentGlyphRunProperties(GetDpi().PixelsPerDip);
if (!uri.IsAbsoluteUri)
{
uri = BindUriHelper.GetResolvedUri(BaseUriHelper.GetBaseUri(this), uri);
}
glyphRunProperties.glyphTypeface = new GlyphTypeface(uri, StyleSimulations);
glyphRunProperties.unicodeString = UnicodeString;
glyphRunProperties.sideways = IsSideways;
glyphRunProperties.deviceFontName = DeviceFontName;
// parse the Indices property
List<ParsedGlyphData> parsedGlyphs;
int glyphCount = ParseGlyphsProperty(
glyphRunProperties.glyphTypeface,
glyphRunProperties.unicodeString,
glyphRunProperties.sideways,
out parsedGlyphs,
out glyphRunProperties.clusterMap);
Debug.Assert(parsedGlyphs.Count == glyphCount);
glyphRunProperties.glyphIndices = new ushort[glyphCount];
glyphRunProperties.advanceWidths = new double[glyphCount];
ParseCaretStops(glyphRunProperties);
// Delay creating glyphOffsets array because in many common cases it will contain only zeroed entries.
glyphRunProperties.glyphOffsets = null;
int i = 0;
glyphRunProperties.fontRenderingSize = FontRenderingEmSize;
glyphRunProperties.bidiLevel = BidiLevel;
double fromEmToMil = glyphRunProperties.fontRenderingSize / EmMultiplier;
foreach (ParsedGlyphData parsedGlyphData in parsedGlyphs)
{
glyphRunProperties.glyphIndices[i] = parsedGlyphData.glyphIndex;
// convert advances and offsets from integers in em space to doubles coordinates in MIL space
glyphRunProperties.advanceWidths[i] = parsedGlyphData.advanceWidth * fromEmToMil;
if (parsedGlyphData.offsetX != 0 || parsedGlyphData.offsetY != 0)
{
// Lazily create glyph offset array. Previous entries will be correctly set to zero
// by the default Point ctor.
if (glyphRunProperties.glyphOffsets == null)
glyphRunProperties.glyphOffsets = new Point[glyphCount];
glyphRunProperties.glyphOffsets[i].X = parsedGlyphData.offsetX * fromEmToMil;
glyphRunProperties.glyphOffsets[i].Y = parsedGlyphData.offsetY * fromEmToMil;
}
++i;
}
}
_glyphRunProperties = glyphRunProperties;
}
private static bool IsEmpty(ReadOnlySpan<char> s)
{
foreach (char c in s)
{
if (!Char.IsWhiteSpace(c))
return false;
}
return true;
}
/// <summary>
/// Read GlyphIndex specification - glyph index value with an optional glyph cluster prefix.
/// </summary>
/// <param name="valueSpec"></param>
/// <param name="inCluster"></param>
/// <param name="glyphClusterSize"></param>
/// <param name="characterClusterSize"></param>
/// <param name="glyphIndex"></param>
/// <returns>true if glyph index is present, false if glyph index is not present.</returns>
private bool ReadGlyphIndex(
ReadOnlySpan<char> valueSpec,
ref bool inCluster,
ref int glyphClusterSize,
ref int characterClusterSize,
ref ushort glyphIndex)
{
// the format is ... [(CharacterClusterSize[:GlyphClusterSize])] GlyphIndex ...
ReadOnlySpan<char> glyphIndexString = valueSpec;
int firstBracket = valueSpec.IndexOf('(');
if (firstBracket != -1)
{
// Only spaces are allowed before the bracket
for (int i=0; i<firstBracket; i++)
{
if (!Char.IsWhiteSpace(valueSpec[i]))
throw new ArgumentException(SR.GlyphsClusterBadCharactersBeforeBracket);
}
if (inCluster)
throw new ArgumentException(SR.GlyphsClusterNoNestedClusters);
int secondBracket = valueSpec.IndexOf(')');
if (secondBracket == -1 || secondBracket <= firstBracket + 1)
throw new ArgumentException(SR.GlyphsClusterNoMatchingBracket);
// look for colon separator
int colon = valueSpec.IndexOf(':');
if (colon == -1)
{
// parse glyph cluster size
ReadOnlySpan<char> characterClusterSpec = valueSpec.Slice(firstBracket + 1, secondBracket - (firstBracket + 1));
characterClusterSize = int.Parse(characterClusterSpec, provider: CultureInfo.InvariantCulture);
glyphClusterSize = 1;
}
else
{
if (colon <= firstBracket + 1 || colon >= secondBracket - 1)
throw new ArgumentException(SR.GlyphsClusterMisplacedSeparator);
ReadOnlySpan<char> characterClusterSpec = valueSpec.Slice(firstBracket + 1, colon - (firstBracket + 1));
characterClusterSize = int.Parse(characterClusterSpec, provider: CultureInfo.InvariantCulture);
ReadOnlySpan<char> glyphClusterSpec = valueSpec.Slice(colon + 1, secondBracket - (colon + 1));
glyphClusterSize = int.Parse(glyphClusterSpec, provider: CultureInfo.InvariantCulture);
}
inCluster = true;
glyphIndexString = valueSpec.Slice(secondBracket + 1);
}
if (IsEmpty(glyphIndexString))
return false;
glyphIndex = ushort.Parse(glyphIndexString, provider: CultureInfo.InvariantCulture);
return true;
}
private static double GetAdvanceWidth(GlyphTypeface glyphTypeface, ushort glyphIndex, bool sideways)
{
double advance = sideways ? glyphTypeface.AdvanceHeights[glyphIndex] : glyphTypeface.AdvanceWidths[glyphIndex];
return advance * EmMultiplier;
}
private ushort GetGlyphFromCharacter(GlyphTypeface glyphTypeface, char character)
{
ushort glyphIndex;
// TryGetValue will return zero glyph index for missing code points,
// which is the right thing to display per http://www.microsoft.com/typography/otspec/cmap.htm
glyphTypeface.CharacterToGlyphMap.TryGetValue(character, out glyphIndex);
return glyphIndex;
}
/// <summary>
/// Performs validation against cluster map size and throws a well defined exception.
/// </summary>
private static void SetClusterMapEntry(ushort[] clusterMap, int index, ushort value)
{
if (index < 0 || index >= clusterMap.Length)
throw new ArgumentException(SR.GlyphsUnicodeStringIsTooShort);
clusterMap[index] = value;
}
private class ParsedGlyphData
{
public ushort glyphIndex;
public double advanceWidth;
public double offsetX;
public double offsetY;
};
// -----------------------------------------------------------------------------
// Parses a semicolon-delimited list of glyph specifiers, each of which consists
// of up to 4 comma-delimited values:
// - glyph index (ushort)
// - glyph advance (double)
// - glyph offset X (double)
// - glyph offset Y (double)
// A glyph entry can be have a cluster size prefix (int or pair of ints separated by a colon)
// Whitespace adjacent to a delimiter (comma or semicolon) is ignored.
// Returns the number of glyph specs parsed (number of semicolons plus 1).
// Need to confirm the treatment of missing specifiers - the following code takes ""
// to mean one glyph of all default values; ";" to mean two glyphs of all defaults;
// "77,231;" to mean two glyphs, the second one all defaults. Right?
private int ParseGlyphsProperty(
GlyphTypeface fontFace,
string unicodeString,
bool sideways,
out List<ParsedGlyphData> parsedGlyphs,
out ushort[] clusterMap)
{
string glyphsProp = Indices;
// init for the whole parse, including the result arrays
int parsedGlyphCount = 0;
int parsedCharacterCount = 0;
int characterClusterSize = 1;
int glyphClusterSize = 1;
bool inCluster = false;
// make reasonable capacity guess on how many glyphs we can expect
int estimatedNumberOfGlyphs;
if (!String.IsNullOrEmpty(unicodeString))
{
clusterMap = new ushort[unicodeString.Length];
estimatedNumberOfGlyphs = unicodeString.Length;
}
else
{
clusterMap = null;
estimatedNumberOfGlyphs = 8;
}
if (!String.IsNullOrEmpty(glyphsProp))
estimatedNumberOfGlyphs = Math.Max(estimatedNumberOfGlyphs, glyphsProp.Length / 5);
parsedGlyphs = new List<ParsedGlyphData>(estimatedNumberOfGlyphs);
ParsedGlyphData parsedGlyphData = new ParsedGlyphData();
#region Parse Glyphs string
if (!String.IsNullOrEmpty(glyphsProp))
{
// init per-glyph values for the first glyph/position
int valueWithinGlyph = 0; // which value we're on (how many commas have we seen in this glyph)?
int valueStartIndex = 0; // where (what index of Glyphs prop string) did this value start?
// iterate and parse the characters of the Indices property
for (int i = 0; i <= glyphsProp.Length; i++)
{
// get next char or pseudo-terminator
char c = i < glyphsProp.Length ? glyphsProp[i] : '\0';
// finished scanning the current per-glyph value?
if ((c == ',') || (c == ';') || (i == glyphsProp.Length))
{
int len = i - valueStartIndex;
ReadOnlySpan<char> valueSpec = glyphsProp.AsSpan(valueStartIndex, len);
#region Interpret one comma-delimited value
switch (valueWithinGlyph)
{
case 0:
bool wasInCluster = inCluster;
// interpret cluster size and glyph index spec
if (!ReadGlyphIndex(
valueSpec,
ref inCluster,
ref glyphClusterSize,
ref characterClusterSize,
ref parsedGlyphData.glyphIndex))
{
if (String.IsNullOrEmpty(unicodeString))
throw new ArgumentException(SR.GlyphsIndexRequiredIfNoUnicode);
if (unicodeString.Length <= parsedCharacterCount)
throw new ArgumentException(SR.GlyphsUnicodeStringIsTooShort);
parsedGlyphData.glyphIndex = GetGlyphFromCharacter(fontFace, unicodeString[parsedCharacterCount]);
}
if (!wasInCluster && clusterMap != null)
{
// fill out cluster map at the start of each cluster
if (inCluster)
{
for (int ch = parsedCharacterCount; ch < parsedCharacterCount + characterClusterSize; ++ch)
{
SetClusterMapEntry(clusterMap, ch, (ushort)parsedGlyphCount);
}
}
else
{
SetClusterMapEntry(clusterMap, parsedCharacterCount, (ushort)parsedGlyphCount);
}
}
parsedGlyphData.advanceWidth = GetAdvanceWidth(fontFace, parsedGlyphData.glyphIndex, sideways);
break;
case 1:
// interpret glyph advance spec
if (!IsEmpty(valueSpec))
{
parsedGlyphData.advanceWidth = double.Parse(valueSpec, provider: CultureInfo.InvariantCulture);
if (parsedGlyphData.advanceWidth < 0)
throw new ArgumentException(SR.GlyphsAdvanceWidthCannotBeNegative);
}
break;
case 2:
// interpret glyph offset X
if (!IsEmpty(valueSpec))
parsedGlyphData.offsetX = double.Parse(valueSpec, provider: CultureInfo.InvariantCulture);
break;
case 3:
// interpret glyph offset Y
if (!IsEmpty(valueSpec))
parsedGlyphData.offsetY = double.Parse(valueSpec, provider: CultureInfo.InvariantCulture);
break;
default:
// too many commas; can't interpret
throw new ArgumentException(SR.GlyphsTooManyCommas);
}
#endregion Interpret one comma-delimited value
// prepare to scan next value (if any)
valueWithinGlyph++;
valueStartIndex = i + 1;
}
// finished processing the current glyph?
if ((c == ';') || (i == glyphsProp.Length))
{
parsedGlyphs.Add(parsedGlyphData);
parsedGlyphData = new ParsedGlyphData();
if (inCluster)
{
--glyphClusterSize;
// when we reach the end of a glyph cluster, increment character index
if (glyphClusterSize == 0)
{
parsedCharacterCount += characterClusterSize;
inCluster = false;
}
}
else
{
++parsedCharacterCount;
}
parsedGlyphCount++;
// initalize new per-glyph values
valueWithinGlyph = 0; // which value we're on (how many commas have we seen in this glyph)?
valueStartIndex = i + 1; // where (what index of Glyphs prop string) did this value start?
}
}
}
#endregion
// fill the remaining glyphs with defaults, assuming 1:1 mapping
if (unicodeString != null)
{
while (parsedCharacterCount < unicodeString.Length)
{
if (inCluster)
throw new ArgumentException(SR.GlyphsIndexRequiredWithinCluster);
if (unicodeString.Length <= parsedCharacterCount)
throw new ArgumentException(SR.GlyphsUnicodeStringIsTooShort);
parsedGlyphData.glyphIndex = GetGlyphFromCharacter(fontFace, unicodeString[parsedCharacterCount]);
parsedGlyphData.advanceWidth = GetAdvanceWidth(fontFace, parsedGlyphData.glyphIndex, sideways);
parsedGlyphs.Add(parsedGlyphData);
parsedGlyphData = new ParsedGlyphData();
SetClusterMapEntry(clusterMap, parsedCharacterCount, (ushort)parsedGlyphCount);
++parsedCharacterCount;
++parsedGlyphCount;
}
}
// return number of glyphs actually specified
return parsedGlyphCount;
}
#endregion Parsing and GlyphRun creation
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
#region Public Properties
private static void FillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Called when Fill is changed.
// If a SubPropertyInvalidatin is in progress this means a Freezable
// has changed and we don't need to invalidate layout. Otherwise
// we have to invalidate
((UIElement)d).InvalidateVisual();
}
private static void GlyphRunPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Called when any property is changed that would require a new call to ParseGlyphRunProperties
((Glyphs)d)._glyphRunProperties = null;
}
private static void OriginPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Called when OriginX or OriginY is changed that would require recreation of the positioned GlyphRun
// The _measurementGlyphRun will get updated as a result of layout
((Glyphs)d)._measurementGlyphRun = null;
}
/// <summary>
/// Fill property
/// </summary>
public static readonly DependencyProperty FillProperty
= DependencyProperty.Register(
"Fill",
typeof(Brush),
typeof(Glyphs),
new FrameworkPropertyMetadata(
(Brush)null,
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(FillChanged),
null)
);
/// <summary>
/// Fill property
/// </summary>
public Brush Fill
{
get
{
return (Brush)GetValue(FillProperty);
}
set
{
SetValue(FillProperty, value);
}
}
/// <summary>
/// Indices property
/// </summary>
public static readonly DependencyProperty IndicesProperty =
DependencyProperty.Register( "Indices", typeof(string), typeof(Glyphs),
new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(GlyphRunPropertyChanged)));
/// <summary>
/// Indices property accessor
/// </summary>
public string Indices
{
get
{
return (string)GetValue(IndicesProperty);
}
set
{
SetValue(IndicesProperty, value);
}
}
/// <summary>
/// UnicodeString property
/// </summary>
public static readonly DependencyProperty UnicodeStringProperty =
DependencyProperty.Register( "UnicodeString", typeof(string), typeof(Glyphs),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(GlyphRunPropertyChanged)));
/// <summary>
/// UnicodeString property accessor
/// </summary>
public string UnicodeString
{
get
{
return (string)GetValue(UnicodeStringProperty);;
}
set
{
SetValue(UnicodeStringProperty, value);
}
}
/// <summary>
/// CaretStops property. The property syntax is a string of hexadecimal digits that describe an array of Boolean values that
/// correspond to every code point in UnicodeString property.
/// </summary>
public static readonly DependencyProperty CaretStopsProperty =
DependencyProperty.Register( "CaretStops", typeof(string), typeof(Glyphs),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(GlyphRunPropertyChanged)));
/// <summary>
/// CaretStops property accessor
/// </summary>
public string CaretStops
{
get
{
return (string)GetValue(CaretStopsProperty);;
}
set
{
SetValue(CaretStopsProperty, value);
}
}
/// <summary>
/// FontRenderingEmSize property
/// </summary>
public static readonly DependencyProperty FontRenderingEmSizeProperty =
DependencyProperty.Register( "FontRenderingEmSize", typeof(double), typeof(Glyphs),
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(GlyphRunPropertyChanged)));
/// <summary>
/// FontRenderingEmSize property accessor
/// </summary>
[TypeConverter($"System.Windows.FontSizeConverter, PresentationFramework, Version={BuildInfo.WCP_VERSION}, Culture=neutral, PublicKeyToken={BuildInfo.WCP_PUBLIC_KEY_TOKEN}, Custom=null")]
public double FontRenderingEmSize
{
get
{
return (double)GetValue(FontRenderingEmSizeProperty);
}
set
{
SetValue(FontRenderingEmSizeProperty, value);
}
}
/// <summary>
/// OriginX property
/// </summary>
public static readonly DependencyProperty OriginXProperty =
DependencyProperty.Register( "OriginX", typeof(double), typeof(Glyphs),
new FrameworkPropertyMetadata(Double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(OriginPropertyChanged)));
/// <summary>
/// OriginX property accessor
/// </summary>
[TypeConverter($"System.Windows.LengthConverter, PresentationFramework, Version={BuildInfo.WCP_VERSION}, Culture=neutral, PublicKeyToken={BuildInfo.WCP_PUBLIC_KEY_TOKEN}, Custom=null")]
public double OriginX
{
get
{
return (double)GetValue(OriginXProperty);
}
set
{
SetValue(OriginXProperty, value);
}
}
/// <summary>
/// OriginY property
/// </summary>
public static readonly DependencyProperty OriginYProperty =
DependencyProperty.Register( "OriginY", typeof(double), typeof(Glyphs),
new FrameworkPropertyMetadata(Double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(OriginPropertyChanged)));
/// <summary>
/// OriginY property accessor
/// </summary>
[TypeConverter($"System.Windows.LengthConverter, PresentationFramework, Version={BuildInfo.WCP_VERSION}, Culture=neutral, PublicKeyToken={BuildInfo.WCP_PUBLIC_KEY_TOKEN}, Custom=null")]
public double OriginY
{
get
{
return (double)GetValue(OriginYProperty);
}
set
{
SetValue(OriginYProperty, value);
}
}
/// <summary>
/// FontUri property
/// </summary>
public static readonly DependencyProperty FontUriProperty =
DependencyProperty.Register( "FontUri", typeof(Uri), typeof(Glyphs),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(GlyphRunPropertyChanged)));
/// <summary>
/// FontUri property accessor
/// </summary>
public Uri FontUri
{
get
{
return (Uri)GetValue(FontUriProperty);
}
set
{
SetValue(FontUriProperty, value);
}
}
/// <summary>
/// StyleSimulations property
/// </summary>
public static readonly DependencyProperty StyleSimulationsProperty =
DependencyProperty.Register( "StyleSimulations", typeof(StyleSimulations), typeof(Glyphs),
new FrameworkPropertyMetadata(StyleSimulations.None, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(GlyphRunPropertyChanged)));
/// <summary>
/// StyleSimulations property accessor
/// </summary>
public StyleSimulations StyleSimulations
{
get
{
return (StyleSimulations)GetValue(StyleSimulationsProperty);
}
set
{
SetValue(StyleSimulationsProperty, value);
}
}
/// <summary>
/// Sideways property
/// </summary>
public static readonly DependencyProperty IsSidewaysProperty =
DependencyProperty.Register( "IsSideways", typeof(bool), typeof(Glyphs),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(GlyphRunPropertyChanged)));
/// <summary>
/// Specifies whether to rotate characters/glyphs 90 degrees anti-clockwise
/// and use vertical baseline positioning metrics.
/// </summary>
/// <value>true if the rotation should be applied, false otherwise.</value>
public bool IsSideways
{
get
{
return (bool)GetValue(IsSidewaysProperty);
}
set
{
SetValue(IsSidewaysProperty, value);
}
}
/// <summary>
/// BidiLevel property
/// </summary>
public static readonly DependencyProperty BidiLevelProperty =
DependencyProperty.Register( "BidiLevel", typeof(int), typeof(Glyphs),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(GlyphRunPropertyChanged)));
/// <summary>
/// Determines LTR/RTL reading order and bidi nesting.
/// </summary>
/// <value>The value of bidirectional nesting level.</value>
public int BidiLevel
{
get
{
return (int)GetValue(BidiLevelProperty);
}
set
{
SetValue(BidiLevelProperty, value);
}
}
/// <summary>
/// DeviceFontName property
/// </summary>
public static readonly DependencyProperty DeviceFontNameProperty =
DependencyProperty.Register("DeviceFontName", typeof(string), typeof(Glyphs),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(GlyphRunPropertyChanged)));
/// <summary>
/// Identifies a specific device font for which the Glyphs element has been optimized. When a Glyphs element is
/// being rendered on a device that has built-in support for this named font, then the Glyphs element should be rendered using a
/// possibly device specific mechanism for selecting that font, and by sending the Unicode codepoints rather than the
/// glyph indices. When rendering onto a device that does not include built-in support for the named font,
/// this property should be ignored.
/// </summary>
public string DeviceFontName
{
get
{
return (string) GetValue(DeviceFontNameProperty);
}
set
{
SetValue(DeviceFontNameProperty, value);
}
}
#endregion Public Properties
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
/// <summary>
/// This property was added for performance reasons. It allows D2 code
/// to access the cached measurement glyph run instead of generating
/// a new GlyphRun object by calling ToGlyphRun()
/// </summary>
internal GlyphRun MeasurementGlyphRun
{
get
{
if (_glyphRunProperties == null || _measurementGlyphRun == null)
{
ComputeMeasurementGlyphRunAndOrigin();
}
return _measurementGlyphRun;
}
}
#endregion Internal Properties
//------------------------------------------------------
//
// Private Classes
//
//------------------------------------------------------
#region Private Classes
/// <summary>
/// This class is temporarily needed because GlyphRun includes rendering information
/// that in future will be passed to DrawGlyphs separately.
/// </summary>
private class LayoutDependentGlyphRunProperties
{
public double fontRenderingSize;
public ushort [] glyphIndices;
public double [] advanceWidths;
public Point [] glyphOffsets;
public ushort [] clusterMap;
public bool sideways;
public int bidiLevel;
public GlyphTypeface glyphTypeface;
public string unicodeString;
public IList<bool> caretStops;
public string deviceFontName;
private float _pixelsPerDip;
public LayoutDependentGlyphRunProperties(double pixelsPerDip)
{
_pixelsPerDip = (float)pixelsPerDip;
}
public GlyphRun CreateGlyphRun(Point origin, XmlLanguage language)
{
return new GlyphRun(
glyphTypeface, // GlyphTypeface
bidiLevel, // Bidi level
sideways, // sideways flag
fontRenderingSize, // rendering em size in MIL units
_pixelsPerDip,
glyphIndices, // glyph indices
origin, // origin of glyph-drawing space
advanceWidths, // glyph advances
glyphOffsets, // glyph offsets
unicodeString.ToCharArray(), // unicode characters
deviceFontName, // device font
clusterMap, // cluster map
caretStops, // caret stops
language // language
);
}
}
#endregion Private Classes
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
#region Private Fields
/// <summary>
/// Caches the result of parsing GlyphRun properties.
/// </summary>
private LayoutDependentGlyphRunProperties _glyphRunProperties;
/// <summary>
/// This GlyphRun instance is needed for measurement purposes only.
/// </summary>
private GlyphRun _measurementGlyphRun;
private Point _glyphRunOrigin = new Point();
private const double EmMultiplier = 100.0;
#endregion Private Fields
};
}
|