|
// 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.
using MS.Internal;
using MS.Internal.TextFormatting;
namespace System.Windows.Media.TextFormatting
{
/// <summary>
/// TextFormatter context. This class encapsulates the unit of reentrancy of TextFormatter.
/// </summary>
/// <remarks>
/// We do not want to make this class finalizable because its lifetime already ties
/// to TextFormatterImp which is finalizable. It is not efficient to have too many
/// finalizable object around in the GC heap.
/// </remarks>
#if OPTIMALBREAK_API
public class TextFormatterContext
#else
internal class TextFormatterContext
#endif
{
private IntPtr _ploc; // Line Services context
private LineServicesCallbacks _callbacks; // object to hold all delegates for callback
private State _state; // internal state flags
private BreakStrategies _breaking; // context's breaking strategy
private static Dictionary<char,bool> _specialCharacters; // special characters
public TextFormatterContext()
{
_ploc = IntPtr.Zero;
Init();
}
private void Init()
{
if(_ploc == System.IntPtr.Zero)
{
// Initializing context
LsErr lserr = LsErr.None;
LsContextInfo contextInfo = new LsContextInfo();
LscbkRedefined lscbkRedef = new LscbkRedefined();
_callbacks = new LineServicesCallbacks();
_callbacks.PopulateContextInfo(ref contextInfo, ref lscbkRedef);
contextInfo.version = 4; // we should set this right, they might check it in the future
contextInfo.pols = IntPtr.Zero; // This will be filled in the un-managed code
contextInfo.cEstimatedCharsPerLine = TextStore.TypicalCharactersPerLine;
contextInfo.fDontReleaseRuns = 1; // dont waste time
// There are 3 justification priorities right now with one considered good
// and the other two provided for emergency expansion.
// Future development to enable international justification will likely change this.
// e.g. Kashida justification would require more than one good priorities.
contextInfo.cJustPriorityLim = 3;
// Fill up text configuration
contextInfo.wchNull = '\u0000';
contextInfo.wchUndef = '\u0001';
contextInfo.wchTab = '\u0009';
contextInfo.wchPosTab = contextInfo.wchUndef;
contextInfo.wchEndPara1 = TextStore.CharParaSeparator; // Unicode para separator
contextInfo.wchEndPara2 = contextInfo.wchUndef;
contextInfo.wchSpace = '\u0020';
contextInfo.wchHyphen = MS.Internal.Text.TextInterface.TextAnalyzer.CharHyphen; //'\x002d';
contextInfo.wchNonReqHyphen = '\u00AD';
contextInfo.wchNonBreakHyphen = '\u2011';
contextInfo.wchEnDash = '\u2013';
contextInfo.wchEmDash = '\u2014';
contextInfo.wchEnSpace = '\u2002';
contextInfo.wchEmSpace = '\u2003';
contextInfo.wchNarrowSpace = '\u2009';
contextInfo.wchJoiner = '\u200D';
contextInfo.wchNonJoiner = '\u200C';
contextInfo.wchVisiNull = '\u2050';
contextInfo.wchVisiAltEndPara = '\u2051';
contextInfo.wchVisiEndLineInPara = '\u2052';
contextInfo.wchVisiEndPara = '\u2053';
contextInfo.wchVisiSpace = '\u2054';
contextInfo.wchVisiNonBreakSpace = '\u2055';
contextInfo.wchVisiNonBreakHyphen = '\u2056';
contextInfo.wchVisiNonReqHyphen = '\u2057';
contextInfo.wchVisiTab = '\u2058';
contextInfo.wchVisiPosTab = contextInfo.wchUndef;
contextInfo.wchVisiEmSpace = '\u2059';
contextInfo.wchVisiEnSpace = '\u205A';
contextInfo.wchVisiNarrowSpace = '\u205B';
contextInfo.wchVisiOptBreak = '\u205C';
contextInfo.wchVisiNoBreak = '\u205D';
contextInfo.wchVisiFESpace = '\u205E';
contextInfo.wchFESpace = '\u3000';
contextInfo.wchEscAnmRun = TextStore.CharParaSeparator;
contextInfo.wchAltEndPara = contextInfo.wchUndef;
contextInfo.wchEndLineInPara = TextStore.CharLineSeparator;
contextInfo.wchSectionBreak = contextInfo.wchUndef;
contextInfo.wchNonBreakSpace = '\u00A0';
contextInfo.wchNoBreak = contextInfo.wchUndef;
contextInfo.wchColumnBreak = contextInfo.wchUndef;
contextInfo.wchPageBreak = contextInfo.wchUndef;
contextInfo.wchOptBreak = contextInfo.wchUndef;
contextInfo.wchToReplace = contextInfo.wchUndef;
contextInfo.wchReplace = contextInfo.wchUndef;
IntPtr ploc = IntPtr.Zero;
IntPtr ppenaltyModule = IntPtr.Zero;
lserr = UnsafeNativeMethods.LoCreateContext(
ref contextInfo,
ref lscbkRedef,
out ploc
);
if (lserr != LsErr.None)
{
ThrowExceptionFromLsError(SR.Format(SR.CreateContextFailure, lserr), lserr);
}
if (_specialCharacters == null)
{
SetSpecialCharacters(ref contextInfo);
}
_ploc = ploc;
GC.KeepAlive(contextInfo);
// There is a trick here to pass in this resolution as in twips
// (1/1440 an inch).
//
// LSCreateLine assumes the max width passed in is in twips so to
// allow its client to express their width in page unit. However
// it asks client to set up the real device resolution here so
// that it can internally translate the "twips" width into client
// actual device unit.
//
// We are not device dependent anyway, so instead of following the
// rule which will cause us to validate the context every time. We
// choose to cheat LS to think that our unit is twips.
//
LsDevRes devRes;
devRes.dxpInch = devRes.dxrInch = TwipsPerInch;
devRes.dypInch = devRes.dyrInch = TwipsPerInch;
SetDoc(
true, // Yes, we will be displaying
true, // Yes, reference and presentation are the same device
ref devRes // Device resolutions
);
SetBreaking(BreakStrategies.BreakCJK);
}
}
/// <summary>
/// Client to get the text penalty module.
/// </summary>
internal TextPenaltyModule GetTextPenaltyModule()
{
Invariant.Assert(_ploc != System.IntPtr.Zero);
return new TextPenaltyModule(_ploc);
}
/// <summary>
/// Unclaim the ownership of the context, release it back to the context pool
/// </summary>
internal void Release()
{
this.CallbackException = null;
this.Owner = null;
}
/// <summary>
/// context's owner
/// </summary>
internal object Owner
{
get { return _callbacks.Owner; }
set { _callbacks.Owner = value; }
}
/// <summary>
/// Exception thrown during LS callback
/// </summary>
internal Exception CallbackException
{
get { return _callbacks.Exception; }
set { _callbacks.Exception = value; }
}
/// <summary>
/// Make min/max empty
/// </summary>
internal void EmptyBoundingBox()
{
_callbacks.EmptyBoundingBox();
}
/// <summary>
/// Bounding box of whole black of line
/// </summary>
internal Rect BoundingBox
{
get { return _callbacks.BoundingBox; }
}
/// <summary>
/// Clear the indexed glyphruns
/// </summary>
internal void ClearIndexedGlyphRuns()
{
_callbacks.ClearIndexedGlyphRuns();
}
/// <summary>
/// Indexed glyphruns of the line
/// </summary>
internal ICollection<IndexedGlyphRun> IndexedGlyphRuns
{
get { return _callbacks.IndexedGlyphRuns; }
}
/// <summary>
/// Destroy LS context
/// </summary>
internal void Destroy()
{
if(_ploc != System.IntPtr.Zero)
{
UnsafeNativeMethods.LoDestroyContext(_ploc);
_ploc = IntPtr.Zero;
}
}
/// <summary>
/// Set LS breaking strategy
/// </summary>
internal void SetBreaking(BreakStrategies breaking)
{
if (_state == State.Uninitialized || breaking != _breaking)
{
Invariant.Assert(_ploc != System.IntPtr.Zero);
LsErr lserr = UnsafeNativeMethods.LoSetBreaking(_ploc, (int) breaking);
if (lserr != LsErr.None)
{
ThrowExceptionFromLsError(SR.Format(SR.SetBreakingFailure, lserr), lserr);
}
_breaking = breaking;
}
_state = State.Initialized;
}
//
// Line Services managed API
//
//
internal LsErr CreateLine(
int cpFirst,
int lineLength,
int maxWidth,
LineFlags lineFlags,
IntPtr previousLineBreakRecord,
out IntPtr ploline,
out LsLInfo plslineInfo,
out int maxDepth,
out LsLineWidths lineWidths
)
{
Invariant.Assert(_ploc != System.IntPtr.Zero);
return UnsafeNativeMethods.LoCreateLine(
_ploc,
cpFirst,
lineLength,
maxWidth,
(uint)lineFlags, // line flags
previousLineBreakRecord,
out plslineInfo,
out ploline,
out maxDepth,
out lineWidths
);
}
internal LsErr CreateBreaks(
int cpFirst,
IntPtr previousLineBreakRecord,
IntPtr ploparabreak,
IntPtr ptslinevariantRestriction,
ref LsBreaks lsbreaks,
out int bestFitIndex
)
{
Invariant.Assert(_ploc != System.IntPtr.Zero);
return UnsafeNativeMethods.LoCreateBreaks(
_ploc,
cpFirst,
previousLineBreakRecord,
ploparabreak,
ptslinevariantRestriction,
ref lsbreaks,
out bestFitIndex
);
}
internal LsErr CreateParaBreakingSession(
int cpFirst,
int maxWidth,
IntPtr previousLineBreakRecord,
ref IntPtr ploparabreak,
ref bool penalizedAsJustified
)
{
Invariant.Assert(_ploc != System.IntPtr.Zero);
return UnsafeNativeMethods.LoCreateParaBreakingSession(
_ploc,
cpFirst,
maxWidth,
previousLineBreakRecord,
ref ploparabreak,
ref penalizedAsJustified
);
}
internal void SetDoc(
bool isDisplay,
bool isReferencePresentationEqual,
ref LsDevRes deviceInfo
)
{
Invariant.Assert(_ploc != System.IntPtr.Zero);
LsErr lserr = UnsafeNativeMethods.LoSetDoc(
_ploc,
isDisplay ? 1 : 0,
isReferencePresentationEqual ? 1 : 0,
ref deviceInfo
);
if(lserr != LsErr.None)
{
ThrowExceptionFromLsError(SR.Format(SR.SetDocFailure, lserr), lserr);
}
}
internal unsafe void SetTabs(
int incrementalTab,
LsTbd* tabStops,
int tabStopCount
)
{
Invariant.Assert(_ploc != System.IntPtr.Zero);
LsErr lserr = UnsafeNativeMethods.LoSetTabs(
_ploc,
incrementalTab,
tabStopCount,
tabStops
);
if(lserr != LsErr.None)
{
ThrowExceptionFromLsError(SR.Format(SR.SetTabsFailure, lserr), lserr);
}
}
static internal void ThrowExceptionFromLsError(string message, LsErr lserr)
{
if (lserr == LsErr.OutOfMemory)
throw new OutOfMemoryException (message);
throw new Exception(message);
}
static internal bool IsSpecialCharacter(char c)
{
return _specialCharacters.ContainsKey(c);
}
static private void SetSpecialCharacters(ref LsContextInfo contextInfo)
{
Dictionary<char,bool> dict = new Dictionary<char,bool>();
/* The first three char fields do not designate special characters
dict[contextInfo.wchUndef] = true;
dict[contextInfo.wchNull] = true;
dict[contextInfo.wchSpace] = true;
*/
dict[contextInfo.wchHyphen] = true;
dict[contextInfo.wchTab] = true;
dict[contextInfo.wchPosTab] = true;
dict[contextInfo.wchEndPara1] = true;
dict[contextInfo.wchEndPara2] = true;
dict[contextInfo.wchAltEndPara] = true;
dict[contextInfo.wchEndLineInPara] = true;
dict[contextInfo.wchColumnBreak] = true;
dict[contextInfo.wchSectionBreak] = true;
dict[contextInfo.wchPageBreak] = true;
dict[contextInfo.wchNonBreakSpace] = true;
dict[contextInfo.wchNonBreakHyphen] = true;
dict[contextInfo.wchNonReqHyphen] = true;
dict[contextInfo.wchEmDash] = true;
dict[contextInfo.wchEnDash] = true;
dict[contextInfo.wchEmSpace] = true;
dict[contextInfo.wchEnSpace] = true;
dict[contextInfo.wchNarrowSpace] = true;
dict[contextInfo.wchOptBreak] = true;
dict[contextInfo.wchNoBreak] = true;
dict[contextInfo.wchFESpace] = true;
dict[contextInfo.wchJoiner] = true;
dict[contextInfo.wchNonJoiner] = true;
dict[contextInfo.wchToReplace] = true;
dict[contextInfo.wchReplace] = true;
dict[contextInfo.wchVisiNull] = true;
dict[contextInfo.wchVisiAltEndPara] = true;
dict[contextInfo.wchVisiEndLineInPara] = true;
dict[contextInfo.wchVisiEndPara] = true;
dict[contextInfo.wchVisiSpace] = true;
dict[contextInfo.wchVisiNonBreakSpace] = true;
dict[contextInfo.wchVisiNonBreakHyphen] = true;
dict[contextInfo.wchVisiNonReqHyphen] = true;
dict[contextInfo.wchVisiTab] = true;
dict[contextInfo.wchVisiPosTab] = true;
dict[contextInfo.wchVisiEmSpace] = true;
dict[contextInfo.wchVisiEnSpace] = true;
dict[contextInfo.wchVisiNarrowSpace] = true;
dict[contextInfo.wchVisiOptBreak] = true;
dict[contextInfo.wchVisiNoBreak] = true;
dict[contextInfo.wchVisiFESpace] = true;
dict[contextInfo.wchEscAnmRun] = true;
dict[contextInfo.wchPad] = true;
// Many of these fields have value 'wchUndef'. Remove it now.
// (This is robust, even if Init() changes the char field assignments.)
dict.Remove(contextInfo.wchUndef);
// Remember the result. First thread to get here wins.
System.Threading.Interlocked.CompareExchange<Dictionary<char,bool>>(ref _specialCharacters, dict, null);
}
#region Enumerations & constants
private enum State : byte
{
Uninitialized = 0,
Initialized
}
private const uint TwipsPerInch = 1440;
#endregion
#region Properties
/// <summary>
/// Actual LS unmanaged context
/// </summary>
internal IntPtr Ploc
{
get { return _ploc; }
}
#endregion
}
internal enum BreakStrategies
{
BreakCJK,
KeepCJK,
Max
}
}
|