|
// 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.
/* SSS_DROP_BEGIN */
/*************************************************************************
* NOTICE: Code excluded from Developer Reference Sources.
* Don't remove the SSS_DROP_BEGIN directive on top of the file.
*******************************************************************/
using System;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using System.Globalization;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.TextFormatting;
using MS.Internal;
using MS.Internal.Shaping;
using MS.Internal.FontCache;
using SR=MS.Internal.PresentationCore.SR;
using MS.Internal.Text.TextInterface;
// Disabling 1634 and 1691:
// In order to avoid generating warnings about unknown message numbers and
// unknown pragmas when compiling C# source code with the C# compiler,
// you need to disable warnings 1634 and 1691. (Presharp Documentation)
#pragma warning disable 1634, 1691
// Disabling 6500:
// Suppressing PRESHARP:Warning 6500 Fatal exceptions (NULLReferenceException, SEHException)
// potentially ignored by this catch.
// LineServices callbacks are designed to catch all exceptions such than an error code can be
// returned to Line Services engine. An exception is eventually re-thrown to the user after line
// services engine finishes cleaning up and returns the control to line layout code.
//
#pragma warning disable 6500
namespace MS.Internal.TextFormatting
{
/// <summary>
/// Line Services callbacks
/// </summary>
internal sealed class LineServicesCallbacks
{
/// <summary>
/// LineServices fetches a run of text. The run is broken down at the following
/// boundaries.
/// o Physical font
/// o Unicode block (shaping engine script ID)
/// o Change of bidirectional level
/// o Change of number substitution method
/// </summary>
/// <remarks>
/// The suffix "Redefined" in the method name indicates that this is one
/// such callback that is not managed code friendly and requires redefinition
/// to be able to perform at its optimal performance.
///
/// The problem with the original LS FetchRun is that it requires a raw character
/// pointer in return. This forces us to have to pin the managed memory using
/// GCHandle for every fetch. It hurts performance not only because of the
/// immediate CLR cost of allocating/releasing a GCHandle, but also the implied
/// side effect of leaving GC heap at fragmented state. The latter requires
/// more timely collection which takes significant hit.
///
/// We solve this by redirecting LS to a proxy FetchRun callback in our unmanaged
/// wrapper. The wrapper allocates fixed memory buffer in process heap and delegates
/// the call to this method with extra parameters. This method could decide whether
/// to use the incoming fixed-size buffer, or to return a raw pointer. When dealing
/// with unmanaged client (i.e. Office), it would be more efficient to just return
/// pointer since the client's backing store memory is already all fixed. However
/// when dealing with managed client (i.e XAML), it would fills the incoming buffer
/// instead to avoid pinning of managed memory.
///
/// When filling the incoming fixed-size buffer is required but the given buffer size
/// is too small, we fail the call by setting the output param 'pwchText' to null
/// and 'fIsBufferUsed' flags to false. The return code is still LsErr.None. The
/// proxy unmanaged FetchRun will respond to this result by expanding the buffer size
/// to be as big as the result 'cchText' value and retry the call.
///
/// </remarks>
internal unsafe LsErr FetchRunRedefined(
IntPtr pols, // Line Layout context
int lscpFetch, // position to fetch
int fIsStyle, // flag indicates if pstyle is given
IntPtr pstyle, // current demanded style
char* pwchTextBuffer, // [in/out] fixed-size character buffer
int cchTextBuffer, // buffer length in characters
ref int fIsBufferUsed, // [out] Boolean flag indicating the fixed-size buffer is used
out char* pwchText, // [out] pointer to run's character string
ref int cchText, // [out] length of string
ref int fIsHidden, // [out] Is this run hidden?
ref LsChp lschp, // [out] run's character properties
ref IntPtr lsplsrun // [out] fetched run
)
{
LsErr lserr = LsErr.None;
pwchText = null;
Plsrun plsrun = Plsrun.Undefined;
LSRun lsrun = null;
try
{
FullTextState fullTextState = FullText;
TextStore store = fullTextState.StoreFrom(lscpFetch);
int lsrunOffset;
lsrun = store.FetchLSRun(
lscpFetch,
fullTextState.TextFormattingMode,
fullTextState.IsSideways,
out plsrun,
out lsrunOffset,
out cchText
);
fIsBufferUsed = 0;
pwchText = lsrun.CharacterBuffer.GetCharacterPointer();
if (pwchText == null)
{
// Unable to obtain the raw character pointer of the associated run character string,
// avoid pinning the managed memory by using the specified local buffer.
//
// Pinning via allocating GCHandle is very costly both in term of the immediate cost
// of GCHandle.Alloc and GCHandle.Free, and the implied cost of a fragemented GC heap
// which reduces the GC's ability to compact the managed heap. That situation leads to
// more collection down the road.
//
// The cost of copying the character string is significantly less than the effect of
// GCHandle, especially in common UI scenario where the number of individual string objects
// tends to be quite high.
if (cchText <= cchTextBuffer)
{
Invariant.Assert(pwchTextBuffer != null);
int j = lsrun.OffsetToFirstChar + lsrunOffset;
for (int i = 0; i < cchText; i++, j++)
{
pwchTextBuffer[i] = lsrun.CharacterBuffer[j];
}
fIsBufferUsed = 1;
}
else
{
return LsErr.None;
}
}
else
{
pwchText += lsrun.OffsetToFirstChar + lsrunOffset;
}
lschp = new LsChp();
fIsHidden = 0;
switch (lsrun.Type)
{
case Plsrun.Reverse:
lschp.idObj = (ushort)TextStore.ObjectId.Reverse;
break;
case Plsrun.FormatAnchor:
case Plsrun.CloseAnchor:
lschp.idObj = (ushort)TextStore.ObjectId.Text_chp;
break;
case Plsrun.InlineObject:
lschp.idObj = (ushort)TextStore.ObjectId.InlineObject;
SetChpFormat(lsrun.RunProp, ref lschp);
break;
case Plsrun.Hidden:
lschp.idObj = (ushort)TextStore.ObjectId.Text_chp;
fIsHidden = 1;
break;
case Plsrun.Text:
{
Debug.Assert(TextStore.IsContent(plsrun), "Unrecognizable run!");
Debug.Assert(lsrun.RunProp != null, "invalid lsrun!");
lschp.idObj = (ushort)TextStore.ObjectId.Text_chp;
if ( lsrun.Shapeable != null
&& lsrun.Shapeable.IsShapingRequired)
{
lschp.flags |= LsChp.Flags.fGlyphBased;
if (lsrun.Shapeable.NeedsMaxClusterSize)
{
//
// dcpMaxContext hints LS the maximum number of characters that could be
// shaped into a cluster. We set it in order to prevent LS from breaking
// the line among characters that could be shaped into a single cluster
// within the formatting width.
// Trident sets it to 15 to support Indic. We had been using 8 because we
// didn't support Indic in V1, but this is bumped to 15 for V3.5 (Indic
// support has been added)
//
lschp.dcpMaxContent = lsrun.Shapeable.MaxClusterSize;
}
}
SetChpFormat(lsrun.RunProp, ref lschp);
// All LineBreak and ParaBreak are separated into individual runs of type Plsrun.LineBreak or Plsrun.ParaBreak.
Invariant.Assert(!TextStore.IsNewline(lsrun.CharacterAttributeFlags));
break;
}
default :
// case Plsrun.LineBreak, Plsrun.ParaBreak, Plsrun.FakeLineBreak.
lschp.idObj = (ushort)TextStore.ObjectId.Text_chp;
store.CchEol = lsrun.Length;
break;
}
if ( lsrun.Type == Plsrun.Text
|| lsrun.Type == Plsrun.InlineObject)
{
// Run properties trigger repositioning
Debug.Assert(lsrun.RunProp != null);
if ( lsrun.RunProp != null
&& lsrun.RunProp.BaselineAlignment != BaselineAlignment.Baseline)
{
FullText.VerticalAdjust = true;
}
}
// plsrun is defined as IntPtr on LS side
lsplsrun = (IntPtr)plsrun;
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("FetchRunRedefined", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
private void SetChpFormat(
TextRunProperties runProp,
ref LsChp lschp
)
{
SetChpFormat(runProp.TextDecorations, ref lschp);
SetChpFormat(FullText.TextStore.Pap.TextDecorations, ref lschp);
}
private void SetChpFormat(
TextDecorationCollection textDecorations,
ref LsChp lschp
)
{
// TextDecorations can be null.
if (textDecorations != null)
{
// enumerate through all the TextDecoration and fill Chp accordingly.
for (int i = 0; i < textDecorations.Count; i++)
{
switch (textDecorations[i].Location)
{
case TextDecorationLocation.Underline:
lschp.flags |= LsChp.Flags.fUnderline;
break;
case TextDecorationLocation.OverLine:
case TextDecorationLocation.Strikethrough:
case TextDecorationLocation.Baseline:
lschp.flags |= LsChp.Flags.fStrike;
break;
}
}
}
}
internal LsErr FetchPap(
IntPtr pols, // Line Layout context
int lscpFetch, // position to fetch
ref LsPap lspap // [out] paragraph properties
)
{
LsErr lserr = LsErr.None;
try
{
lspap = new LsPap();
TextStore store = FullText.StoreFrom(lscpFetch);
lspap.cpFirst = lspap.cpFirstContent = lscpFetch; // note: LS doesnt really care
lspap.lskeop = LsKEOP.lskeopEndPara1;
//
// Set flag fFmiTreatHyphenAsRegular to make Hyphen follow line breaking class table like
// regular characters. If the flag is not set, LS will consider Hyphen to have direct break
// opp before and after which is not always desirable, e.g. space after hyphen may be put
// to the start of next line.
//
lspap.grpf |= LsPap.Flags.fFmiTreatHyphenAsRegular;
ParaProp pap = store.Pap;
if (FullText.ForceWrap)
{
lspap.grpf |= LsPap.Flags.fFmiApplyBreakingRules;
}
else if (pap.Wrap)
{
lspap.grpf |= LsPap.Flags.fFmiApplyBreakingRules;
if (!pap.EmergencyWrap)
{
lspap.grpf |= LsPap.Flags.fFmiForceBreakAsNext;
}
if (pap.Hyphenator != null)
{
lspap.grpf |= LsPap.Flags.fFmiAllowHyphenation;
}
}
if (pap.FirstLineInParagraph)
{
lspap.cpFirstContent = store.CpFirst;
lspap.cpFirst = lspap.cpFirstContent;
if (FullText.TextMarkerStore != null)
{
lspap.grpf |= LsPap.Flags.fFmiAnm;
}
}
lspap.fJustify = (pap.Justify ? 1 : 0);
if (pap.Wrap && pap.OptimalBreak)
{
lspap.lsbrj = LsBreakJust.lsbrjBreakOptimal;
lspap.lskj = LsKJust.lskjFullMixed;
}
else
{
lspap.lsbrj = LsBreakJust.lsbrjBreakJustify;
if (pap.Justify)
{
lspap.lskj = LsKJust.lskjFullInterWord;
}
}
lspap.lstflow = pap.RightToLeft ? LsTFlow.lstflowWS : LsTFlow.lstflowES;
}
catch (Exception e)
{
SaveException(e, Plsrun.Undefined, null);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("FetchPap", Plsrun.Undefined, null);
lserr = LsErr.ClientAbort;
}
return lserr;
}
internal LsErr FetchLineProps(
IntPtr pols, // Line Layout context
int lscpFetch, // character position to fetch
int firstLineInPara, // (bool) whether this the first line in paragraph
ref LsLineProps lsLineProps // [out] line properties
)
{
LsErr lserr = LsErr.None;
try
{
TextStore store = FullText.TextStore;
TextStore markerStore = FullText.TextMarkerStore;
ParaProp pap = store.Pap;
FormatSettings settings = store.Settings;
lsLineProps = new LsLineProps();
if (FullText.GetMainTextToMarkerIdealDistance() != 0)
lsLineProps.durLeft = TextFormatterImp.RealToIdeal(markerStore.Pap.TextMarkerProperties.Offset);
else
lsLineProps.durLeft = settings.TextIndent;
if ( pap.Wrap
&& pap.OptimalBreak
&& settings.MaxLineWidth < FullText.FormatWidth)
{
// durRightBreak & durRightJustify are the distances from the paragraph right margin
lsLineProps.durRightBreak = lsLineProps.durRightJustify = (FullText.FormatWidth - settings.MaxLineWidth);
}
}
catch (Exception e)
{
SaveException(e, Plsrun.Undefined, null);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("FetchLineProps", Plsrun.Undefined, null);
lserr = LsErr.ClientAbort;
}
return lserr;
}
internal LsErr GetRunTextMetrics(
System.IntPtr pols, // Line Layout context
Plsrun plsrun, // plsrun
LsDevice lsDevice, // kind of device
LsTFlow lstFlow, // text flow
ref LsTxM lstTextMetrics // [out] returning metrics
)
{
LsErr lserr = LsErr.None;
LSRun lsrun = null;
try
{
FullTextState fullText = FullText;
TextStore store = fullText.StoreFrom(plsrun);
lsrun = store.GetRun(plsrun);
if (lsrun.Height > 0)
{
lstTextMetrics.dvAscent = lsrun.BaselineOffset;
lstTextMetrics.dvMultiLineHeight = lsrun.Height;
}
else
{
Typeface typeface = store.Pap.DefaultTypeface;
lstTextMetrics.dvAscent = (int)Math.Round(typeface.Baseline(store.Pap.EmSize, Constants.DefaultIdealToReal, store.Settings.TextSource.PixelsPerDip, fullText.TextFormattingMode));
lstTextMetrics.dvMultiLineHeight = (int)Math.Round(typeface.LineSpacing(store.Pap.EmSize, Constants.DefaultIdealToReal, store.Settings.TextSource.PixelsPerDip, fullText.TextFormattingMode));
}
lstTextMetrics.dvDescent = lstTextMetrics.dvMultiLineHeight - lstTextMetrics.dvAscent;
lstTextMetrics.fMonospaced = 0;
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("GetRunTextMetrics", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
internal unsafe LsErr GetRunCharWidths(
IntPtr pols, // Line Layout context
Plsrun plsrun, // plsrun
LsDevice device, // kind of device
char* charString, // character string
int stringLength, // string length
int maxWidth, // max width allowance
LsTFlow textFlow, // text flow
int* charWidths, // [out] returning char widths up to given upperbound
ref int totalWidth, // [out] total run width
ref int stringLengthFitted // [out] number of char fitted
)
{
LsErr lserr = LsErr.None;
LSRun lsrun = null;
try
{
TextFormatterImp formatter;
if (FullText != null)
{
lsrun = FullText.StoreFrom(plsrun).GetRun(plsrun);
formatter = FullText.Formatter;
}
else
{
// LS calls this API at display time when tab leader is used.
// We generally do not carry full text state around at display time as it
// is rather heavy. LS also does not generally request formatting info at
// display time. However there are specific cases (such as tab leader)
// when this might be needed. For better performance of all other more
// common cases, we treat this as an exception rather than the norm.
// We assume at this point that the current line has been formatted with
// full text state retained in the line.
#if DEBUG
// FullTextState property is only used in this Assert.
// Put both the property and the assert under #if DEBUG to avoid
// FxCop violations
Debug.Assert(Draw.CurrentLine.FullTextState != null);
#endif
lsrun = Draw.CurrentLine.GetRun(plsrun);
formatter = Draw.CurrentLine.Formatter;
}
if (lsrun.Type == Plsrun.Text)
{
Debug.Assert(lsrun.Shapeable != null && stringLength > 0);
lsrun.Shapeable.GetAdvanceWidthsUnshaped(charString, stringLength, TextFormatterImp.ToIdeal, charWidths);
totalWidth = 0;
stringLengthFitted = 0;
do
{
totalWidth += charWidths[stringLengthFitted];
} while (
++stringLengthFitted < stringLength
&& totalWidth <= maxWidth
);
if (totalWidth <= maxWidth && FullText != null)
{
int cpLimit = lsrun.OffsetToFirstCp + stringLengthFitted;
if (cpLimit > FullText.CpMeasured)
{
FullText.CpMeasured = cpLimit;
}
}
}
else
{
// synthetic run
charWidths[0] = 0;
totalWidth = 0;
stringLengthFitted = stringLength;
}
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("GetRunCharWidths", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
internal LsErr GetDurMaxExpandRagged(
IntPtr pols, // Line Layout context
Plsrun plsrun, // plsrun
LsTFlow lstFlow, // text flow
ref int maxExpandRagged // [out] em width
)
{
LsErr lserr = LsErr.None;
LSRun lsrun = null;
try
{
// According to Knuth, the recommended value of the maximum "good" amount of
// empty space for the ragged case is the width of 3 space characters.
// A space width is generally 1/3 of an em.
lsrun = FullText.StoreFrom(plsrun).GetRun(plsrun);
maxExpandRagged = lsrun.EmSize;
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("GetDurMaxExpandRagged", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
internal LsErr GetAutoNumberInfo(
IntPtr pols, // Line Layout context
ref LsKAlign alignment, // [out] Marker alignment
ref LsChp lschp, // [out] Marker properties
ref IntPtr lsplsrun, // [out] Marker run
ref ushort addedChar, // [out] Character to add after marker
ref LsChp lschpAddedChar, // [out] Added character properties
ref IntPtr lsplsrunAddedChar, // [out] Added character run
ref int fWord95Model, // [out] true iff follow Word95 autonumbering model
ref int offset, // [out] Offset from marker to start of main text (relevant iff word95Model is true)
ref int width // [out] Offset from margin to start of main text (relevant iff word95Model is true)
)
{
LsErr lserr = LsErr.None;
Plsrun plsrun = Plsrun.Undefined;
LSRun lsrun = null;
try
{
FullTextState fullTextState = FullText;
TextStore markerStore = fullTextState.TextMarkerStore;
TextStore store = fullTextState.TextStore;
Debug.Assert(markerStore != null, "No marker store, yet autonumbering is specified!");
int lscp = TextStore.LscpFirstMarker;
int lsrunLength;
do
{
int lsrunOffset;
lsrun = markerStore.FetchLSRun(
lscp,
fullTextState.TextFormattingMode,
fullTextState.IsSideways,
out plsrun,
out lsrunOffset,
out lsrunLength
);
lscp += lsrunLength;
} while (!TextStore.IsContent(plsrun));
alignment = LsKAlign.lskalRight;
lschp = new LsChp();
lschp.idObj = (ushort)TextStore.ObjectId.Text_chp;
SetChpFormat(lsrun.RunProp, ref lschp);
addedChar = FullText.GetMainTextToMarkerIdealDistance() != 0 ? (ushort)'\t' : (ushort)0;
lschpAddedChar = lschp;
fWord95Model = 0; // Word95 model requires precise marker width in which we never have
offset = 0; // marker offset is controlled by tab stop
width = 0;
lsplsrun = (IntPtr)plsrun;
lsplsrunAddedChar = lsplsrun;
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("GetAutoNumberInfo", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
internal LsErr GetRunUnderlineInfo(
IntPtr pols, // Line Layout context
Plsrun plsrun, // plsrun
ref LsHeights lsHeights, // run height
LsTFlow textFlow, // text flow direction
ref LsULInfo ulInfo // [out] result underline info
)
{
LsErr lserr = LsErr.None;
LSRun lsrun = null;
try
{
lsrun = Draw.CurrentLine.GetRun(plsrun);
Debug.Assert(
!TextStore.IsContent(plsrun) || lsrun.Type == Plsrun.Text || lsrun.Type == Plsrun.InlineObject,
"Invalid run"
);
ulInfo = new LsULInfo();
double underlinePositionInEm;
double underlineThicknessInEm;
if (lsrun.Shapeable != null)
{
underlinePositionInEm = lsrun.Shapeable.UnderlinePosition;
underlineThicknessInEm = lsrun.Shapeable.UnderlineThickness;
}
else
{
// e.g. underline on inline object
underlinePositionInEm = lsrun.RunProp.Typeface.UnderlinePosition;
underlineThicknessInEm = lsrun.RunProp.Typeface.UnderlineThickness;
}
ulInfo.cNumberOfLines = 1;
ulInfo.dvpFirstUnderlineOffset = (int)Math.Round(lsrun.EmSize * -underlinePositionInEm);
ulInfo.dvpFirstUnderlineSize = (int)Math.Round(lsrun.EmSize * underlineThicknessInEm);
// Some fonts (e.g. Bodoni MT Condensed) have underline thickness value of zero,
// or we can arrive at zero after integer rounding. Since Line Services require underline thickness
// to be greater than zero, we replace zero and negative values with 1.
// Note that the font driver already corrects zero thickness to something more reasonable,
// but we can still end up with zero if em size multiplied by position is a small value that rounds to zero.
Debug.Assert(ulInfo.dvpFirstUnderlineSize >= 0);
if (ulInfo.dvpFirstUnderlineSize <= 0)
ulInfo.dvpFirstUnderlineSize = 1;
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("GetAutoNumberInfo", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
internal LsErr GetRunStrikethroughInfo(
IntPtr pols, // Line Layout context
Plsrun plsrun, // plsrun
ref LsHeights lsHeights, // run height
LsTFlow textFlow, // text flow direction
ref LsStInfo stInfo // [out] result strikethrough info
)
{
LsErr lserr = LsErr.None;
LSRun lsrun = null;
try
{
lsrun = Draw.CurrentLine.GetRun(plsrun);
Debug.Assert(
!TextStore.IsContent(plsrun) || lsrun.Type == Plsrun.Text || lsrun.Type == Plsrun.InlineObject,
"Invalid run"
);
stInfo = new LsStInfo();
double strikeThroughPositionInEm;
double strikeThroughThicknessInEm;
GetLSRunStrikethroughMetrics(lsrun, out strikeThroughPositionInEm, out strikeThroughThicknessInEm);
stInfo.cNumberOfLines = 1;
stInfo.dvpLowerStrikethroughOffset = (int)Math.Round(lsrun.EmSize * strikeThroughPositionInEm);
stInfo.dvpLowerStrikethroughSize = (int)Math.Round(lsrun.EmSize * strikeThroughThicknessInEm);
Debug.Assert(stInfo.dvpLowerStrikethroughSize >= 0);
// Since Line Services require strikethrough thickness to be greater than zero,
// we replace potential zero and negative values with 1.
// Note that the font driver already corrects zero thickness to something more reasonable,
// but we can still end up with zero if em size multiplied by position is a small value that rounds to zero.
if (stInfo.dvpLowerStrikethroughSize <= 0)
stInfo.dvpLowerStrikethroughSize = 1;
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("GetRunStrikethroughInfo", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
private void GetLSRunStrikethroughMetrics(
LSRun lsrun,
out double strikeThroughPositionInEm,
out double strikeThroughThicknessInEm
)
{
if (lsrun.Shapeable != null)
{
strikeThroughPositionInEm = lsrun.Shapeable.StrikethroughPosition;
strikeThroughThicknessInEm = lsrun.Shapeable.StrikethroughThickness;
}
else
{
// e.g. strike on inline object
strikeThroughPositionInEm = lsrun.RunProp.Typeface.StrikethroughPosition;
strikeThroughThicknessInEm = lsrun.RunProp.Typeface.StrikethroughThickness;
}
}
internal LsErr Hyphenate(
IntPtr pols, // Line Layout context
int fLastHyphenFound, // whether last hyphen found?
int lscpLastHyphen, // cp of the last found hyphen
ref LsHyph lastHyph, // [in] last found hyphenation
int lscpWordStart, // first character of word
int lscpExceed, // first character in this word that exceeds column
ref int fHyphenFound, // [out] hyphenation opportunity found?
ref int lscpHyphen, // [out] cp of the character before hyphen
ref LsHyph lsHyph // [out] hyphen info
)
{
LsErr lserr = LsErr.None;
try
{
fHyphenFound = FullText.FindNextHyphenBreak(
lscpWordStart,
lscpExceed - lscpWordStart,
true, // isCurrentAtWordStart
ref lscpHyphen,
ref lsHyph
) ? 1 : 0;
Invariant.Assert(fHyphenFound == 0 || (lscpHyphen >= lscpWordStart && lscpHyphen < lscpExceed));
}
catch (Exception e)
{
SaveException(e, Plsrun.Undefined, null);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("Hyphenate", Plsrun.Undefined, null);
lserr = LsErr.ClientAbort;
}
return lserr;
}
internal LsErr GetNextHyphenOpp(
IntPtr pols, // Line Layout context
int lscpStartSearch, // LSCP to start search for hyphen opportunity
int lsdcpSearch, // number of LSCP to look for the hyphen opportunity
ref int fHyphenFound, // [out] hyphen found
ref int lscpHyphen, // [out] LSCP of character before hyphen
ref LsHyph lsHyph // [out] hyphen info
)
{
LsErr lserr = LsErr.None;
try
{
fHyphenFound = FullText.FindNextHyphenBreak(
lscpStartSearch,
lsdcpSearch,
false, // !isCurrentAtWordStart
ref lscpHyphen,
ref lsHyph
) ? 1 : 0;
Invariant.Assert(fHyphenFound == 0 || (lscpHyphen >= lscpStartSearch && lscpHyphen < lscpStartSearch + lsdcpSearch));
}
catch (Exception e)
{
SaveException(e, Plsrun.Undefined, null);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("GetNextHyphenOpp", Plsrun.Undefined, null);
lserr = LsErr.ClientAbort;
}
return lserr;
}
internal LsErr GetPrevHyphenOpp(
IntPtr pols, // Line Layout context
int lscpStartSearch, // LSCP to start search for hyphen opportunity
int lsdcpSearch, // number of LSCP to look for the hyphen opportunity
ref int fHyphenFound, // [out] hyphen found
ref int lscpHyphen, // [out] LSCP of character before hyphen
ref LsHyph lsHyph // [out] hyphen info
)
{
LsErr lserr = LsErr.None;
try
{
fHyphenFound = FullText.FindNextHyphenBreak(
// plus 1 here because LS also wants to examine whether hyphen can occur after
// the character identified by lscpStartSearch while the hyphenator generates
// break before the character. This plus 1 is safe, it'll never trigger buffer
// overread since the code never read the character buffer at this index, it is
// properly bound-check'd.
lscpStartSearch + 1,
-lsdcpSearch,
false, // !isCurrentAtWordStart
ref lscpHyphen,
ref lsHyph
) ? 1 : 0;
Invariant.Assert(fHyphenFound == 0 || (lscpHyphen > lscpStartSearch - lsdcpSearch && lscpHyphen <= lscpStartSearch));
}
catch (Exception e)
{
SaveException(e, Plsrun.Undefined, null);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("GetPrevHyphenOpp", Plsrun.Undefined, null);
lserr = LsErr.ClientAbort;
}
return lserr;
}
internal LsErr DrawStrikethrough(
IntPtr pols, // Line Layout context
Plsrun plsrun, // plsrun
uint stType, // kind of strike
ref LSPOINT ptOrigin, // [in] drawing origin
int stLength, // strike length
int stThickness, // strike thickness
LsTFlow textFlow, // text flow direction
uint displayMode, // display mode
ref LSRECT clipRect // [in] clipping rectangle
)
{
LsErr lserr = LsErr.None;
LSRun lsrun = null;
try
{
if (!TextStore.IsContent(plsrun))
{
// dont draw for non-content run e.g. reversal
return LsErr.None;
}
TextMetrics.FullTextLine currentLine = Draw.CurrentLine;
lsrun = currentLine.GetRun(plsrun);
double strikeThroughPositionInEm;
double strikeThroughThicknessInEm;
GetLSRunStrikethroughMetrics(lsrun, out strikeThroughPositionInEm, out strikeThroughThicknessInEm);
int baselineTop = ptOrigin.y + (int)Math.Round(lsrun.EmSize * strikeThroughPositionInEm);
int overlineTop = baselineTop - (lsrun.BaselineOffset - (int)Math.Round(lsrun.EmSize * strikeThroughThicknessInEm));
const uint locationMask =
(1U << (int)TextDecorationLocation.OverLine) |
(1U << (int)TextDecorationLocation.Strikethrough) |
(1U << (int)TextDecorationLocation.Baseline);
DrawTextDecorations(
lsrun,
locationMask,
ptOrigin.x, // left
0, // underline top; not used
overlineTop,
ptOrigin.y, // strikethrough top from LS
baselineTop,
stLength,
stThickness,
textFlow
);
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("DrawStrikethrough", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
internal LsErr DrawUnderline(
IntPtr pols, // Line Layout context
Plsrun plsrun, // plsrun
uint ulType, // kind of underline
ref LSPOINT ptOrigin, // [in] drawing origin
int ulLength, // underline length
int ulThickness, // underline thickness
LsTFlow textFlow, // text flow direction
uint displayMode, // display mode
ref LSRECT clipRect // [in] clipping rectangle
)
{
LsErr lserr = LsErr.None;
LSRun lsrun = null;
try
{
if (!TextStore.IsContent(plsrun))
{
// dont draw for non-content run e.g. reversal
return LsErr.None;
}
lsrun = Draw.CurrentLine.GetRun(plsrun);
const uint locationMask = (1U << (int)TextDecorationLocation.Underline);
DrawTextDecorations(
lsrun,
locationMask,
ptOrigin.x, // left
ptOrigin.y, // underline top from LS
0, // overline top; not used
0, // strikethrough top; not used
0, // baseline top; not used
ulLength,
ulThickness,
textFlow
);
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("DrawUnderline", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
private void DrawTextDecorations(
LSRun lsrun,
uint locationMask,
int left,
int underlineTop,
int overlineTop,
int strikethroughTop,
int baselineTop,
int length,
int thickness,
LsTFlow textFlow
)
{
TextMetrics.FullTextLine currentLine = Draw.CurrentLine;
// Draw paragraph-level text decorations (if any).
TextDecorationCollection textDecorations = currentLine.TextDecorations;
if (textDecorations != null)
{
DrawTextDecorationCollection(
lsrun,
locationMask,
textDecorations,
currentLine.DefaultTextDecorationsBrush,
left,
underlineTop,
overlineTop,
strikethroughTop,
baselineTop,
length,
thickness,
textFlow
);
}
// Draw run-level text decorations (if any).
textDecorations = lsrun.RunProp.TextDecorations;
if (textDecorations != null)
{
DrawTextDecorationCollection(
lsrun,
locationMask,
textDecorations,
lsrun.RunProp.ForegroundBrush,
left,
underlineTop,
overlineTop,
strikethroughTop,
baselineTop,
length,
thickness,
textFlow
);
}
}
private void DrawTextDecorationCollection(
LSRun lsrun,
uint locationMask,
TextDecorationCollection textDecorations,
Brush foregroundBrush,
int left,
int underlineTop,
int overlineTop,
int strikethroughTop,
int baselineTop,
int length,
int thickness,
LsTFlow textFlow
)
{
Invariant.Assert(textDecorations != null);
foreach (TextDecoration td in textDecorations)
{
if (((1U << (int)td.Location) & locationMask) != 0)
{
switch (td.Location)
{
case TextDecorationLocation.Underline:
_boundingBox.Union(
DrawTextDecoration(
lsrun,
foregroundBrush,
new LSPOINT(left, underlineTop),
length,
thickness,
textFlow,
td
)
);
break;
case TextDecorationLocation.OverLine:
_boundingBox.Union(
DrawTextDecoration(
lsrun,
foregroundBrush,
new LSPOINT(left, overlineTop),
length,
thickness,
textFlow,
td
)
);
break;
case TextDecorationLocation.Strikethrough:
_boundingBox.Union(
DrawTextDecoration(
lsrun,
foregroundBrush,
new LSPOINT(left, strikethroughTop),
length,
thickness,
textFlow,
td
)
);
break;
case TextDecorationLocation.Baseline:
_boundingBox.Union(
DrawTextDecoration(
lsrun,
foregroundBrush,
new LSPOINT(left, baselineTop),
length,
thickness,
textFlow,
td
)
);
break;
}
}
}
}
/// <summary>
/// Draw any text decoration line
/// </summary>
private Rect DrawTextDecoration(
LSRun lsrun, // lsrun
Brush foregroundBrush, // default brush if text decoration has no pen
LSPOINT ptOrigin, // drawing origin
int ulLength, // underline length
int ulThickness, // underline thickness
LsTFlow textFlow, // text flow direction
TextDecoration textDecoration //TextDecoration to be draw (add to sublinecollection
)
{
switch (textFlow)
{
case LsTFlow.lstflowWS:
case LsTFlow.lstflowNE:
case LsTFlow.lstflowNW:
// line is drawn in the direction opposite to XY
ptOrigin.x -= ulLength;
break;
}
TextMetrics.FullTextLine currentLine = Draw.CurrentLine;
if (currentLine.RightToLeft)
{
ptOrigin.x = -ptOrigin.x;
}
int u = currentLine.LSLineUToParagraphU(ptOrigin.x);
Point baselineOrigin = LSRun.UVToXY(
Draw.LineOrigin,
Draw.VectorToLineOrigin,
u,
currentLine.BaselineOffset,
currentLine
);
Point lineOrigin = LSRun.UVToXY(
Draw.LineOrigin,
Draw.VectorToLineOrigin,
u,
ptOrigin.y + lsrun.BaselineMoveOffset,
currentLine
);
//
// Resolve the final thickness of the text decoration
//
double penThickness = 1.0;
if (textDecoration.Pen != null)
{
penThickness = textDecoration.Pen.Thickness;
}
// multiplied penThickness value by PenThicknessUnit to get final absolute thickenss
switch (textDecoration.PenThicknessUnit)
{
case TextDecorationUnit.FontRecommended:
// ulThickness is the averaged thickness for Underline case
penThickness = currentLine.Formatter.IdealToReal(ulThickness * penThickness, currentLine.PixelsPerDip);
break;
case TextDecorationUnit.FontRenderingEmSize:
penThickness = currentLine.Formatter.IdealToReal(penThickness * lsrun.EmSize, currentLine.PixelsPerDip);
break;
case TextDecorationUnit.Pixel:
// Don't need to change the thickness for absolute pixels
break;
default:
Debug.Assert(false, "Not supported TextDecorationUnit");
break;
}
// pen thickness can be negative, which has the same effect as its absolute value
penThickness = Math.Abs(penThickness);
//
// Resolve text decoration offset unit
//
double unitValue = 1.0;
switch (textDecoration.PenOffsetUnit)
{
case TextDecorationUnit.FontRecommended:
// lineOrigin.Y is the averaged position for Underline case.
unitValue = (lineOrigin.Y - baselineOrigin.Y);
break;
case TextDecorationUnit.FontRenderingEmSize:
unitValue = currentLine.Formatter.IdealToReal(lsrun.EmSize, currentLine.PixelsPerDip);
break;
case TextDecorationUnit.Pixel:
unitValue = 1.0;
break;
default:
Debug.Assert(false, "Not supported TextDecorationUnit");
break;
}
double lineLength = currentLine.Formatter.IdealToReal(ulLength, currentLine.PixelsPerDip);
DrawingContext drawingContext = Draw.DrawingContext;
if (drawingContext != null)
{
// Thickness used to draw the text decoration.
// It might be scaled to account for PenOffset animation
double drawingPenThickness = penThickness;
// The origin used to draw the text decoration
// It might be offset to account for PenOffset animation
Point drawingLineOrigin = lineOrigin;
bool animated = !textDecoration.CanFreeze && (unitValue != 0);
int pushCount = 0; // counter for the number of explicit DrawingContext.Push()
// put the guideline collection for the text decoration.
Draw.SetGuidelineY(baselineOrigin.Y);
try
{
if (animated)
{
//
// When TextDecoration has animation, we use Translate transform to
// directly apply animations for PenOffset property.
// The final position is:
// (calculated position) + (TextDecoration.PenOffset) * (Unit Value)
// We also apply a ScaleTransform for the (Unit Value) factor. When Unit Value is zero
// there is no need to perform animation
//
ScaleTransform scaleTransform = new ScaleTransform(
1.0, // X scale
unitValue, // y scale
drawingLineOrigin.X, // reference point of scaling
drawingLineOrigin.Y // reference point of scaling
);
TranslateTransform yTranslate = new TranslateTransform(
0, // x translate
textDecoration.PenOffset // y translate
);
// adjust the pen's thickness as it will be scaled back by the scale transform
drawingPenThickness = drawingPenThickness / Math.Abs(unitValue);
// applied transforms
drawingContext.PushTransform(scaleTransform);
pushCount++;
drawingContext.PushTransform(yTranslate);
pushCount++;
}
else
{
// TextDecoration doesn't have animation, adjust the line origin directly
drawingLineOrigin.Y += unitValue * textDecoration.PenOffset;
}
// Apply the pair of guidelines: one for baseline and another
// for top edge of undelining line. Both will be snapped to pixel grid.
// Guideline pairing algorithm detects the case when these two
// guidelines happen to be close to one another and provides
// synchronous snapping, so that the gap between baseline and
// undelining line does not depend on the position of text line.
drawingContext.PushGuidelineY2(baselineOrigin.Y, drawingLineOrigin.Y - drawingPenThickness * 0.5 - baselineOrigin.Y);
pushCount++;
//
// Drawing the actual text decoration line
// As perf optimization, if the Pen given is null, the text decoration is drawn as Rectangle
// to avoid the cost of creating a new Pen (estimated to be 200 bytes overhead).
// If a non-null pen is given, DrawLine will be used and we will pay the price of create a new pen.
// However, this will not be very common.
//
if (textDecoration.Pen == null)
{
// Draw text decoration by DrawRectangle. It avoids the overhead of creating a new Pen.
drawingContext.DrawRectangle(
foregroundBrush, // fill using foreground
null, // null pen for rectangle stroke
new Rect(
drawingLineOrigin.X,
drawingLineOrigin.Y - drawingPenThickness * 0.5,
lineLength,
drawingPenThickness
)
);
}
else
{
// a pen is specified for the text decoration. need to create a new copy
// in order to set the thickness.
// Try to get a copy through CloneCurrentValue() first because it can resolve
// the animation on the Pen.
Pen textDecorationPen = textDecoration.Pen.CloneCurrentValue();
if (Object.ReferenceEquals(textDecoration.Pen, textDecorationPen))
{
// If it is still the same pen, we'll call Copy() to get a new one.
textDecorationPen = textDecoration.Pen.Clone();
}
textDecorationPen.Thickness = drawingPenThickness;
// draw the text decoration
drawingContext.DrawLine(
textDecorationPen,
drawingLineOrigin,
new Point(drawingLineOrigin.X + lineLength, drawingLineOrigin.Y)
);
}
}
finally
{
for (int i = 0; i < pushCount; i++)
{
drawingContext.Pop();
}
Draw.UnsetGuidelineY();
}
}
return new Rect(
lineOrigin.X,
lineOrigin.Y + unitValue * textDecoration.PenOffset - penThickness * 0.5,
lineLength,
penThickness
);
}
internal unsafe LsErr DrawTextRun(
IntPtr pols, // Line Layout context
Plsrun plsrun, // plsrun
ref LSPOINT ptText, // [in] text origin
char* pwchText, // character string
int* piCharAdvances, // char advance widths
int cchText, // text length
LsTFlow textFlow, // text flow
uint displayMode, // draw in transparent or opaque
ref LSPOINT ptRun, // [in] run origin
ref LsHeights lsHeights, // [in] run height
int dupRun, // run length
ref LSRECT clipRect // [in] from DisplayLine's clip rectangle param
)
{
LsErr lserr = LsErr.None;
LSRun lsrun = null;
try
{
TextMetrics.FullTextLine currentLine = Draw.CurrentLine;
lsrun = currentLine.GetRun(plsrun);
GlyphRun glyphRun = ComputeUnshapedGlyphRun(
lsrun,
textFlow,
currentLine.Formatter,
true, // origin of the glyph run provided at drawing time
ptText,
dupRun,
cchText,
pwchText,
piCharAdvances,
currentLine.IsJustified
);
if (glyphRun != null)
{
DrawingContext drawingContext = Draw.DrawingContext;
Draw.SetGuidelineY(glyphRun.BaselineOrigin.Y);
try
{
_boundingBox.Union(
lsrun.DrawGlyphRun(
drawingContext,
null, // draw with the run's foreground brush
glyphRun
)
);
}
finally
{
Draw.UnsetGuidelineY();
}
}
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("DrawTextRun", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
internal LsErr FInterruptShaping(
IntPtr pols, // Line Layout context
LsTFlow textFlow, // text flow
Plsrun plsrunFirst, // first run
Plsrun plsrunSecond, // second run
ref int fIsInterruptOk // [out] disconnect glyphs between runs?
)
{
LsErr lserr = LsErr.None;
try
{
TextStore store = FullText.StoreFrom(plsrunFirst);
if ( !TextStore.IsContent(plsrunFirst)
|| !TextStore.IsContent(plsrunSecond))
{
fIsInterruptOk = 1;
return LsErr.None;
}
LSRun lsrunFirst = store.GetRun(plsrunFirst);
LSRun lsrunSecond = store.GetRun(plsrunSecond);
// shape any runs together as long as they share the following attributes
fIsInterruptOk = !(
// same bidi level
lsrunFirst.BidiLevel == lsrunSecond.BidiLevel
// both are shapeable and equals
&& lsrunFirst.Shapeable != null
&& lsrunSecond.Shapeable != null
&& lsrunFirst.Shapeable.CanShapeTogether(lsrunSecond.Shapeable)
) ? 1 : 0;
}
catch (Exception e)
{
SaveException(e, Plsrun.Undefined, null);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("FInterruptShaping", Plsrun.Undefined, null);
lserr = LsErr.ClientAbort;
}
return lserr;
}
internal static CultureInfo GetNumberCulture(TextRunProperties properties, out NumberSubstitutionMethod method)
{
NumberSubstitution sub = properties.NumberSubstitution;
if (sub == null)
{
method = NumberSubstitutionMethod.AsCulture;
return CultureMapper.GetSpecificCulture(properties.CultureInfo);
}
method = sub.Substitution;
switch (sub.CultureSource)
{
case NumberCultureSource.Text:
return CultureMapper.GetSpecificCulture(properties.CultureInfo);
case NumberCultureSource.User:
return CultureInfo.CurrentCulture;
case NumberCultureSource.Override:
return sub.CultureOverride;
}
return null;
}
/// <summary>
/// LineServices to get glyph indices and related per-glyph data generated from
/// the shaping engine of the specified input character string.
/// </summary>
/// <remarks>
/// The suffix "Redefined" in the method name indicates that this is one
/// such callback that is not managed code friendly and requires redefinition
/// to be able to perform at its optimal performance.
///
/// Similar to FetchRunRedefined, we redefine and wrap this callback with the
/// proxy GetGlyphs callback on the unmanaged side with the same goal. And that is
/// is to avoid high frequency pinning of managed arrays generated by OpenType
/// library via shaping engine code.
///
/// When the specified fixed-size buffer is too small, this method returns LsErr.None
/// with 'fIsGlyphBuffersUsed' set to false and 'glyphCount' indicates the number
/// of glyphs generated. The unmanaged GetGlyphs proxy callback then extends the buffer
/// size to at least the returning value of 'glyphCount' and call the method again.
///
/// </remarks>
internal unsafe LsErr GetGlyphsRedefined(
IntPtr pols, // Line Layout context
IntPtr* plsplsruns, // array of plsruns
int* pcchPlsrun, // array of character count per run
int plsrunCount, // number of runs
char* pwchText, // character string
int cchText, // character count
LsTFlow textFlow, // text flow direction
ushort* puGlyphsBuffer, // [in/out] fixed-size buffer for glyph indices
uint* piGlyphPropsBuffer, // [in/out] fixed-size buffer for glyph properties list
int cgiGlyphBuffers, // glyph buffers length in glyphs
ref int fIsGlyphBuffersUsed, // [out] Boolean flag indicates glyph buffers being used
ushort* puClusterMap, // [out] character-to-glyph cluster map
ushort* puCharProperties, // [out] character properties
int* pfCanGlyphAlone, // [out] parallel to character codes: glyphing does not depend on neighbor?
ref int glyphCount // [out] glyph buffer length and returning actual glyph count
)
{
Invariant.Assert(puGlyphsBuffer != null && piGlyphPropsBuffer != null);
LsErr lserr = LsErr.None;
LSRun lsrunFirst = null;
try
{
LSRun[] lsruns = RemapLSRuns(plsplsruns, plsrunCount);
lsrunFirst = lsruns[0];
Debug.Assert(lsrunFirst.Shapeable != null);
Debug.Assert(cchText > 0); // LineServices should not pass in zero character count;
bool isRightToLeft = ((lsrunFirst.BidiLevel & 1) != 0);
DWriteFontFeature[][] fontFeatures;
uint[] fontFeatureRanges;
uint actualGlyphCount;
checked
{
uint uCchText = (uint)cchText;
LSRun.CompileFeatureSet(lsruns, pcchPlsrun, uCchText, out fontFeatures, out fontFeatureRanges);
GlyphTypeface glyphTypeface = lsrunFirst.Shapeable.GlyphTypeFace;
FullText.Formatter.TextAnalyzer.GetGlyphs(
pwchText,
uCchText,
glyphTypeface.FontDWrite,
glyphTypeface.BlankGlyphIndex,
false,
isRightToLeft,
lsrunFirst.RunProp.CultureInfo,
fontFeatures,
fontFeatureRanges,
(uint)cgiGlyphBuffers,
FullText.TextFormattingMode,
lsrunFirst.Shapeable.ItemProps,
puClusterMap,
puCharProperties,
puGlyphsBuffer,
piGlyphPropsBuffer,
pfCanGlyphAlone,
out actualGlyphCount
);
glyphCount = (int)actualGlyphCount;
if (glyphCount <= cgiGlyphBuffers)
{
fIsGlyphBuffersUsed = 1;
}
else
{
fIsGlyphBuffersUsed = 0;
}
}
}
catch (Exception e)
{
SaveException(e, (Plsrun)(plsplsruns[0]), lsrunFirst);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("GetGlyphsRedefined", (Plsrun)(plsplsruns[0]), lsrunFirst);
lserr = LsErr.ClientAbort;
}
return lserr;
}
internal unsafe LsErr GetGlyphPositions(
IntPtr pols, // Line Layout context
IntPtr* plsplsruns, // array of plsruns
int* pcchPlsrun, // array of character count per run
int plsrunCount, // number of runs
LsDevice device, // on reference or presentation device
char* pwchText, // character string
ushort* puClusterMap, // character-to-glyph cluster map
ushort* puCharProperties, // character properties
int cchText, // character count
ushort* puGlyphs, // glyph indices
uint* piGlyphProperties, // glyph properties
int glyphCount, // glyph count
LsTFlow textFlow, // text flow direction
int* piGlyphAdvances, // [out] glyph advances
GlyphOffset* piiGlyphOffsets // [out] glyph offsets
)
{
LsErr lserr = LsErr.None;
LSRun lsrunFirst = null;
try
{
LSRun[] lsruns = RemapLSRuns(plsplsruns, plsrunCount);
lsrunFirst = lsruns[0];
bool isRightToLeft = ((lsrunFirst.BidiLevel & 1) != 0);
GlyphOffset[] glyphOffset;
GlyphTypeface glyphTypeface = lsrunFirst.Shapeable.GlyphTypeFace;
DWriteFontFeature[][] fontFeatures;
uint[] fontFeatureRanges;
LSRun.CompileFeatureSet(lsruns, pcchPlsrun, checked((uint)cchText), out fontFeatures, out fontFeatureRanges);
FullText.Formatter.TextAnalyzer.GetGlyphPlacements(
pwchText,
puClusterMap,
(ushort*)puCharProperties,
(uint)cchText,
puGlyphs,
piGlyphProperties,
(uint)glyphCount,
glyphTypeface.FontDWrite,
lsrunFirst.Shapeable.EmSize,
TextFormatterImp.ToIdeal,
false,
isRightToLeft,
lsrunFirst.RunProp.CultureInfo,
fontFeatures,
fontFeatureRanges,
FullText.TextFormattingMode,
lsrunFirst.Shapeable.ItemProps,
(float)FullText.StoreFrom(lsrunFirst.Type).Settings.TextSource.PixelsPerDip,
piGlyphAdvances,
out glyphOffset
);
for (int i = 0; i < glyphCount; ++i)
{
piiGlyphOffsets[i].du = glyphOffset[i].du;
piiGlyphOffsets[i].dv = glyphOffset[i].dv;
}
}
catch (Exception e)
{
SaveException(e, (Plsrun)(plsplsruns[0]), lsrunFirst);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("GetGlyphPositions", (Plsrun)(plsplsruns[0]), lsrunFirst);
lserr = LsErr.ClientAbort;
}
return lserr;
}
/// <summary>
/// Generate a list of correspondent lsruns
/// </summary>
private unsafe LSRun[] RemapLSRuns(
IntPtr* plsplsruns,
int plsrunCount
)
{
LSRun[] lsruns = new LSRun[plsrunCount];
TextStore store = FullText.StoreFrom((Plsrun)(*plsplsruns));
for (int i = 0; i < lsruns.Length; i++)
{
Plsrun plsrun = (Plsrun)plsplsruns[i];
lsruns[i] = store.GetRun(plsrun);
Debug.Assert(TextStore.IsContent(plsrun) && lsruns[i] != null);
}
return lsruns;
}
internal unsafe LsErr DrawGlyphs(
IntPtr pols, // Line Layout context
Plsrun plsrun, // plsrun
char* pwchText, // character string
ushort* puClusterMap, // character-to-cluster map
ushort* puCharProperties, // character properties
int charCount, // character count
ushort* puGlyphs, // glyph indices
int* piJustifiedGlyphAdvances, // justified glyph advances
int* piGlyphAdvances, // original ideal glyph advances
GlyphOffset* piiGlyphOffsets, // glyph offsets
uint* piGlyphProperties, // glyph properties
LsExpType* plsExpType, // glyph expansion types
int glyphCount, // glyph count
LsTFlow textFlow, // text flow
uint displayMode, // draw transparent or opaque
ref LSPOINT ptRun, // [in] display position (at baseline)
ref LsHeights lsHeights, // [in] run height metrics
int runWidth, // run overall advance width
ref LSRECT clippingRect // [in] clipping rectangle if any applied
)
{
LsErr lserr = LsErr.None;
LSRun lsrun = null;
try
{
TextMetrics.FullTextLine currentLine = Draw.CurrentLine;
lsrun = currentLine.GetRun(plsrun);
Debug.Assert(TextStore.IsContent(plsrun) && lsrun.Shapeable != null);
GlyphRun glyphRun = ComputeShapedGlyphRun(
lsrun,
currentLine.Formatter,
true, // origin of the glyph run provided at drawing time
ptRun,
charCount,
pwchText,
puClusterMap,
glyphCount,
puGlyphs,
piJustifiedGlyphAdvances,
piiGlyphOffsets,
currentLine.IsJustified
);
if (glyphRun != null)
{
DrawingContext drawingContext = Draw.DrawingContext;
Draw.SetGuidelineY(glyphRun.BaselineOrigin.Y);
try
{
_boundingBox.Union(
lsrun.DrawGlyphRun(
drawingContext,
null, // draw with the run's foreground
glyphRun
)
);
}
finally
{
Draw.UnsetGuidelineY();
}
}
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("DrawGlyphs", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
/// <summary>
/// LS calls this method to fill in compression amount between characters in
/// full-mixed justification used only by optimal break mode. It may fill in
/// the critical _exception member.
/// </summary>
internal unsafe LsErr GetCharCompressionInfoFullMixed(
IntPtr pols, // Line Layout context
LsDevice device, // kind of device
LsTFlow textFlow, // text flow
LsCharRunInfo *plscharrunInfo, // char-based run info
LsNeighborInfo *plsneighborInfoLeft, // left neighbor info
LsNeighborInfo *plsneighborInfoRight, // right neigbor info
int maxPriorityLevel, // maximum priority level
int** pplscompressionLeft, // [in/out] fill in left compression amount per priority level on the way out
int** pplscompressionRight // [in/out] fill in right compression amount per priority level on the way out
)
{
LsErr lserr = LsErr.None;
Plsrun plsrun = Plsrun.Undefined;
LSRun lsrun = null;
try
{
Invariant.Assert(maxPriorityLevel == 3);
plsrun = plscharrunInfo->plsrun;
lsrun = FullText.StoreFrom(plsrun).GetRun(plsrun);
return AdjustChars(
plscharrunInfo,
false, // compressing
(int)(lsrun.EmSize * Constants.MinInterWordCompressionPerEm),
pplscompressionLeft,
pplscompressionRight
);
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("GetCharCompressionInfoFullMixed", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
/// <summary>
/// LS calls this method to fill in expansion amount between characters in
/// full-mixed justification used only by optimal break mode. It may fill in
/// the critical _exception member.
/// </summary>
internal unsafe LsErr GetCharExpansionInfoFullMixed(
IntPtr pols, // Line Layout context
LsDevice device, // kind of device
LsTFlow textFlow, // text flow
LsCharRunInfo *plscharrunInfo, // char-based run info
LsNeighborInfo *plsneighborInfoLeft, // left neighbor info
LsNeighborInfo *plsneighborInfoRight, // right neigbor info
int maxPriorityLevel, // maximum priority level
int** pplsexpansionLeft, // [in/out] fill in left expansion amount per priority level on the way out
int** pplsexpansionRight // [in/out] fill in right expansion amount per priority level on the way out
)
{
LsErr lserr = LsErr.None;
Plsrun plsrun = Plsrun.Undefined;
LSRun lsrun = null;
try
{
Invariant.Assert(maxPriorityLevel == 3);
plsrun = plscharrunInfo->plsrun;
lsrun = FullText.StoreFrom(plsrun).GetRun(plsrun);
return AdjustChars(
plscharrunInfo,
true, // expanding
(int)(lsrun.EmSize * Constants.MaxInterWordExpansionPerEm),
pplsexpansionLeft,
pplsexpansionRight
);
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("GetCharExpansionInfoFullMixed", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
/// <summary>
/// Adjust characters at inter-word spacing position targetting the specified amount.
/// </summary>
private unsafe LsErr AdjustChars(
LsCharRunInfo *plscharrunInfo,
bool expanding,
int interWordAdjustTo,
int** pplsAdjustLeft,
int** pplsAdjustRight
)
{
char* pwch = plscharrunInfo->pwch;
int cchRun = plscharrunInfo->cwch;
for (int i = 0; i < cchRun; i++)
{
int adjustedCharWidth = plscharrunInfo->rgduNominalWidth[i] + plscharrunInfo->rgduChangeLeft[i] + plscharrunInfo->rgduChangeRight[i];
// no adjust to the left
pplsAdjustLeft[0][i] = 0;
pplsAdjustLeft[1][i] = 0;
pplsAdjustLeft[2][i] = 0;
// no valid adjustment except at interword spacing
pplsAdjustRight[0][i] = 0;
pplsAdjustRight[1][i] = 0;
pplsAdjustRight[2][i] = 0;
ushort flags = (ushort)(Classification.CharAttributeOf((int)Classification.GetUnicodeClassUTF16(pwch[i]))).Flags;
if ((flags & ((ushort)CharacterAttributeFlags.CharacterSpace)) != 0)
{
if (expanding)
{
int expandedBy = Math.Max(0, interWordAdjustTo - adjustedCharWidth);
pplsAdjustRight[0][i] = expandedBy;
pplsAdjustRight[1][i] = expandedBy * Constants.AcceptableLineStretchability;
pplsAdjustRight[2][i] = FullText.FormatWidth;
}
else
{
pplsAdjustRight[0][i] = Math.Max(0, adjustedCharWidth - interWordAdjustTo);
}
}
else if (expanding)
{
// emergency expansion, use the column width as maximum allowance
pplsAdjustRight[2][i] = FullText.FormatWidth;
}
}
return LsErr.None;
}
/// <summary>
/// LS calls this method to fill in compression amount between glyphs in
/// full-mixed justification used only by optimal break mode. It may fill in
/// the critical _exception member.
/// </summary>
internal unsafe LsErr GetGlyphCompressionInfoFullMixed(
IntPtr pols, // Line Layout context
LsDevice device, // kind of device
LsTFlow textFlow, // text flow
LsGlyphRunInfo *plsglyphrunInfo, // glyph-based run info
LsNeighborInfo *plsneighborInfoLeft, // left neighbor info
LsNeighborInfo *plsneighborInfoRight, // right neigbor info
int maxPriorityLevel, // maximum priority level
int **pplscompressionLeft, // [in/out] fill in left compression amount per priority level on the way out
int **pplscompressionRight // [in/out] fill in right compression amount per priority level on the way out
)
{
LsErr lserr = LsErr.None;
Plsrun plsrun = Plsrun.Undefined;
LSRun lsrun = null;
try
{
Invariant.Assert(maxPriorityLevel == 3);
plsrun = plsglyphrunInfo->plsrun;
lsrun = FullText.StoreFrom(plsrun).GetRun(plsrun);
int em = lsrun.EmSize;
return CompressGlyphs(
plsglyphrunInfo,
(int)(em * Constants.MinInterWordCompressionPerEm),
pplscompressionLeft,
pplscompressionRight
);
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("GetGlyphCompressionInfoFullMixed", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
/// <summary>
/// Compress glyphs at inter-word spacing position targetting the specified compression amount.
/// Further compression beyond the specified amount at inter-word or inter-letter positions
/// is not allowed at all time.
/// </summary>
private unsafe LsErr CompressGlyphs(
LsGlyphRunInfo *plsglyphrunInfo,
int interWordCompressTo,
int **pplsCompressionLeft,
int **pplsCompressionRight
)
{
char* pwch = plsglyphrunInfo->pwch;
ushort* pgmap = plsglyphrunInfo->rggmap;
int cchRun = plsglyphrunInfo->cwch;
int cgiRun = plsglyphrunInfo->cgindex;
int ich = 0;
int igi = pgmap[ich];
int cgi = 0;
while (ich < cchRun)
{
// get the number of chars of the current cluster
int cch = 1;
while (ich + cch < cchRun && pgmap[ich + cch] == igi)
cch++;
// get the number of glyphs of the current cluster
cgi = (ich + cch == cchRun) ? cgiRun - igi : pgmap[ich + cch] - igi;
int i, j;
// scan cluster to find interword spacing
for (j = 0; j < cch; j++)
{
ushort flags = (ushort)(Classification.CharAttributeOf((int)Classification.GetUnicodeClassUTF16(pwch[ich + j]))).Flags;
if ((flags & ((ushort)CharacterAttributeFlags.CharacterSpace)) != 0)
break;
}
int glyphAdvance = 0;
for (i = 0; i < cgi; i++)
{
glyphAdvance += plsglyphrunInfo->rgduWidth[igi + i];
// no compression to the left
pplsCompressionLeft[0][igi + i] = 0;
pplsCompressionLeft[1][igi + i] = 0;
pplsCompressionLeft[2][igi + i] = 0;
// no compression except at interword spacing
pplsCompressionRight[0][igi + i] = 0;
pplsCompressionRight[1][igi + i] = 0;
pplsCompressionRight[2][igi + i] = 0;
if ( i == cgi - 1
&& cch == 1
&& j < cch
)
{
// cluster has interword space, compress to the right of the last glyph of the cluster
pplsCompressionRight[0][igi + i] = Math.Max(0, glyphAdvance - interWordCompressTo);
}
}
ich += cch;
igi += cgi;
}
Invariant.Assert(igi == cgiRun);
return LsErr.None;
}
/// <summary>
/// LS calls this method to fill in expansion amount between glyphs in
/// full-mixed justification used only by optimal break mode.
/// </summary>
internal unsafe LsErr GetGlyphExpansionInfoFullMixed(
IntPtr pols, // Line Layout context
LsDevice device, // kind of device
LsTFlow textFlow, // text flow
LsGlyphRunInfo *plsglyphrunInfo, // glyph-based run info
LsNeighborInfo *plsneighborInfoLeft, // left neighbor info
LsNeighborInfo *plsneighborInfoRight, // right neigbor info
int maxPriorityLevel, // maximum priority level
int **pplsexpansionLeft, // [in/out] fill in left expansion amount per priority level on the way out
int **pplsexpansionRight, // [in/out] fill in right expansion amount per priority level on the way out
LsExpType *plsexptype, // [in/out] fill in glyph expansion type for each glyph
int *pduMinInk // [in/out] fill in glyph minimum expansion for exptAddInkContinuous
)
{
LsErr lserr = LsErr.None;
Plsrun plsrun = Plsrun.Undefined;
LSRun lsrun = null;
try
{
Invariant.Assert(maxPriorityLevel == 3);
plsrun = plsglyphrunInfo->plsrun;
lsrun = FullText.StoreFrom(plsrun).GetRun(plsrun);
int em = lsrun.EmSize;
return ExpandGlyphs(
plsglyphrunInfo,
(int)(em * Constants.MaxInterWordExpansionPerEm),
pplsexpansionLeft,
pplsexpansionRight,
plsexptype,
LsExpType.AddWhiteSpace, // inter-word expansion type
// No inter-letter expansion for RTL run for now
((lsrun.BidiLevel & 1) == 0 ? LsExpType.AddWhiteSpace : LsExpType.None)
);
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("GetGlyphExpansionInfoFullMixed", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
/// <summary>
/// Expand glyphs at inter-word spacing position targetting the specified expansion amount.
/// Inter-letter expansion may be allowed in emergency case.
/// </summary>
private unsafe LsErr ExpandGlyphs(
LsGlyphRunInfo *plsglyphrunInfo,
int interWordExpandTo,
int **pplsExpansionLeft,
int **pplsExpansionRight,
LsExpType *plsexptype,
LsExpType interWordExpansionType,
LsExpType interLetterExpansionType
)
{
char* pwch = plsglyphrunInfo->pwch;
ushort* pgmap = plsglyphrunInfo->rggmap;
int cchRun = plsglyphrunInfo->cwch;
int cgiRun = plsglyphrunInfo->cgindex;
int ich = 0;
int igi = pgmap[ich];
int cgi = 0;
while (ich < cchRun)
{
// get the number of chars of the current cluster
int cch = 1;
while (ich + cch < cchRun && pgmap[ich + cch] == igi)
cch++;
// get the number of glyphs of the current cluster
cgi = (ich + cch == cchRun) ? cgiRun - igi : pgmap[ich + cch] - igi;
int i, j;
// scan cluster to find interword spacing
for (j = 0; j < cch; j++)
{
ushort flags = (ushort)(Classification.CharAttributeOf((int)Classification.GetUnicodeClassUTF16(pwch[ich + j]))).Flags;
if ((flags & ((ushort)CharacterAttributeFlags.CharacterSpace)) != 0)
break;
}
int glyphAdvance = 0;
for (i = 0; i < cgi; i++)
{
glyphAdvance += plsglyphrunInfo->rgduWidth[igi + i];
// no expansion to the left
pplsExpansionLeft[0][igi + i] = 0;
pplsExpansionLeft[1][igi + i] = 0;
pplsExpansionLeft[2][igi + i] = 0;
// no expansion except at interword spacing
pplsExpansionRight[0][igi + i] = 0;
pplsExpansionRight[1][igi + i] = 0;
pplsExpansionRight[2][igi + i] = 0;
if (i == cgi - 1)
{
if (cch == 1 && j < cch)
{
// cluster has interword space, expand to the right of the last glyph of the cluster
int expandedBy = Math.Max(0, interWordExpandTo - glyphAdvance);
pplsExpansionRight[0][igi + i] = expandedBy;
pplsExpansionRight[1][igi + i] = expandedBy * Constants.AcceptableLineStretchability;
pplsExpansionRight[2][igi + i] = FullText.FormatWidth;
plsexptype[igi + i] = interWordExpansionType;
}
else
{
// emergency expansion, use the column width as maximum allowance
pplsExpansionRight[2][igi + i] = FullText.FormatWidth;
plsexptype[igi + i] = interLetterExpansionType;
}
}
}
ich += cch;
igi += cgi;
}
Invariant.Assert(igi == cgiRun);
return LsErr.None;
}
//
// Line Services object handler callbacks
//
//
internal unsafe LsErr GetObjectHandlerInfo(
System.IntPtr pols, // Line Layout context
uint objectId, // installed object id
void* objectInfo // [out] object handler info
)
{
LsErr lserr = LsErr.None;
try
{
if (objectId < (uint)TextStore.ObjectId.MaxNative)
{
// Send to native object handler
return UnsafeNativeMethods.LocbkGetObjectHandlerInfo(
pols,
objectId,
objectInfo
);
}
///// Custom object handler
//
switch (objectId)
{
case (uint)TextStore.ObjectId.InlineObject:
InlineInit inlineInit = new InlineInit();
inlineInit.pfnFormat = this.InlineFormatDelegate;
inlineInit.pfnDraw = this.InlineDrawDelegate;
Marshal.StructureToPtr(inlineInit, (System.IntPtr)objectInfo, false);
break;
default:
Debug.Assert(false, "Unsupported installed object!");
break;
}
}
catch (Exception e)
{
SaveException(e, Plsrun.Undefined, null);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("GetObjectHandlerInfo", Plsrun.Undefined, null);
lserr = LsErr.ClientAbort;
}
return lserr;
}
internal LsErr InlineFormat(
System.IntPtr pols, // Line Layout context
Plsrun plsrun, // plsrun
int lscpInline, // first cp of the run
int currentPosition, // inline's current pen location in text direction
int rightMargin, // right margin
ref ObjDim pobjDim, // [out] object dimension
out int fFirstRealOnLine, // [out] is this run the first in line
out int fPenPositionUsed, // [out] is pen position used to format object
out LsBrkCond breakBefore, // [out] break condition before this object
out LsBrkCond breakAfter // [out] break condition after this object
)
{
LsErr lserr = LsErr.None;
LSRun lsrun = null;
fFirstRealOnLine = 0;
fPenPositionUsed = 0;
breakBefore = LsBrkCond.Please;
breakAfter = LsBrkCond.Please;
try
{
TextFormatterImp formatter = FullText.Formatter;
TextStore store = FullText.StoreFrom(plsrun);
lsrun = store.GetRun(plsrun);
TextEmbeddedObject textObject = lsrun.TextRun as TextEmbeddedObject;
Debug.Assert(textObject != null);
int cpInline = store.GetExternalCp(lscpInline);
fFirstRealOnLine = (cpInline == store.CpFirst) ? 1 : 0;
TextEmbeddedObjectMetrics metrics = store.FormatTextObject(
textObject,
cpInline,
currentPosition,
rightMargin
);
pobjDim = new ObjDim();
pobjDim.dur = TextFormatterImp.RealToIdeal(metrics.Width);
pobjDim.heightsRef.dvMultiLineHeight = TextFormatterImp.RealToIdeal(metrics.Height);
pobjDim.heightsRef.dvAscent = TextFormatterImp.RealToIdeal(metrics.Baseline);
pobjDim.heightsRef.dvDescent = pobjDim.heightsRef.dvMultiLineHeight - pobjDim.heightsRef.dvAscent;
pobjDim.heightsPres = pobjDim.heightsRef;
breakBefore = BreakConditionToLsBrkCond(textObject.BreakBefore);
breakAfter = BreakConditionToLsBrkCond(textObject.BreakAfter);
fPenPositionUsed = (!textObject.HasFixedSize) ? 1 : 0;
// update lsrun metrics of text object
lsrun.BaselineOffset = pobjDim.heightsRef.dvAscent;
lsrun.Height = pobjDim.heightsRef.dvMultiLineHeight;
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("InlineFormat", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
private LsBrkCond BreakConditionToLsBrkCond(LineBreakCondition breakCondition)
{
switch (breakCondition)
{
case LineBreakCondition.BreakDesired:
return LsBrkCond.Please;
case LineBreakCondition.BreakPossible:
return LsBrkCond.Can;
case LineBreakCondition.BreakRestrained:
return LsBrkCond.Never;
case LineBreakCondition.BreakAlways:
return LsBrkCond.Must;
}
Debug.Assert(false);
return LsBrkCond.Please;
}
internal LsErr InlineDraw(
System.IntPtr pols, // Line Layout context
Plsrun plsrun, // plsrun
ref LSPOINT ptRun, // [in] pen position at which to render the object
LsTFlow textFlow, // text flow direction
int runWidth // object width
)
{
LsErr lserr = LsErr.None;
LSRun lsrun = null;
try
{
TextMetrics.FullTextLine currentLine = Draw.CurrentLine;
lsrun = currentLine.GetRun(plsrun);
LSPOINT lsrunOrigin = ptRun;
Debug.Assert(lsrun.Type == Plsrun.InlineObject);
int baseDirection = currentLine.RightToLeft ? 1 : 0;
int runDirection = (int)(lsrun.BidiLevel & 1);
if (baseDirection != 0)
{
lsrunOrigin.x = -lsrunOrigin.x;
}
TextEmbeddedObject textObject = lsrun.TextRun as TextEmbeddedObject;
Debug.Assert(textObject != null);
Debug.Assert(textFlow != LsTFlow.lstflowWS || runDirection != 0);
if ((baseDirection ^ runDirection) != 0)
{
// always draw as if the object run has the same direction as the base level
lsrunOrigin.x -= runWidth;
}
// object baseline origin in UV relative to paragraph start
Point baselineOrigin = new Point(
currentLine.Formatter.IdealToReal(currentLine.LSLineUToParagraphU(lsrunOrigin.x), currentLine.PixelsPerDip)+ Draw.VectorToLineOrigin.X,
currentLine.Formatter.IdealToReal((lsrunOrigin.y + lsrun.BaselineMoveOffset), currentLine.PixelsPerDip) + Draw.VectorToLineOrigin.Y
);
// get object bounding box
Rect objectBounds = textObject.ComputeBoundingBox(
baseDirection != 0, // rightToLeft
false // no sideway support yet
);
if (!objectBounds.IsEmpty)
{
// bounding box received from text object is relative to
// calculated object baseline origin
objectBounds.X += baselineOrigin.X;
objectBounds.Y += baselineOrigin.Y;
}
// map object bounds to XY space and integrate with the line bounding box
_boundingBox.Union(
new Rect(
// map logical top-left location
LSRun.UVToXY(
Draw.LineOrigin,
new Point(),
objectBounds.Location.X,
objectBounds.Location.Y,
currentLine
),
// map logical bottom-right location
LSRun.UVToXY(
Draw.LineOrigin,
new Point(),
objectBounds.Location.X + objectBounds.Size.Width,
objectBounds.Location.Y + objectBounds.Size.Height,
currentLine
)
)
);
DrawingContext drawingContext = Draw.DrawingContext;
if (drawingContext != null)
{
// snapping for inline object
Draw.SetGuidelineY(baselineOrigin.Y);
try
{
if (Draw.AntiInversion == null)
{
// Draw at XY origin
textObject.Draw(
drawingContext,
LSRun.UVToXY(
Draw.LineOrigin,
new Point(),
baselineOrigin.X,
baselineOrigin.Y,
currentLine
),
baseDirection != 0,
false
);
}
else
{
// restore the original state of the drawing surface if we've inverted it,
// client should be able to draw a text object on to the original surface
// they intend to draw it.
drawingContext.PushTransform(Draw.AntiInversion);
try
{
textObject.Draw(drawingContext, baselineOrigin, baseDirection != 0, false);
}
finally
{
drawingContext.Pop();
}
}
}
finally
{
Draw.UnsetGuidelineY();
}
}
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("InlineDraw", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
//
// Line enumeration methods through Line Services LsEnumLine callbacks
//
// We want to map cp in backing store onto its GlyphRun. We aren't able to achieve this through
// LineServices drawing callbacks because they (i.e. DrawGlyphs, DrawTextRun) only give
// characters in GlyphRun which don't always match cps in the backing store (e.g. Hyphen, Tab)
// We can resolve the matching cp of a GlyphRun through LSRun boundaries either because a GlyphRun
// may span two LSRun (i.e. two LSRun are shaped together)
//
// Line service enumeration API gives the actual backing store CP range as well as all the necessary info
// to contruct the GlyphRun.
//
internal unsafe LsErr EnumText(
IntPtr pols, // ls context
Plsrun plsrun, // plsrun
int cpFirst, // first cp of the ls dnode
int dcp, // dcp of the dnode
char *pwchText, // characters for glyph run
int cchText, // length of characters
LsTFlow lstFlow, // flow direction
int fReverseOrder, // flag for reverse order enumeration
int fGeometryProvided, // flag for providing geometry
ref LSPOINT pptStart, // [in] logical start of the run
ref LsHeights pheights, // [in] height (iff geometryProvided)
int dupRun, // width of the run
int glyphBaseRun, // flag for glyph based run
int *piCharAdvances, // character advance widths (iff !glyphBaseRun)
ushort *puClusterMap, // cluster map (iff glyphBaseRun)
ushort *characterProperties, // character properties (iff glyphBaseRun)
ushort *puGlyphs, // glyph indices (iff glyphBaseRun)
int *piJustifiedGlyphAdvances, // glyph advances (iff glyphBaseRun)
GlyphOffset *piiGlyphOffsets, // glyph offsets (iff glyphBaseRun)
uint *piGlyphProperties, // glyph properties (iff glyphProperties)
int glyphCount // glyph count
)
{
Debug.Assert(fGeometryProvided == 0, "Line enumeration doesn't need geometry information");
if (cpFirst < 0)
{
// Do not enumerate negative cps because they are not in the backing store.
return LsErr.None;
}
LsErr lserr = LsErr.None;
LSRun lsrun = null;
try
{
TextMetrics.FullTextLine currentLine = Draw.CurrentLine;
lsrun = currentLine.GetRun(plsrun);
GlyphRun glyphRun = null;
if (glyphBaseRun != 0)
{
// it is a glyph based run
if (glyphCount > 0)
{
// create shaped glyph run
glyphRun = ComputeShapedGlyphRun(
lsrun,
currentLine.Formatter,
false, // glyph run origin not provided
pptStart,
cchText,
pwchText,
puClusterMap,
glyphCount,
puGlyphs,
piJustifiedGlyphAdvances,
piiGlyphOffsets,
currentLine.IsJustified
);
}
}
else if (cchText > 0)
{
// need to accumulate the width of the run
dupRun = 0;
for (int i = 0; i < cchText; i++)
{
dupRun += piCharAdvances[i];
}
// it is an unshaped glyphrun
glyphRun = ComputeUnshapedGlyphRun(
lsrun,
lstFlow,
currentLine.Formatter,
false, // glyph run origin not provided at enumeration
pptStart,
dupRun,
cchText,
pwchText,
piCharAdvances,
currentLine.IsJustified
);
}
if (glyphRun != null)
{
//
// Add this glyph run into the enumeration list
// Note that we are using the cpFirst/dcp pair as index.
// They correspond to actualy cps in backing store.
//
IndexedGlyphRuns.Add(
new IndexedGlyphRun(
currentLine.GetExternalCp(cpFirst),
dcp,
glyphRun
)
);
}
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("EnumText", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
// enumerating a tab
internal unsafe LsErr EnumTab(
IntPtr pols, // pointer to context
Plsrun plsrun, // plsrun
int cpFirst, // first cp of the dnode run
char *pwchText, // a single tab character
char tabLeader, // a single tab leader character
LsTFlow lstFlow, // flow direction
int fReverseOrder, // flag for reverse order enumeration
int fGeometryProvided, // flag for providing geometry information
ref LSPOINT pptStart, // [in] logical start of the run (iff geometryProvided)
ref LsHeights heights, // [in] height (iff geometryProvided)
int dupRun // width of the run
)
{
if (cpFirst < 0)
{
// Do not enumerate negative cps because they are not in the backing store.
return LsErr.None;
}
LsErr lserr = LsErr.None;
LSRun lsrun = null;
try
{
TextMetrics.FullTextLine currentLine = Draw.CurrentLine;
lsrun = currentLine.GetRun(plsrun);
GlyphRun glyphRun = null;
if (lsrun.Type == Plsrun.Text)
{
// Construct glyph run for the single tableader.
// We don't repeat the tab leader justification logic here.
int charWidth = 0;
lsrun.Shapeable.GetAdvanceWidthsUnshaped(
&tabLeader,
1,
TextFormatterImp.ToIdeal,
&charWidth
);
glyphRun = ComputeUnshapedGlyphRun(
lsrun,
lstFlow,
currentLine.Formatter,
false, // glyph run origin not provided at enumeration time
pptStart,
charWidth,
1,
&tabLeader,
&charWidth,
currentLine.IsJustified
);
}
if (glyphRun != null)
{
IndexedGlyphRuns.Add(
new IndexedGlyphRun(
currentLine.GetExternalCp(cpFirst),
1, // dcp is 1 for a Tab character
glyphRun
)
);
}
}
catch (Exception e)
{
SaveException(e, plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
catch
{
SaveNonCLSException("EnumTab", plsrun, lsrun);
lserr = LsErr.ClientAbort;
}
return lserr;
}
/// <summary>
/// Returns whether a given character is a space character and hence can safely be expanded/compressed
/// with little visual impact on the text.
/// </summary>
private bool IsSpace(char ch)
{
if ( ch == '\u0009' // tab
|| ch == '\u0020' // Space
)
{
return true;
}
return false;
}
/// <summary>
/// Scale real value to LS ideal resolution
/// </summary>
private static int RealToIdeal(double i)
{
return TextFormatterImp.RealToIdeal(i);
}
/// <summary>
/// The default behavior of Math.Round() leads to undesirable behavior
/// When used for display mode justified text, where we can find
/// characters belonging to the same word jumping sideways.
/// A word can break among several GlyphRuns. So we need consistent
/// rounding of the width of the GlyphRuns. If the width of one GlyphRun
/// rounds up and the next GlyphRun rounds down then we see characters
/// overlapping and so on.
/// It is too late to change the behavior of our rounding universally
/// so we are making the change targeted to Display mode + Justified text
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private static double RoundDipForDisplayModeJustifiedText(double value, double pixelsPerDip)
{
return TextFormatterImp.RoundDipForDisplayModeJustifiedText(value, pixelsPerDip);
}
/// <summary>
/// Scale LS ideal resolution value to real value
/// </summary>
private static double IdealToRealWithNoRounding(double i)
{
return TextFormatterImp.IdealToRealWithNoRounding(i);
}
/// <summary>
/// This method is used to make sure we do not accumulate rounding errors more than 1 pixel.
/// It is used specifically for Display mode justifed text. The reason is that when text
/// is justified LS will tweek the glyph advance widths to spread the text to fill the paragraph.
/// This process will take place in the ideal metrics domain and hence we can incur large amounts of rounding
/// errors when we convert each metric separately to the real domain.
/// So this method checks the glyph run as a whole and distribute the rounding errors such that in the end
/// the whole glyph run will be have a 1 pixel error at most.
/// </summary>
/// <param name="pwchText"></param>
/// <param name="piGlyphAdvances"></param>
/// <param name="glyphCount"></param>
/// <param name="isRightToLeft"></param>
/// <param name="baselineOrigin"></param>
/// <param name="adjustedAdvanceWidths"></param>
private unsafe void AdjustMetricsForDisplayModeJustifiedText(
char *pwchText,
int *piGlyphAdvances,
int glyphCount,
bool isRightToLeft,
int idealBaselineOriginX,
int idealBaselineOriginY,
double pixelsPerDip,
out Point baselineOrigin,
out IList<double> adjustedAdvanceWidths
)
{
adjustedAdvanceWidths = new double[glyphCount];
baselineOrigin = new Point(RoundDipForDisplayModeJustifiedText(IdealToRealWithNoRounding(idealBaselineOriginX), pixelsPerDip),
RoundDipForDisplayModeJustifiedText(IdealToRealWithNoRounding(idealBaselineOriginY), pixelsPerDip));
int idealRoundedBaselineOriginX = RealToIdeal(baselineOrigin.X);
// Floating point errors were causing issues since it could tip a number
// over the 0.5 boundary and cause the number to round incorrectly.
// By "incorrectly" we mean in a different way than the way the number was
// rounded by the previous GlyphRun (refer to the comment below about calculating the error)
int idealStartingError = idealBaselineOriginX - idealRoundedBaselineOriginX;
if (isRightToLeft)
{
idealStartingError *= -1;
}
if (glyphCount > 0)
{
// We first try to compensate for the rounding errors by adding the accumulated error whenever it rounds to 1
// to the last known space character. This is done because if we were to add the error to the character that caused
// it to grow to be approximately 1 pixel we will end up shifting characters belonging to a word left/right by 1 pixel.
double realAccumulatedRoundedAdvanceWidth = 0;
double realAccumulatedAdvanceWidth = 0;
int idealAccumulatedAdvanceWidth = idealStartingError;
double error = 0;
double realAdvanceWidth = 0;
int indexOfLastKnownSpace = -1;
double realRoundedAdvanceWidth = 0;
for (int i = 0; i < glyphCount; ++i)
{
if (IsSpace(pwchText[i]))
{
indexOfLastKnownSpace = i;
}
idealAccumulatedAdvanceWidth += piGlyphAdvances[i];
realAccumulatedAdvanceWidth = IdealToRealWithNoRounding(idealAccumulatedAdvanceWidth);
realAdvanceWidth = IdealToRealWithNoRounding(piGlyphAdvances[i]);
realRoundedAdvanceWidth = RoundDipForDisplayModeJustifiedText(realAdvanceWidth, pixelsPerDip);
realAccumulatedRoundedAdvanceWidth += realRoundedAdvanceWidth;
// The error is calculated as the difference between where the glyph will be after rounding all the previous
// advance widths and where it would have been if we round the accumulated unrounded advance widths.
// This is necessary to handle the case where a word spans more than one GlyphRun.
// The next GlyphRun will start from the rounded value of its baselineOrigin.X.
// BaselineOrigin.X is equal to the sum of the previous GlyphRun's BaselineOrigin.X + The sum of the GlyphRun's
// AdvanceWidths.
// So a GlyphRun's only knowledge of the preceeding GlyphRun comes from the knowledge of the current GlyphRun's
// baseline origin. Hence, when rounding the current GlyphRun we must pay attention to where will
// the next GlyphRun start after rounding its baseline origin.
//
// Consider this example:
// Suppose you have 2 glyphruns and each glyphrun contains 1 glyph.
// AB.
// Each glyph has an advance width 1.5 pixels.
// If we did not Round the realAccumulatedAdvanceWidth in the error calculation below
// Then we will compute the error to be:
// error = Round(2 - 1.5)
// = 1
// Hence the advance width for the A will be
// adjustedAdvanceWidths = RoundedAw - error
// = 2 - 1
// = 1;
//
// Now consider what will happen to the run that contains the "B". It X value for the
// Baseline origin will be 1.5. So after rounding it will be 2!
// So while A actually occupied 1 pixel B will start on the 3rd pixel and hence the space
// between them will be more than needed.
// Of course the above exmaple is very simplistic. The effect described above will introduce an extra
// space between characters belonging to the same word (if a GlyphRun boundary intersects with it).
// This effect of this will be very visible in scripts like Arabic where the characters of a word are
// joined.
// Note: We round the end result again because floating point errors in high dpi are not trivial.
error += RoundDipForDisplayModeJustifiedText(
realAccumulatedRoundedAdvanceWidth
- RoundDipForDisplayModeJustifiedText(realAccumulatedAdvanceWidth, pixelsPerDip),
pixelsPerDip
);
adjustedAdvanceWidths[i] = realRoundedAdvanceWidth;
if (indexOfLastKnownSpace >= 0)
{
adjustedAdvanceWidths[indexOfLastKnownSpace] -= error;
realAccumulatedRoundedAdvanceWidth -= error;
error = 0;
}
}
// We have a long glyphrun that has no spaces, so we are left with no other option but to add/subtract
// 1 pixel to the characters that increased the error so as it rounds to 1 pixel. By long we mean more than 150 characters with no spaces in between.
// this is because roundtripping the glyph metrics through LS might introduce a 1/300 error per character.
if (indexOfLastKnownSpace < 0)
{
realAccumulatedRoundedAdvanceWidth = 0;
realAccumulatedAdvanceWidth = 0;
idealAccumulatedAdvanceWidth = idealStartingError;
realAdvanceWidth = 0;
realRoundedAdvanceWidth = 0;
error = 0;
for (int i = 0; i < glyphCount; ++i)
{
idealAccumulatedAdvanceWidth += piGlyphAdvances[i];
realAccumulatedAdvanceWidth = IdealToRealWithNoRounding(idealAccumulatedAdvanceWidth);
realAdvanceWidth = IdealToRealWithNoRounding(piGlyphAdvances[i]);
realRoundedAdvanceWidth = RoundDipForDisplayModeJustifiedText(realAdvanceWidth, pixelsPerDip);
realAccumulatedRoundedAdvanceWidth += realRoundedAdvanceWidth;
// The error is calculated as the difference between where the glyph will be after rounding all the previous
// Advance Widths and where it would have been if there were no rounding involved at all for the previous glyphs.
error = RoundDipForDisplayModeJustifiedText(
realAccumulatedRoundedAdvanceWidth
- RoundDipForDisplayModeJustifiedText(realAccumulatedAdvanceWidth, pixelsPerDip),
pixelsPerDip
);
adjustedAdvanceWidths[i] = realRoundedAdvanceWidth - error;
realAccumulatedRoundedAdvanceWidth -= error;
}
}
}
}
// Compute shaped glyph run from LS data
private unsafe GlyphRun ComputeShapedGlyphRun(
LSRun lsrun, // ls run
TextFormatterImp textFormatterImp, // The TextFormatter Implementation
bool originProvided, // flag indicate whether the origin of the run is provided
LSPOINT lsrunOrigin, // physical start of the run
int charCount, // characters count
char *pwchText, // characters for the GlyphRun
ushort *puClusterMap, // cluster map
int glyphCount, // glyph count
ushort *puGlyphs, // glyph indices
int *piJustifiedGlyphAdvances, // glyph advances
GlyphOffset *piiGlyphOffsets, // glyph offsets
bool justify
)
{
TextMetrics.FullTextLine currentLine = Draw.CurrentLine;
Point runOrigin = new Point();
int nominalX = 0;
int nominalY = 0;
if (originProvided)
{
if (currentLine.RightToLeft)
{
// line origin is actually in XY as it is computed by LS during display.
// For simplicity, we always set line origin to (0,0) for LS. This means
// that all the run X positions in an RTL paragraph would always be
// negative values. Therefore, inverting that value would result in an
// ideal distance in U-axis.
lsrunOrigin.x = -lsrunOrigin.x;
}
if (textFormatterImp.TextFormattingMode == TextFormattingMode.Display && justify)
{
LSRun.UVToNominalXY(
Draw.LineOrigin,
Draw.VectorToLineOrigin,
currentLine.LSLineUToParagraphU(lsrunOrigin.x),
lsrunOrigin.y + lsrun.BaselineMoveOffset,
currentLine,
out nominalX,
out nominalY
);
}
else
{
runOrigin = LSRun.UVToXY(
Draw.LineOrigin,
Draw.VectorToLineOrigin,
currentLine.LSLineUToParagraphU(lsrunOrigin.x),
lsrunOrigin.y + lsrun.BaselineMoveOffset,
currentLine
);
}
}
// We have to copy all the arrays here because glyphrun retains its own
// copy of drawing data. It cannot hold on to pointers to LS memory since
// those memory lifetime is bound to the lifetime of the line. But the drawing
// data's bound to lifetime of Drawing.
char[] charString = new char[charCount];
ushort[] clusterMap = new ushort[charCount];
for (int i = 0; i < charCount; i++)
{
charString[i] = pwchText[i];
clusterMap[i] = puClusterMap[i];
}
ushort[] glyphIndices = new ushort[glyphCount];
IList<double> glyphAdvances;
IList<Point> glyphOffsets;
bool isRightToLeft = (lsrun.BidiLevel & 1) != 0;
if (textFormatterImp.TextFormattingMode == TextFormattingMode.Ideal)
{
glyphAdvances = new ThousandthOfEmRealDoubles(textFormatterImp.IdealToReal(lsrun.EmSize, currentLine.PixelsPerDip), glyphCount);
glyphOffsets = new ThousandthOfEmRealPoints(textFormatterImp.IdealToReal(lsrun.EmSize, currentLine.PixelsPerDip), glyphCount);
for (int i = 0; i < glyphCount; i++)
{
glyphIndices[i] = puGlyphs[i];
glyphAdvances[i] = textFormatterImp.IdealToReal(piJustifiedGlyphAdvances[i], currentLine.PixelsPerDip);
glyphOffsets[i] = new Point(
textFormatterImp.IdealToReal(piiGlyphOffsets[i].du, currentLine.PixelsPerDip),
textFormatterImp.IdealToReal(piiGlyphOffsets[i].dv, currentLine.PixelsPerDip)
);
}
}
else
{
if (justify)
{
AdjustMetricsForDisplayModeJustifiedText(
pwchText,
piJustifiedGlyphAdvances,
glyphCount,
isRightToLeft,
nominalX,
nominalY,
currentLine.PixelsPerDip,
out runOrigin,
out glyphAdvances
);
}
else
{
glyphAdvances = new List<double>(glyphCount);
for (int i = 0; i < glyphCount; i++)
{
glyphAdvances.Add(textFormatterImp.IdealToReal(piJustifiedGlyphAdvances[i], currentLine.PixelsPerDip));
}
}
glyphOffsets = new List<Point>(glyphCount);
for (int i = 0; i < glyphCount; i++)
{
glyphIndices[i] = puGlyphs[i];
glyphOffsets.Add(new Point(
textFormatterImp.IdealToReal(piiGlyphOffsets[i].du, currentLine.PixelsPerDip),
textFormatterImp.IdealToReal(piiGlyphOffsets[i].dv, currentLine.PixelsPerDip)
));
}
}
#if CHECK_GLYPHS
if ( lsrun._glyphs != null
&& glyphCount <= lsrun._glyphs.Length)
{
for (int i = 0; i < glyphCount; i++)
{
Debug.Assert(glyphIndices[i] == lsrun._glyphs[i], "Corrupted glyphs");
}
}
#endif
GlyphRun glyphRun = lsrun.Shapeable.ComputeShapedGlyphRun(
runOrigin,
charString,
clusterMap,
glyphIndices,
glyphAdvances,
glyphOffsets,
isRightToLeft,
false // no sideway support yet
);
return glyphRun;
}
// Compute unshaped glyph run from LS data
private unsafe GlyphRun ComputeUnshapedGlyphRun(
LSRun lsrun, // LSrun used to shape the GlyphRun
LsTFlow textFlow, // flow direction
TextFormatterImp textFormatterImp, // The TextFormatter Implementation
bool originProvided, // flag indicate whether the origin of the run is provided
LSPOINT lsrunOrigin, // physical start of the run
int dupRun, // width of the run
int cchText, // character count
char *pwchText, // characters for display
int *piCharAdvances, // character advance widths,
bool justify
)
{
GlyphRun glyphRun = null;
if (lsrun.Type == Plsrun.Text)
{
Debug.Assert(lsrun.Shapeable != null);
Point runOrigin = new Point();
int nominalX = 0;
int nominalY = 0;
if (originProvided)
{
TextMetrics.FullTextLine currentLine = Draw.CurrentLine;
if (textFlow == LsTFlow.lstflowWS)
{
lsrunOrigin.x -= dupRun;
}
if (currentLine.RightToLeft)
{
lsrunOrigin.x = -lsrunOrigin.x;
}
if (textFormatterImp.TextFormattingMode == TextFormattingMode.Display && justify)
{
LSRun.UVToNominalXY(
Draw.LineOrigin,
Draw.VectorToLineOrigin,
currentLine.LSLineUToParagraphU(lsrunOrigin.x),
lsrunOrigin.y + lsrun.BaselineMoveOffset,
currentLine,
out nominalX,
out nominalY
);
}
else
{
runOrigin = LSRun.UVToXY(
Draw.LineOrigin,
Draw.VectorToLineOrigin,
currentLine.LSLineUToParagraphU(lsrunOrigin.x),
lsrunOrigin.y + lsrun.BaselineMoveOffset,
currentLine
);
}
}
// We have to copy the character string here due to the same reason
// we copy glyph arrays in ComputeShapedGlyphRun.
char[] charString = new char[cchText];
IList<double> charWidths;
bool isRightToLeft = (lsrun.BidiLevel & 1) != 0;
if (textFormatterImp.TextFormattingMode == TextFormattingMode.Ideal)
{
charWidths = new ThousandthOfEmRealDoubles(textFormatterImp.IdealToReal(lsrun.EmSize, Draw.CurrentLine.PixelsPerDip), cchText);
for (int i = 0; i < cchText; i++)
{
charString[i] = pwchText[i];
charWidths[i] = textFormatterImp.IdealToReal(piCharAdvances[i], Draw.CurrentLine.PixelsPerDip);
}
}
else
{
if (justify)
{
AdjustMetricsForDisplayModeJustifiedText(
pwchText,
piCharAdvances,
cchText,
isRightToLeft,
nominalX,
nominalY,
Draw.CurrentLine.PixelsPerDip,
out runOrigin,
out charWidths
);
}
else
{
charWidths = new List<double>(cchText);
for (int i = 0; i < cchText; i++)
{
charWidths.Add(textFormatterImp.IdealToReal(piCharAdvances[i], Draw.CurrentLine.PixelsPerDip));
}
}
for (int i = 0; i < cchText; i++)
{
charString[i] = pwchText[i];
}
}
glyphRun = lsrun.Shapeable.ComputeUnshapedGlyphRun(
runOrigin,
charString,
charWidths
);
}
return glyphRun;
}
///// Delegate holder
//
// It is critical to have an object holding all delegates exercised
// by LS within the lifetime of the context, as it guarantees none
// of these delegates is to be garbagged collected.
//
internal unsafe LineServicesCallbacks()
{
_pfnFetchRunRedefined = new FetchRunRedefined(this.FetchRunRedefined);
_pfnFetchLineProps = new FetchLineProps(this.FetchLineProps);
_pfnFetchPap = new FetchPap(this.FetchPap);
_pfnGetRunTextMetrics = new GetRunTextMetrics(this.GetRunTextMetrics);
_pfnGetRunCharWidths = new GetRunCharWidths(this.GetRunCharWidths);
_pfnGetDurMaxExpandRagged = new GetDurMaxExpandRagged(this.GetDurMaxExpandRagged);
_pfnDrawTextRun = new DrawTextRun(this.DrawTextRun);
_pfnGetGlyphsRedefined = new GetGlyphsRedefined(this.GetGlyphsRedefined);
_pfnGetGlyphPositions = new GetGlyphPositions(this.GetGlyphPositions);
_pfnGetAutoNumberInfo = new GetAutoNumberInfo(this.GetAutoNumberInfo);
_pfnDrawGlyphs = new DrawGlyphs(this.DrawGlyphs);
_pfnGetObjectHandlerInfo = new GetObjectHandlerInfo(this.GetObjectHandlerInfo);
_pfnGetRunUnderlineInfo = new GetRunUnderlineInfo(this.GetRunUnderlineInfo);
_pfnGetRunStrikethroughInfo = new GetRunStrikethroughInfo(this.GetRunStrikethroughInfo);
_pfnHyphenate = new Hyphenate(this.Hyphenate);
_pfnGetNextHyphenOpp = new GetNextHyphenOpp(this.GetNextHyphenOpp);
_pfnGetPrevHyphenOpp = new GetPrevHyphenOpp(this.GetPrevHyphenOpp);
_pfnDrawUnderline = new DrawUnderline(this.DrawUnderline);
_pfnDrawStrikethrough = new DrawStrikethrough(this.DrawStrikethrough);
_pfnFInterruptShaping = new FInterruptShaping(this.FInterruptShaping);
_pfnGetCharCompressionInfoFullMixed = new GetCharCompressionInfoFullMixed(this.GetCharCompressionInfoFullMixed);
_pfnGetCharExpansionInfoFullMixed = new GetCharExpansionInfoFullMixed(this.GetCharExpansionInfoFullMixed);
_pfnGetGlyphCompressionInfoFullMixed = new GetGlyphCompressionInfoFullMixed(this.GetGlyphCompressionInfoFullMixed);
_pfnGetGlyphExpansionInfoFullMixed = new GetGlyphExpansionInfoFullMixed(this.GetGlyphExpansionInfoFullMixed);
_pfnEnumText = new EnumText(this.EnumText);
_pfnEnumTab = new EnumTab(this.EnumTab);
}
internal void PopulateContextInfo(ref LsContextInfo contextInfo, ref LscbkRedefined lscbkRedef)
{
lscbkRedef.pfnFetchRunRedefined = _pfnFetchRunRedefined;
lscbkRedef.pfnGetGlyphsRedefined = _pfnGetGlyphsRedefined;
lscbkRedef.pfnFetchLineProps = _pfnFetchLineProps;
contextInfo.pfnFetchLineProps = _pfnFetchLineProps;
contextInfo.pfnFetchPap = _pfnFetchPap;
contextInfo.pfnGetRunTextMetrics = _pfnGetRunTextMetrics;
contextInfo.pfnGetRunCharWidths = _pfnGetRunCharWidths;
contextInfo.pfnGetDurMaxExpandRagged = _pfnGetDurMaxExpandRagged;
contextInfo.pfnDrawTextRun = _pfnDrawTextRun;
contextInfo.pfnGetGlyphPositions = _pfnGetGlyphPositions;
contextInfo.pfnGetAutoNumberInfo = _pfnGetAutoNumberInfo;
contextInfo.pfnDrawGlyphs = _pfnDrawGlyphs;
contextInfo.pfnGetObjectHandlerInfo = _pfnGetObjectHandlerInfo;
contextInfo.pfnGetRunUnderlineInfo = _pfnGetRunUnderlineInfo;
contextInfo.pfnGetRunStrikethroughInfo = _pfnGetRunStrikethroughInfo;
contextInfo.pfnHyphenate = _pfnHyphenate;
contextInfo.pfnGetNextHyphenOpp = _pfnGetNextHyphenOpp;
contextInfo.pfnGetPrevHyphenOpp = _pfnGetPrevHyphenOpp;
contextInfo.pfnDrawUnderline = _pfnDrawUnderline;
contextInfo.pfnDrawStrikethrough = _pfnDrawStrikethrough;
contextInfo.pfnFInterruptShaping = _pfnFInterruptShaping;
contextInfo.pfnGetCharCompressionInfoFullMixed = _pfnGetCharCompressionInfoFullMixed;
contextInfo.pfnGetCharExpansionInfoFullMixed = _pfnGetCharExpansionInfoFullMixed;
contextInfo.pfnGetGlyphCompressionInfoFullMixed = _pfnGetGlyphCompressionInfoFullMixed;
contextInfo.pfnGetGlyphExpansionInfoFullMixed = _pfnGetGlyphExpansionInfoFullMixed;
contextInfo.pfnEnumText = _pfnEnumText;
contextInfo.pfnEnumTab = _pfnEnumTab;
}
private FetchRunRedefined _pfnFetchRunRedefined;
private FetchLineProps _pfnFetchLineProps;
private FetchPap _pfnFetchPap;
private GetRunTextMetrics _pfnGetRunTextMetrics;
private GetRunCharWidths _pfnGetRunCharWidths;
private GetDurMaxExpandRagged _pfnGetDurMaxExpandRagged;
private GetAutoNumberInfo _pfnGetAutoNumberInfo;
private DrawTextRun _pfnDrawTextRun;
private GetGlyphsRedefined _pfnGetGlyphsRedefined;
private GetGlyphPositions _pfnGetGlyphPositions;
private DrawGlyphs _pfnDrawGlyphs;
private GetObjectHandlerInfo _pfnGetObjectHandlerInfo;
private GetRunUnderlineInfo _pfnGetRunUnderlineInfo;
private GetRunStrikethroughInfo _pfnGetRunStrikethroughInfo;
private Hyphenate _pfnHyphenate;
private GetNextHyphenOpp _pfnGetNextHyphenOpp;
private GetPrevHyphenOpp _pfnGetPrevHyphenOpp;
private DrawUnderline _pfnDrawUnderline;
private DrawStrikethrough _pfnDrawStrikethrough;
private FInterruptShaping _pfnFInterruptShaping;
private GetCharCompressionInfoFullMixed _pfnGetCharCompressionInfoFullMixed;
private GetCharExpansionInfoFullMixed _pfnGetCharExpansionInfoFullMixed;
private GetGlyphCompressionInfoFullMixed _pfnGetGlyphCompressionInfoFullMixed;
private GetGlyphExpansionInfoFullMixed _pfnGetGlyphExpansionInfoFullMixed;
private EnumText _pfnEnumText;
private EnumTab _pfnEnumTab;
///// Delegates used by custom object handler
//
private InlineFormat _pfnInlineFormat;
internal InlineFormat InlineFormatDelegate
{
get
{
unsafe
{
if (_pfnInlineFormat == null)
_pfnInlineFormat = new InlineFormat(this.InlineFormat);
return _pfnInlineFormat;
}
}
}
private InlineDraw _pfnInlineDraw;
internal InlineDraw InlineDrawDelegate
{
get
{
if (_pfnInlineDraw == null)
_pfnInlineDraw = new InlineDraw(this.InlineDraw);
return _pfnInlineDraw;
}
}
///// Caught exception occurring inside the callback
//
private void SaveException(Exception e, Plsrun plsrun, LSRun lsrun)
{
e.Data[ExceptionContext.Key] = new ExceptionContext(e.Data[ExceptionContext.Key], e.StackTrace, plsrun, lsrun);
_exception = e;
}
private void SaveNonCLSException(string methodName, Plsrun plsrun, LSRun lsrun)
{
Exception e = new System.Exception(SR.NonCLSException);
e.Data[ExceptionContext.Key] = new ExceptionContext(null, methodName, plsrun, lsrun);
_exception = e;
}
[Serializable()]
private class ExceptionContext
{
public ExceptionContext(object innerContext, string stackTraceOrMethodName, Plsrun plsrun, LSRun lsrun)
{
_stackTraceOrMethodName = stackTraceOrMethodName;
_plsrun = (uint)plsrun;
_lsrun = lsrun;
_innerContext = innerContext;
}
public override string ToString()
{
return _stackTraceOrMethodName;
}
public const string Key = "ExceptionContext";
private object _innerContext;
private string _stackTraceOrMethodName;
private uint _plsrun;
[NonSerialized()]
private LSRun _lsrun;
}
private Exception _exception;
internal Exception Exception
{
get { return _exception; }
set { _exception = value; }
}
/// <summary>
/// Object that owns this callback for the time being until it gets released.
/// It could only be either a FullTextState or a DrawingState and not else as
/// both are only LS clients.
/// </summary>
private object _owner;
internal object Owner
{
get { return _owner; }
set { _owner = value; }
}
private FullTextState FullText
{
get { return _owner as FullTextState; }
}
private DrawingState Draw
{
get { return _owner as DrawingState; }
}
private Rect _boundingBox;
/// <summary>
/// Empty the bounding box
/// </summary>
internal void EmptyBoundingBox()
{
_boundingBox = Rect.Empty;
}
/// <summary>
/// Accumulated bounding box of the current line
/// </summary>
internal Rect BoundingBox
{
get { return _boundingBox; }
}
// accumulate the indexed glyphruns in line enumeration
private ICollection<IndexedGlyphRun> _indexedGlyphRuns;
internal void ClearIndexedGlyphRuns()
{
// Throw aways the list.
_indexedGlyphRuns = null;
}
/// <summary>
/// IndexedGlyphRuns of the line
/// </summary>
internal ICollection<IndexedGlyphRun> IndexedGlyphRuns
{
get
{
if (_indexedGlyphRuns == null)
{
_indexedGlyphRuns = new List<IndexedGlyphRun>(8);
}
return _indexedGlyphRuns;
}
}
}
}
|