Code:
/ DotNET / DotNET / 8.0 / untmp / WIN_WINDOWS / lh_tools_devdiv_wpf / Windows / wcp / Core / MS / Internal / Shaping / IndicShape.cs / 2 / IndicShape.cs
//---------------------------------------------------------------------- // // Microsoft Windows Client Platform // Copyright (C) Microsoft Corporation, 2003 // // File: IndicShape.cs // // Contents: Implementation of Indic shaping engine and its factory // // Created: 06-15-2005 [....] ([....]) // //----------------------------------------------------------------------- // #define VALIDATE_CLUSTER_PARAMETERS using System; using System.Security; using System.Security.Permissions; using System.Diagnostics; using System.Collections; using System.Globalization; using System.Windows; using System.Windows.Media; using System.Windows.Media.TextFormatting; using MS.Internal.FontCache; using MS.Internal.FontFace; using MS.Internal.PresentationCore; namespace MS.Internal.Shaping { ////// IndicCharClass - enumeration of Indic classification /// internal enum IndicCharClass : ushort { Halant, Vedic, VowelSign, Matra, Ra, // all these first character classes have repositioning information in their shape info Neutral, NBSP, Consonant, NuktaConsonant, Nukta, Vowel, ZWJ, ZWNJ, Reserved, // Unknown class -- NumberOfCharClasses, // This needs to be < 16, or else the flags and re-positioning classes will need to shifted CharClassMask = 0xF, // mask off the positioning info flag (used instead of CharShapeInfo.ShaperClassMask) IncludesPositioningInfo = 0x10, // set if classification table entry includes repositioning info // Note that the bits represented by CharClassMask | IncludesPositioningInfo are the // same bits as in CharShapeInfo.ShaperClassMask. This is necessary as the Indic char // classifier expects the CharShapeInfo for each Unicode Indic character to have this // IndicCharCLass information in the ShaperClassMask range. }; internal enum IndicRepositioningClass : ushort { AtStart, BeforeMain, AfterMain, // Repositions to right behind main consonant BeforeSub, // Repositions to in front of first sub-base consonant's halant AfterSub, // Repositions to right behind sub-base consonant BeforePost, // Repositions to in front of first post-base consonant's halant AfterPost, // Repositions to right behind post-base consonant BeforeEnd, // Repositions to end of cluster NumberOfRepositioningClasses, // there're 7 repositioning class members RepositioningClassMask = 0x07, // NumberOfRepositioningClasses must fit within this mask // IsMatra = 0x8, // this bit is set if the character is a matra // Note that the bits represented by // RepositioningClassMask | IsConsonant | IsMatra are the // same bits as in CharShapeInfo.ShaperClassMask. This is necessary as the Indic char // cluster processor expects the CharShapeInfo for each reordering Indic character to // have this IndicRepositioningClass information in the ShaperClassMask range. // for matra's and reph chars, the entry in the char classification table is a packed entity // e{IndicCharClass} + ( e{IndicRepositioningClass} << RepositioningClassShift). So, this // shift constant for normalizing the repositioning class info when it is stored // in a CharShapeInfo entity. When the char classification information is saved // to the ShaperWorkspace shapeinfo array, the IndicCharClass information is // replaced with the repositioning information and the IsConsonant, IsMatra bits // are set as appropriate RepositioningClassShift = 5, }; internal enum ConsonantState : byte { Start, AfterZWJ, AfterReph, AfterMain, AfterSub, AfterPost, AfterDravidianBase, EndOfClusterReached } enum IndicFormFlags : byte { InitForm = 0x01, // only used in Bengali to flag that this character may take init form // Consonant flags (These are generally font dependent and will be cached with the font // data). PostbaseReph = 0x02, DravidianBase = 0x04, RephForm = 0x08, VattuForm = 0x10, SubForm = 0x20, PostForm = 0x40, HalfForm = 0x80, // Matra Flags (these indicate glyph's position relative to the base; These are not font // dependent but will be cached with the consonant form flags). PreBaseMatra = 0x02, // matra modifier AboveBaseMatra = 0x04, // matra modifier BelowBaseMatra = 0x08, // matra modifiers PostBaseMatra = 0x10, // matra modifier }; ////// The Indic Shaping Engine - (shapes Indic text) /// ////// The IShaper and IShapingEngine interfaces are implemented to /// provide the shaping methods for Indic Scripts. /// There are four Indic private types defined/used in this class: /// 1.) IndicShapeFSM - this class manages the shape information /// 2.) IndicClusterCop - this class manages the canonical ordering /// 3.) IndicFontClient - this class manages the font interface /// 4.) IndicCharClassInfo - contains the char classification tables /// internal class IndicShape : BaseShape { ////// All required shaping Features (GSUB) /// private static readonly Feature[] _indicSubstitutionFeatures = { new Feature(0,ushort.MaxValue,(uint)FeatureTags.PrebaseSubstitutions,1), new Feature(0,ushort.MaxValue,(uint)FeatureTags.AboveBaseSubstitutions,1), new Feature(0,ushort.MaxValue,(uint)FeatureTags.BelowBaseSubstitutions,1), new Feature(0,ushort.MaxValue,(uint)FeatureTags.PostbaseSubstitutions,1), new Feature(0,ushort.MaxValue,(uint)FeatureTags.HalantForms,1), new Feature(0,ushort.MaxValue,(uint)FeatureTags.ContextualAlternates,1), }; ////// All required shaping Features (GPOS) /// private static readonly Feature[] _indicPositioningFeatures = { new Feature(0,ushort.MaxValue,(uint)FeatureTags.Kerning,1), new Feature(0,ushort.MaxValue,(uint)FeatureTags.Distances,1), new Feature(0,ushort.MaxValue,(uint)FeatureTags.AboveBaseMarkPositioning,1), new Feature(0,ushort.MaxValue,(uint)FeatureTags.BelowBaseMarkPositioning,1), }; // // Indic Script is only supported scripts // private static readonly ScriptTags[] _supportedScripts = new ScriptTags[] { ScriptTags.Bengali, ScriptTags.Devanagari, ScriptTags.Gujarati, ScriptTags.Gurmukhi, ScriptTags.Kannada, ScriptTags.Malayalam, ScriptTags.Oriya, ScriptTags.Tamil, ScriptTags.Telugu }; ////// Constructor for the Indic Open Type Shaping Engine. /// internal IndicShape() { // Indic diacritics have width _forceDiacriticsToZeroWidth = false; } //-------------------------------------- // // Internal Methods // //-------------------------------------- #region Internal methods ////// IndicShape.SupportedScripts - /// IShapingEngine member override /// ///Our supported scripts (9 Indic scripts). public override ScriptTags[] SupportedScripts { get { return _supportedScripts; } } ////// IndicShape.GetCharClassifier - creates /// ////// This will normally be overridden by derived shapers. It is used in OnLoadFont /// protected override ShaperCharacterClassifier GetCharClassifier(ScriptTags scriptTag, GlyphTypeface fontFace) { return new IndicCharClassifier (scriptTag, fontFace); } internal const CharShapeInfo IndicCharClassMask = (CharShapeInfo)IndicCharClass.CharClassMask; internal const ushort RepositioningInfoShift = (ushort)IndicRepositioningClass.RepositioningClassShift; internal const CharShapeInfo ShapeIncludesPositioningInfoFlag = (CharShapeInfo) IndicCharClass.IncludesPositioningInfo; private bool _forceSerializedOn = false; ////// IndicShape.GetGlyphs - Indic implementation of the GetGlyphs() helper function. /// /// shaping currentRun /// Text item ///number of glyphs ////// Critical - calls critical code /// [SecurityCritical] unsafe protected override int GetGlyphs ( ref ShapingWorkspace currentRun, Item item ) { ushort charsCount = 0; RecordTraceEvent(MS.Utility.EventType.StartEvent, "Indic Init Start"); // initialize the cluster cop/state machine IndicCharClassifier indicClassifier = (IndicCharClassifier)currentRun.CharConverter; IndicShapeFSM stateMachine = new IndicShapeFSM( indicClassifier, currentRun.HasLeadingJoin ); currentRun.IsForceSerializedClusterOn = _forceSerializedOn; RecordTraceEvent(MS.Utility.EventType.EndEvent, "IndicShape Init End"); RecordTraceEvent(MS.Utility.EventType.StartEvent, "IndicShape ShapeIndicText Start"); // Shape and initialize the glyph list // process the char stream, creating shape info, applying features // as necessary char nextChar; while ( currentRun.GetNextChar(out nextChar) ) { ++charsCount; CharShapeInfo nextShape = stateMachine.StepToNextState( nextChar ); // Check for special handling; might be a nukta that needs reordering or a matra // that needs decomposing or maybe a Tamil akhand has just been completed if ((nextShape & CharShapeInfo.RequiresSpecialHandling) != 0) { // special handling required nextShape ^= CharShapeInfo.RequiresSpecialHandling; // clear the special handling flag IndicCharClass nextCharClass = (IndicCharClass)(nextShape & IndicCharClassMask); Debug.Assert (nextCharClass < IndicCharClass.NumberOfCharClasses, "invalid Indic char class index"); switch (nextCharClass) { case IndicCharClass.Nukta: // swap the nukta and its preceeding halant ushort prevGlyph = currentRun.PreviousGlyph; // get halant glyph CharShapeInfo prevShape = currentRun.PreviousShape; currentRun.PreviousGlyph = indicClassifier.ToGlyph(nextChar); currentRun.SetShapeInfo(currentRun.PreviousCharIx,nextShape); currentRun.SetGlyphPropertiesUsingGlyph(prevShape, prevGlyph); continue; case IndicCharClass.Matra: // decompose the matra... char[] matraComponents = indicClassifier.ToDecompositionList(currentRun.CurrentChar); if (matraComponents != null) { currentRun.AddGlyphs(matraComponents.Length - 1); // add space for the extra glyphs currentRun.SetGlyphPropertiesUsingChar(nextShape, matraComponents[0] ); currentRun.CurrentShape = CharShapeInfo.NoFlagsSet; for (int i = 1; i < matraComponents.Length; ++i) { currentRun.SetGlyphProperties(currentRun.CharConverter.ToGlyph( matraComponents[i] ) ); } currentRun.CurrentShape = nextShape; continue; } break; case IndicCharClass.Halant: // a Tamil akhand? if (!stateMachine.IsAkhandPending(ref currentRun)) { // no akhand, so this is end of cluster stateMachine.ResetStateMachine(); } break; } } currentRun.SetGlyphPropertiesUsingChar(nextShape, nextChar); } // now go through the chars/shapes and apply the features cluster by cluster... currentRun.Reset(0,0,charsCount); IndicClusterCop clusterCop = new IndicClusterCop( (IndicFontClient)currentRun.FontClient ); ushort charsCount2 = 0; while (currentRun.ToNextChar()) { ++charsCount2; if (!clusterCop.AddCharToCluster(ref currentRun)) { // an unexpected end of cluster has occurred. we need // to create one or more new clusters... ushort currentCharIx = currentRun.CurrentCharIx; // find the start of the next cluster ushort nextClusterStartIx = (ushort)(currentCharIx + 1); while (nextClusterStartIx < currentRun.CharsCount && (currentRun.GetShapeInfo(nextClusterStartIx) & CharShapeInfo.IsStartOfCluster) == 0) { ++nextClusterStartIx; } // re-cluster the newly "orphaned" characters from the end of this unexpectedly // done cluster int orphanedCharsCount = nextClusterStartIx - currentRun.CurrentCharIx; while ( orphanedCharsCount-- > 0 ) { stateMachine.ResetStateMachine(); CharShapeInfo nextShape = stateMachine.StepToNextState( currentRun.CurrentChar ); if ((nextShape & CharShapeInfo.RequiresInsertedBase) == CharShapeInfo.RequiresInsertedBase) { currentRun.UpdateCurrentGlyphProperties(nextShape); clusterCop.ApplyClusterFeatures(ref currentRun); for (ushort i = currentRun.CharsCount; --i > currentRun.CurrentCharIx;) { currentRun.CharMap[i] += 1; } } if ( orphanedCharsCount > 0 ) { ++charsCount2; currentRun.ToNextChar(); } } } } clusterCop.ApplyClusterFeatures(ref currentRun); RecordTraceEvent(MS.Utility.EventType.EndEvent, "IndicShape ShapeIndicText End"); return currentRun.GlyphsCount; // we're done } ////// IndicShape.ApplySubstitutionFeatures - default implementation of the GetGlyphs() helper function. /// /// shaping currentRun /// Set of gsub features to be applied to the unicode run. ///result of applying features ////// Critical - this method calls unsafe methods. /// [SecurityCritical] protected override OpenTypeLayoutResult ApplySubstitutionFeatures( ref ShapingWorkspace currentRun, FeatureSet featureSet ) { // Apply the Indic Text Features from currentRun ShaperFontClient fontClient = currentRun.FontClient; return fontClient.SubstituteGlyphs( ref currentRun, _indicSubstitutionFeatures, _indicSubstitutionFeatures.Length ); } ////// IndicShape.ApplyPositioningFeatures - generic implementation of the GetGlyphPlacement helper. /// This method goes through a list of glyphs and adds placement information. /// /// the wrapper for glyph advances, offset arrays /// metrics for all the positioning features /// Set of gpos features to be applied to the unicode run. ///result of applying features ////// Critical - calls critical code /// [SecurityCritical] protected override OpenTypeLayoutResult ApplyPositioningFeatures( ref PlacementWorkspace placementInfo, ref LayoutMetrics layoutMetrics, FeatureSet featureSet ) { ShaperFontClient fontClient = placementInfo.FontClient; return fontClient.PositionGlyphs( ref placementInfo, ref layoutMetrics, _indicPositioningFeatures, // In: List of features to apply _indicPositioningFeatures.Length ); } ////// IndicShape.OnLoadFont - IShapingEngine method override. /// ////// This should normally be sufficient for most shapers - the only /// exceptions are those shapers that want to always return true /// /// Script of interest. /// Font face being loaded /// the font client ///True if font supports script. ////// Critical - This method reads into raw font table bits. /// Safe - This method doesn't expose any critical data. /// [SecurityCritical, SecurityTreatAsSafe] public override bool OnLoadFont( ScriptTags scriptTag, GlyphTypeface fontFace, out object shaperFontClient ) { // create the font client for this font/script IndicCharClassifier indicClassifier = new IndicCharClassifier(scriptTag, fontFace); IndicFontClient fontClient = new IndicFontClient( fontFace, indicClassifier ); // if font is happy with this script, return true and give caller the ShaperFontClient // to use in all method calls to us. if (fontClient.ValidateScriptTag(scriptTag)) { shaperFontClient = fontClient; return true; } else { shaperFontClient = null; return false; } } } #endregion ////// Class IndicShapeFSM: /// The Indic state machine /// ////// This class implements the Indic state machine. The GetNextShape() routine /// is the most important method, returning the corresponding shape flags for each /// unicode char in the text stream. /// internal struct IndicShapeFSM { ushort _shapedCharCount; IndicClusterState _currentState; IndicClusterState _previousState; IndicCharClassifier _charConverter; ushort _clusterSize; public const ushort ClusterSizeLimit = (ushort)15; // cluster size limit (big enough for any valid cluster) public const ushort MaximumConsonantOffset = (ushort)10; // better be less than ClusterSizeLimit! char _previousChar; bool _isAkhandPending; char[][] _akhands; public IndicShapeFSM (IndicCharClassifier charConverter, bool hasLeadingZWJ) { _previousState = _currentState = IndicClusterState.StartState; if (hasLeadingZWJ) { _currentState = IndicClusterState.StartLinkedState; } _shapedCharCount = 0; _clusterSize = 0; _charConverter = charConverter; _isAkhandPending = false; _previousChar = '\0'; _akhands = (charConverter.ScriptIx == IndicScriptIndices.Tamil) ? charConverter.TamilAkhands : null; } internal void ResetStateMachine() { _currentState = IndicClusterState.StartState; _clusterSize = 0; _isAkhandPending = false; } ////// IndicShapeFSM.StepToNextState - process the latest character. /// ////// This routine steps the state machine to its next state /// based on the current state and the next char. /// ///state flags for current character appropriate to new state internal CharShapeInfo StepToNextState ( char nextChar ) { // get the cluster state entry from the cluster state table. The char shape flags from this // table form our starting place for CharShapeInfo nextCharShape = _charConverter.ToShapeInfo(nextChar); IndicCharClass nextCharClass = (IndicCharClass)(nextCharShape & IndicShape.IndicCharClassMask); Debug.Assert (nextCharClass < IndicCharClass.NumberOfCharClasses, "invalid Indic char class index"); ushort tableEntry = IndicClusterStateTable[(((int)_currentState - 1) << 4) + (int)nextCharClass]; // 16 entries per state. // if the char shape info includes repositioning information, save it in the lower nibble of the // returned char shape info (we'll need this later when we are preparing the cluster for feature // application)... _previousState = _currentState; // save current state. _currentState = (IndicClusterState)(tableEntry & ClusterStateMask); // some special things to look for... bool isRaConsonant = false; switch (nextCharClass) { case IndicCharClass.Ra: nextCharShape = (CharShapeInfo) nextCharClass; // If this is supposedly a reph, verify that it is, in fact, a reph if (_currentState == IndicClusterState.RaState) { if ((_charConverter.ToFormInfo(nextChar) & IndicFormFlags.RephForm) == 0) { isRaConsonant = true; _currentState = IndicClusterState.ConsonantState; } } break; case IndicCharClass.Vedic: case IndicCharClass.VowelSign: case IndicCharClass.Matra: if ((nextCharShape & (CharShapeInfo) IndicCharClass.IncludesPositioningInfo) == 0) { // matras without positioning info (must be a multi-component matra) // must be specially handled (for adding extra glyphs into glyph // run) nextCharShape |= CharShapeInfo.RequiresSpecialHandling; } else { nextCharShape = (CharShapeInfo) nextCharClass; } break; case IndicCharClass.Halant: nextCharShape = (CharShapeInfo) nextCharClass; if ( _charConverter.ScriptIx == IndicScriptIndices.Tamil) { // check for akhands... if (_isAkhandPending) { ResetStateMachine(); // akhand + halant, this is end of cluster _previousChar = nextChar; ++_shapedCharCount; return nextCharShape; } else { nextCharShape |= CharShapeInfo.RequiresSpecialHandling; } } break; case IndicCharClass.Nukta: nextCharShape = (CharShapeInfo) nextCharClass; // this is a nukta following a halant, we'll need to swap 'em. if (_previousState == IndicClusterState.HalantState || _previousState == IndicClusterState.RephState) { nextCharShape |= CharShapeInfo.RequiresSpecialHandling; } break; default: Debug.Assert((nextCharShape & (CharShapeInfo) IndicCharClass.IncludesPositioningInfo) == 0, "This character class should not have positioning info in its char shape!"); break; } // whenever cluster size is larger than 10, we're gonna get nervous. Make sure // that any further characters aren't consonants and make sure that under no // circumstances will we allow a cluster size greater than 15. if ( (tableEntry & StartOfCluster) == 0) { if (++_clusterSize > MaximumConsonantOffset) { bool isClusterSizeOk = true; switch(nextCharClass) { case IndicCharClass.Ra: case IndicCharClass.Consonant: case IndicCharClass.NuktaConsonant: isClusterSizeOk = false; break; default: if (_clusterSize >= ClusterSizeLimit) { isClusterSizeOk = false; } break; } if (!isClusterSizeOk) { // arbitrary limit on cluster size. If its reached, start a new cluster ResetStateMachine(); tableEntry = IndicClusterStateTable[(((int)_currentState - 1) << 4) + (int)nextCharClass]; // 16 entries per state. _currentState = isRaConsonant ? IndicClusterState.ConsonantState : (IndicClusterState)(tableEntry & ClusterStateMask); _clusterSize = 1; } } } else { _clusterSize = 1; _isAkhandPending = false; } if ( (tableEntry & ClusterShapeFlagsMask) != 0 ) { nextCharShape |= (CharShapeInfo)(tableEntry & ClusterShapeFlagsMask); } _previousChar = nextChar; ++_shapedCharCount; return nextCharShape; } ////// IndicShapeFSM.IsAkhandPending - check if akhand is done. /// ////// This routine steps the state machine to its next state /// based on the current state and the next char. /// ///true if preceding consonant is valid start of akhand ////// Critical - calls critical code /// [SecurityCritical] internal bool IsAkhandPending(ref ShapingWorkspace currentRun) { _isAkhandPending = false; if (_clusterSize > 1) { ushort currentIx = currentRun.CurrentCharIx; for (int i = 0; i < _akhands.Length; ++i) { ushort nextCharIx = (ushort)(currentIx - 1); char[] akhandCandidate = _akhands[i]; if (nextCharIx + akhandCandidate.Length <= currentRun.CharsCount) { _isAkhandPending = true; // we hope! for (int j = 0; j < akhandCandidate.Length; ++j) { if (currentRun.GetChar(nextCharIx) != akhandCandidate[j]) { _isAkhandPending = false; // nope, not this one! break; } ++nextCharIx; } if (_isAkhandPending) { // we've just started an akhand sequence! break; } } } } return _isAkhandPending; } ////// IndicClusterState - enumeration of Indic cluster states /// ordinal position /// private enum IndicClusterState : byte { NoChange, StartState, StartLinkedState, NBSPState, StandaloneState, VowelState, VowelZWJState, ConsonantState, RaState, HalantState, RephState, VowelSignState, VedicState, SecondVedicState, ZWJState, ZWJHalantState, MatraState, FinalState, NumberOfIndicClusterStates, ClusterStateMask = 0x1f, // enough bits for all the cluster states, must be // <= CharShapeInfo.ShapeClassMask }; private const ushort ClusterStateMask = (ushort) IndicClusterState.ClusterStateMask; private const ushort ClusterShapeFlagsMask = (ushort) CharShapeInfo.ShapeFlagsMask; // These values are used to populate the state tables. Each entry is a packed // value with the next state in the lower byte, and the shape flags in the upper // byte. The GetNextShape routine unpacks these entries. // The "ToState" definitions generally mean that the current character is // a continuation of the current cluster and that only the state machine's state // is changing private const ushort InvalidBase = (ushort)CharShapeInfo.RequiresInsertedBase; private const ushort ZWControlChar = (ushort) CharShapeInfo.IsUnicodeLayoutControl; private const ushort StartOfCluster = (ushort) CharShapeInfo.IsStartOfCluster; private const ushort NoChange = (ushort )IndicClusterState.NoChange; private const ushort ToStartState = (ushort )IndicClusterState.StartState; private const ushort ToStartLinkedState = (ushort )IndicClusterState.StartLinkedState; private const ushort StartCluster = ToStartState | StartOfCluster; private const ushort ToNBSPState = (ushort )IndicClusterState.NBSPState; private const ushort StartNBSPCluster = ToNBSPState | StartOfCluster; private const ushort ToVowelState = (ushort )IndicClusterState.VowelState; private const ushort StartVowelCluster = ToVowelState | StartOfCluster; private const ushort ToConsonantState = (ushort )IndicClusterState.ConsonantState; private const ushort StartConsonantCluster = ToConsonantState | StartOfCluster; private const ushort ToRaState = (ushort )IndicClusterState.RaState; private const ushort StartRephConsonantCluster = ToRaState | StartOfCluster; // These state machine entries are used to respond to ZWJ/ZWNJ characters in the text stream private const ushort ToZWJState = (ushort )IndicClusterState.ZWJState; private const ushort ToVowelZWJState = (ushort )IndicClusterState.VowelZWJState; private const ushort ToStandaloneState = (ushort )IndicClusterState.StandaloneState; private const ushort StartStandaloneCluster = ToStandaloneState + StartOfCluster; private const ushort ToZWJHalantState = (ushort )IndicClusterState.ZWJHalantState; private const ushort ToMatraState = (ushort )IndicClusterState.MatraState; private const ushort ToHalantState = (ushort )IndicClusterState.HalantState; private const ushort ToRephState = (ushort )IndicClusterState.RephState; private const ushort ToVedicState = (ushort )IndicClusterState.VedicState; private const ushort To2ndVedicState = (ushort )IndicClusterState.SecondVedicState; private const ushort ToVowelSignState = (ushort )IndicClusterState.VowelSignState; private const ushort ToFinalState = (ushort )IndicClusterState.FinalState; private const ushort EndOfCluster = ToStartState; // private const ushort StartNextCluster = StartOfCluster; // The syllable state machine state table. For each current state // there are 16 entries - one for each char class (see IndicCharClass, 14 classes) and // 2 extra for padding - so that the first entry for any current state can // be found at "current state << 4" private static readonly ushort[] IndicClusterStateTable = //new IndicSyllableState[] { // State Table Entry Input Char Class StartCluster | InvalidBase,// Halant (mark) StartCluster | InvalidBase,// Vedic (mark) StartCluster | InvalidBase,// VowelSign (mark) StartCluster | InvalidBase,// Matra (mark) StartRephConsonantCluster, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- StartConsonantCluster, // Consonant -- StartConsonantCluster, // Consonant with Nukta -- StartCluster | InvalidBase,// Nukta (mark) StartVowelCluster, // Vowel (base) StartStandaloneCluster | InvalidBase, // ZWJ (assume it needs a dotted circle) StartCluster, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding // StartLinkedState Input Char Class ToZWJHalantState, // Halant (mark) ToStartLinkedState, // Vedic (mark) ToStartLinkedState, // VowelSign (mark) ToMatraState, // Matra (mark) StartRephConsonantCluster, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- StartConsonantCluster, // Consonant -- StartConsonantCluster, // Consonant with Nukta -- ToStartLinkedState, // Nukta (mark) StartVowelCluster, // Vowel (base) ToZWJState, // ZWJ StartCluster, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding // NBSPState Input Char Class EndOfCluster, // Halant (mark) EndOfCluster, // Vedic (mark) EndOfCluster, // VowelSign (mark) ToMatraState, // Matra (mark) StartRephConsonantCluster, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- StartConsonantCluster, // Consonant -- StartConsonantCluster, // Consonant with Nukta -- EndOfCluster, // Nukta (mark) StartVowelCluster, // Vowel (base) ToStandaloneState, // ZWJ EndOfCluster, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding // Standalone Input Char Class EndOfCluster, // Halant (mark) EndOfCluster, // Vedic (mark) StartCluster | InvalidBase,// VowelSign (mark) StartCluster | InvalidBase,// Matra (mark) StartRephConsonantCluster, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- StartConsonantCluster, // Consonant -- StartConsonantCluster, // Consonant with Nukta -- EndOfCluster, // Nukta (mark) StartVowelCluster, // Vowel (base) StartStandaloneCluster | InvalidBase, // ZWJ (assume it needs a dotted circle) StartCluster, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding // VowelState Input Char Class ToHalantState, // Halant (mark) ToVedicState, // Vedic (mark) ToVowelSignState, // VowelSign (mark) ToMatraState, // Matra (mark) StartRephConsonantCluster, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- StartConsonantCluster, // Consonant -- StartConsonantCluster, // Consonant with Nukta -- ToVowelState, // Nukta (mark) StartVowelCluster, // Vowel (base) ToVowelZWJState, // ZWJ ToVowelZWJState, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding // VowelZWJState Input Char Class ToHalantState, // Halant (mark) StartCluster | InvalidBase,// Vedic (mark) StartCluster | InvalidBase,// VowelSign (mark) ToMatraState, // Matra (mark) ToVowelState, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- ToVowelState, // Consonant -- StartConsonantCluster, // Consonant with Nukta -- StartCluster | InvalidBase,// Nukta (mark) StartVowelCluster, // Vowel (base) StartStandaloneCluster | InvalidBase, // ZWJ (assume it needs a dotted circle) StartCluster, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding // ConsonantState Input Char Class ToHalantState, // Halant (mark) ToVedicState, // Vedic (mark) ToVowelSignState, // VowelSign (mark) ToMatraState, // Matra (mark) StartRephConsonantCluster, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- StartConsonantCluster, // Consonant -- StartConsonantCluster, // Consonant with Nukta -- ToConsonantState, // Nukta (mark) StartVowelCluster, // Vowel (base) ToZWJState, // ZWJ ToConsonantState, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding // RaState Input Char Class ToRephState, // Halant (mark) ToVedicState, // Vedic (mark) ToVowelSignState, // VowelSign (mark) ToMatraState, // Matra (mark) StartRephConsonantCluster, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- StartConsonantCluster, // Consonant -- StartConsonantCluster, // Consonant with Nukta -- ToRaState, // Nukta (mark) StartVowelCluster, // Vowel (base) ToZWJState, // ZWJ ToConsonantState | ZWControlChar, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding // HalantState Input Char Class StartCluster | InvalidBase,// Halant (mark) ToVedicState, // Vedic (mark) ToVowelSignState, // VowelSign (mark) StartCluster | InvalidBase,// Matra (mark) ToConsonantState, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- ToConsonantState, // Consonant -- ToConsonantState, // Consonant with Nukta -- ToHalantState, // Nukta (mark) StartVowelCluster, // Vowel (base) ToZWJState, // ZWJ EndOfCluster | ZWControlChar, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding // RephState Input Char Class StartCluster | InvalidBase,// Halant (mark) ToVedicState, // Vedic (mark) ToVowelSignState, // VowelSign (mark) StartCluster | InvalidBase,// Matra (mark) ToConsonantState, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- ToConsonantState, // Consonant -- ToConsonantState, // Consonant with Nukta -- ToRephState, // Nukta (mark) ToVowelState, // Vowel (base) ToZWJState, // ZWJ EndOfCluster | ZWControlChar, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding // VowelSignState Input Char Class StartCluster | InvalidBase,// Halant (mark) ToVedicState, // Vedic (mark) StartCluster | InvalidBase,// VowelSign (mark) StartCluster | InvalidBase,// Matra (mark) StartRephConsonantCluster, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- StartConsonantCluster, // Consonant -- StartConsonantCluster, // Consonant with Nukta -- StartCluster | InvalidBase,// Nukta (mark) StartVowelCluster, // Vowel (base) StartStandaloneCluster | InvalidBase, // ZWJ (assume it needs a dotted circle) EndOfCluster, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding // VedicState Input Char Class StartCluster | InvalidBase,// Halant (mark) To2ndVedicState, // Vedic (mark) StartCluster | InvalidBase,// VowelSign (mark) StartCluster | InvalidBase,// Matra (mark) StartRephConsonantCluster, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- StartConsonantCluster, // Consonant -- StartConsonantCluster, // Consonant with Nukta -- StartCluster | InvalidBase,// Nukta (mark) StartVowelCluster, // Vowel (base) StartStandaloneCluster | InvalidBase, // ZWJ (assume it needs a dotted circle) EndOfCluster, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding // SecondVedicState Input Char Class StartCluster | InvalidBase, // Halant (mark) EndOfCluster, // Vedic (mark) StartCluster | InvalidBase,// VowelSign (mark) StartCluster | InvalidBase,// Matra (mark) StartRephConsonantCluster, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- StartConsonantCluster, // Consonant -- StartConsonantCluster, // Consonant with Nukta -- StartCluster | InvalidBase,// Nukta (mark) StartVowelCluster, // Vowel (base) StartStandaloneCluster | InvalidBase, // ZWJ (assume it needs a dotted circle) EndOfCluster, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding // ZWState Input Char Class ToZWJHalantState, // Halant (mark) ToVedicState, // Vedic (mark) ToVowelSignState, // VowelSign (mark) ToMatraState, // Matra (mark) ToConsonantState, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- ToConsonantState, // Consonant -- ToConsonantState, // Consonant with Nukta -- ToZWJState, // Nukta (mark) ToVowelState, // Vowel (base) StartStandaloneCluster | InvalidBase, // ZWJ (assume it needs a dotted circle) StartCluster, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding // ZWHalant Input Char Class StartCluster | InvalidBase,// Halant (mark) StartCluster | InvalidBase,// Vedic (mark) StartCluster | InvalidBase,// VowelSign (mark) StartCluster | InvalidBase,// Matra (mark) ToConsonantState, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- ToConsonantState, // Consonant -- ToConsonantState, // Consonant with Nukta -- StartCluster | InvalidBase,// Nukta (mark) StartVowelCluster, // Vowel (base) StartStandaloneCluster | InvalidBase, // ZWJ (assume it needs a dotted circle) StartCluster, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding // MatraState Input Char Class ToMatraState, // Halant (mark) ToVedicState, // Vedic (mark) ToVowelSignState, // VowelSign (mark) ToMatraState, // Matra (mark) StartRephConsonantCluster, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- StartConsonantCluster, // Consonant -- StartConsonantCluster, // Consonant with Nukta -- ToMatraState, // Nukta (mark) StartVowelCluster, // Vowel (base) StartStandaloneCluster | InvalidBase, // ZWJ (assume it needs a dotted circle) StartCluster, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding //FinalState Input Char Class // This state is identical to StartState StartCluster | InvalidBase, // Halant (mark) StartCluster | InvalidBase, // Vedic (mark) StartCluster | InvalidBase, // VowelSign (mark) StartCluster | InvalidBase, // Matra StartRephConsonantCluster, // Ra -- StartCluster, // Neutral, Spaces and Punctuation and such -- StartNBSPCluster, // NBSP -- StartConsonantCluster, // Consonant -- StartConsonantCluster, // Consonant with Nukta -- StartCluster | InvalidBase, // Nukta (mark) StartVowelCluster, // Vowel (base) StartStandaloneCluster | InvalidBase, // ZWJ (assume it needs a dotted circle) StartCluster, // ZWNJ StartCluster, // Unknown class -- NoChange,NoChange, // padding }; } internal struct IndicClusterCop { IndicScriptIndices _scriptIx; ConsonantState _consonantState; IndicCharClassifier _charConverter; IndicFormFlags _matraPositionFlags; Feature[] _textFeatures; uint[] _featureTags; IndicFontClient _fontClient; ushort _firstCharIx; ushort _lastHalantOffset; ushort _lastConsonantOffset; ushort _lastNuktaOffset; ushort _preMainMatraOffset; ushort _vowelOffset; ushort _clusterSize; ushort _unmappedGlyphsCount; ushort _movedToBeforeSubCount; ushort _movedToBeforePostCount; ushort _movedToAfterSubCount; ushort _movedToAfterPostCount; ushort _movedToBeforeEndCount; ushort _halfOffset; ushort _mainOffset; ushort _subOffset; ushort _postOffset; ushort _lastMainOffset; ushort _rephOffset; ushort _postbaseRaOffset; ushort _ZWJOffset; bool _useV2FontRules; bool _isVowelPresent; bool _isZWJPresent; bool _isMatraPresent; bool _isSplitMatraPresent; bool _isNuktaPresent; bool _isHalantActive; bool _isMatraReorderPending; bool _isPostMatraHalantPresent; bool _isPostMatraNuktaPresent; bool _isRephReorderPending; bool _isVattuPresent; bool _isReorderingNecessary; bool _gurmukhiRephGoesAfterPostMatra; IndicRepositioningClass _lastMatraRepositioningClass; private const CharShapeInfo RepositioningClassMask = (CharShapeInfo)IndicRepositioningClass.RepositioningClassMask; /// /// Critical - calls critical code /// [SecurityCritical] public IndicClusterCop( IndicFontClient fontClient ) { _firstCharIx = 0; _vowelOffset = _mainOffset = _lastMainOffset = 0; _subOffset = _postOffset = 0; _rephOffset = _postbaseRaOffset = 0; _lastConsonantOffset = _lastHalantOffset = _lastNuktaOffset = 0; _preMainMatraOffset = 0; _ZWJOffset = 0; _halfOffset = 0xffff; _movedToBeforeSubCount = 0; _movedToBeforePostCount = 0; _movedToAfterSubCount = 0; _movedToAfterPostCount = 0; _movedToBeforeEndCount = 0; _isReorderingNecessary = false; _consonantState = ConsonantState.Start; _isVowelPresent = false; _isZWJPresent = false; _isNuktaPresent = false; _isHalantActive = false; _isRephReorderPending = false; _isVattuPresent = false; _gurmukhiRephGoesAfterPostMatra = false; _matraPositionFlags = 0; _isMatraPresent = _isSplitMatraPresent = false; _isMatraReorderPending = false; _isPostMatraNuktaPresent = _isPostMatraHalantPresent = false; _lastMatraRepositioningClass = IndicRepositioningClass.AtStart; _clusterSize = 0; _unmappedGlyphsCount = 0; _textFeatures = new Feature[1]; _textFeatures[0] = new Feature(0,0,0,0); _fontClient = fontClient; _charConverter = fontClient.IndicCharConverter; _featureTags = fontClient.ScriptGsubFeaturesList; // new style fonts expect post-main consonant features // to be applied starting at the preceding halant, whereas // the old style fonts expect the post-main consonants to // be reversed from h+C to C+h before applying the features _useV2FontRules = fontClient.IsIndicV2Font; _scriptIx = _charConverter.ScriptIx; } public void ResetClusterInfo( ushort firstCharInCluster ) { if (_consonantState != ConsonantState.Start) { _consonantState = ConsonantState.Start; _isVowelPresent = false; _isZWJPresent = false; _isNuktaPresent = false; _isHalantActive = false; _isVattuPresent = false; _gurmukhiRephGoesAfterPostMatra = false; _mainOffset = _lastMainOffset = 0; _subOffset = _postOffset = 0; _rephOffset = 0; _lastConsonantOffset = 0; _preMainMatraOffset = 0; _halfOffset = 0xffff; } _firstCharIx = firstCharInCluster; _matraPositionFlags = 0; _lastHalantOffset = _lastNuktaOffset = 0; _isMatraPresent = _isSplitMatraPresent = _isPostMatraNuktaPresent = _isPostMatraHalantPresent = false; _clusterSize = 0; _unmappedGlyphsCount = 0; } ////// IndicClusterCop.ApplyClusterFeatures - finalize the preceding cluster. This means do /// any reordering and apply features as appropriate /// ////// This routine reorders matras, applies all the features that need to be applied, /// and does any final reordering /// ///state flags for current character appropriate to new state ////// Critical - calls critical code, uses unsafe accessors /// [SecurityCritical] internal void ApplyClusterFeatures(ref ShapingWorkspace currentRun ) { if (_clusterSize > 1) { FinalizeOffsets( ref currentRun, _clusterSize, true ); // make sure cluster offsets are right bool isInitFormPending = false; ushort preBaseRephGlyphIx = 0; ushort preBaseRephGlyph = 0; // apply features for (int i = 0; i < _featureTags.Length; ++i) { _textFeatures[0].Tag = _featureTags[i]; _textFeatures[0].Parameter = 1; _textFeatures[0].StartIndex = _firstCharIx; _textFeatures[0].Length = _clusterSize; switch ( _featureTags[i] ) { case (uint)FeatureTags.InitialForms: // this better be the last Bengali feature Debug.Assert( i + 1 == _featureTags.Length, "init isn't last feature!") ; _textFeatures[0].Length = 1; isInitFormPending = true; continue; // wait till final reordering is done case (uint)FeatureTags.NuktaForms: if (!_isNuktaPresent) { continue; // don't do this one } break; case (uint)FeatureTags.Akhands: // Since I've changed the order of applying reph and akhand features // I don't think I need to worry about applying this feature to the // reph because the reph has already formed (so shouldn't participate // in any lookups for this feature) if (/*_rephOffset > 0 || */_postbaseRaOffset > 0) { /*ushort startOffset = 0; // if (_rephOffset > 0 && (_postbaseRaOffset == 0 || _postbaseRaOffset > _rephOffset)) { // don't apply this feature to reph (or the prebase ra, whichever's // first) Since there may be characters after the reph, apply to the // reph, then apply to everything after the reph... _textFeatures[0].Length = _rephOffset; _fontClient.SubstituteGlyphs( ref currentRun, _textFeatures, 1 ); // start again past the reph. If necessary, use LigatureCounts to take // account of the possibility that we've said the reph is a single // character due to split matra adjustments startOffset = (ushort)(_rephOffset + (!_isSplitMatraPresent ? (ushort)1 : currentRun.GlyphInfoList.LigatureCounts[firstGlyphIx + _rephOffset])); _textFeatures[0].Length = (ushort)(_clusterSize - startOffset); } if (_postbaseRaOffset > 0) { if (_postbaseRaOffset <= startOffset) { continue; // must be right after reph. We're done with akhands. } */ // don't apply this feature to Malayalam prebase ra _textFeatures[0].Length = (ushort)(_postbaseRaOffset ); /*} if (startOffset >= _clusterSize) { continue; } _textFeatures[0].StartIndex = (ushort)(_firstCharIx + startOffset); */ } break; case (uint)FeatureTags.RephForm: if (_rephOffset == 0) { continue; // don't do this one } _textFeatures[0].StartIndex = (ushort)(_firstCharIx + _rephOffset); // only apply to reph if (_isSplitMatraPresent) { ushort rephGlyphIx = currentRun.GetGlyphIx((ushort)(_firstCharIx + _rephOffset)); _textFeatures[0].Length = currentRun.GlyphInfoList.LigatureCounts[rephGlyphIx]; } else { _textFeatures[0].Length = 2; } break; case (uint)FeatureTags.PrebaseForms: if ( _postbaseRaOffset == 0 || !_useV2FontRules ) { continue; // don't do this one } else { ushort prebaseRephIx = (ushort)(_firstCharIx + _postbaseRaOffset); preBaseRephGlyphIx = currentRun.GetGlyphIx(prebaseRephIx); preBaseRephGlyph = currentRun.GetGlyph(preBaseRephGlyphIx); _textFeatures[0].StartIndex = prebaseRephIx; _textFeatures[0].Length = _isSplitMatraPresent ? currentRun.GlyphInfoList.LigatureCounts[preBaseRephGlyphIx] : (ushort) 2; } break; case (uint)FeatureTags.BelowBaseForms: if (_subOffset > 0 || _lastMainOffset > _mainOffset) { // apply to consonants pre main to last sub _textFeatures[0].StartIndex = (ushort)(_firstCharIx + _mainOffset + 1); // apply through sub consonants } else if (!_isVattuPresent) { continue; // don't apply this one } _textFeatures[0].Length = (ushort)((_postOffset > 0 ? _postOffset : _clusterSize) - ( _mainOffset + 1)); break; case (uint)FeatureTags.HalfForms: if (_lastMainOffset <= _halfOffset) { continue; // don't apply this one } // apply to consonants from first half to last main _textFeatures[0].StartIndex = (ushort)(_firstCharIx + _halfOffset); _textFeatures[0].Length = (ushort)(_lastMainOffset - _halfOffset); break; case (uint)FeatureTags.PostbaseForms: // pre-Vista kartika font uses PSTF feature to form prebase rephs if ( _postbaseRaOffset != 0 && !_useV2FontRules) { ushort prebaseRephIx = (ushort)(_firstCharIx + _postbaseRaOffset); preBaseRephGlyphIx = currentRun.GetGlyphIx(prebaseRephIx); preBaseRephGlyph = currentRun.GetGlyph(preBaseRephGlyphIx); if (_postbaseRaOffset < _postOffset) { _textFeatures[0].StartIndex = (ushort)(_firstCharIx + _postbaseRaOffset); _textFeatures[0].Length = (ushort)(_clusterSize - _postbaseRaOffset); break; } } else if (_postOffset == 0) { continue; // don't apply this one } // apply to post consonants _textFeatures[0].StartIndex = (ushort)(_firstCharIx + _postOffset); _textFeatures[0].Length = (ushort)(_clusterSize - _postOffset); break; case (uint)FeatureTags.VattuVariants: if (!(_isVattuPresent || _subOffset > 0)) { continue; // don't apply this one } break; default: break; } // Apply the Indic Text Features from currentRun _fontClient.SubstituteGlyphs( ref currentRun, _textFeatures, 1 ); if (preBaseRephGlyphIx != 0) { // check if the first prebase ra glyph is changed. If not, prebase ra // didn't form if (currentRun.GetGlyph(preBaseRephGlyphIx) == preBaseRephGlyph) { _postbaseRaOffset = 0; } preBaseRephGlyphIx = 0; } } // do post-feature reordering ReorderClusterFinal( ref currentRun ); if (isInitFormPending) { // Apply the Bengali init feature to the first character _fontClient.SubstituteGlyphs( ref currentRun, _textFeatures, 1 ); } } else { // the previous cluster is a single character cluster. The only thing we need // to do is check if its a dotted circle with a prebase matra ushort previousCharIx = currentRun.IsFinished ? currentRun.CurrentCharIx : currentRun.PreviousCharIx; CharShapeInfo previousShape = currentRun.GetShapeInfo(previousCharIx); if ((previousShape & CharShapeInfo.RequiresInsertedBase) == CharShapeInfo.RequiresInsertedBase) { // the cluster is a "dotted circle" cluster. So, check for prebase // matra. First test is for a split matra. If its a split matra // use the repositioning info for the first component matra if ((IndicCharClass)(previousShape & IndicShape.IndicCharClassMask) == IndicCharClass.Matra) { char previousChar = currentRun.GetChar(previousCharIx); CharShapeInfo matraShapeInfo = _charConverter.ToShapeInfo(previousChar); bool isSplitMatra = ((matraShapeInfo & IndicShape.ShapeIncludesPositioningInfoFlag) == 0); if (isSplitMatra) { char [] matraComponents = _charConverter.ToDecompositionList(previousChar); if (matraComponents != null) { matraShapeInfo = (CharShapeInfo)_charConverter.ToRepositioningClass(matraComponents[0]); } } else { matraShapeInfo = (CharShapeInfo)GetRepositioningClass(matraShapeInfo); } // Now check if matra's a before main matra... if ((ushort)matraShapeInfo <= (ushort)IndicRepositioningClass.BeforeMain) { // the matra is "before main" so // swap the dotted circle glyph and its first matra ushort dottedCircleGlyphIx = currentRun.GetGlyphIx(previousCharIx); currentRun.SetGlyph(dottedCircleGlyphIx, currentRun.GetGlyph((ushort)(dottedCircleGlyphIx + 1))); currentRun.SetGlyph((ushort)(dottedCircleGlyphIx + 1), _fontClient.DottedCircleGlyph); } } } } } ////// Critical - calls critical code /// [SecurityCritical] private bool ReorderClusterFinal(ref ShapingWorkspace currentRun) { // now that initial shaping has been done, make adjustments if // necessary to properly reposition matras/reph when we have // a non-conjunct forming sequence of pre-sub consonants if (_isReorderingNecessary) { ushort toPosition = 0; bool foundReorderingAnchor = FindLateRepositionAnchor(ref currentRun); // do the reordering necessary... // (first do matras, vowel signs, then reph) if (_isMatraReorderPending) { if (foundReorderingAnchor) { toPosition = _lastMainOffset; _mainOffset -= 1; ushort glyphsCount = 1; ushort matraGlyphIx = currentRun.GetGlyphIx((ushort)(_firstCharIx + _preMainMatraOffset)); while (matraGlyphIx + glyphsCount < currentRun.GlyphsCount && currentRun.GlyphInfoList.FirstChars[matraGlyphIx + glyphsCount] == currentRun.GlyphInfoList.FirstChars[matraGlyphIx]) { glyphsCount += 1; } RepositionGlyphs( ref currentRun, _preMainMatraOffset, toPosition, 1, glyphsCount); } if (_isRephReorderPending && _postbaseRaOffset == 0) { foundReorderingAnchor = FindLateRepositionAnchor(ref currentRun); } _preMainMatraOffset = 0; _isMatraReorderPending = false; } // if there's a pre-base reph to move, nows the time... if (_postbaseRaOffset > 0) { // move the post base ra to the appropriate main consonant toPosition = _mainOffset; if (toPosition < _postbaseRaOffset) { ushort raGlyphIx = currentRun.GetGlyphIx((ushort)(_firstCharIx + _postbaseRaOffset)); ushort charCount = currentRun.GlyphInfoList.LigatureCounts[raGlyphIx]; if (_postbaseRaOffset > _lastMainOffset) { toPosition = _lastMainOffset; _lastMainOffset += charCount; } if (_postbaseRaOffset > _rephOffset && toPosition <= _rephOffset) { _rephOffset += charCount; } RepositionGlyphs( ref currentRun, _postbaseRaOffset, toPosition, charCount, 1 ); } if (_isRephReorderPending) { foundReorderingAnchor = FindLateRepositionAnchor(ref currentRun); } _postbaseRaOffset = 0; } // And, finally, if there's a reph to revisit do it now... if (_isRephReorderPending) { _isRephReorderPending = false; if ( foundReorderingAnchor && _rephOffset > _lastMainOffset ) { ushort rephGlyphIx = currentRun.GetGlyphIx((ushort)(_firstCharIx + _rephOffset)); ushort charCount = currentRun.GlyphInfoList.LigatureCounts[rephGlyphIx]; RepositionGlyphs( ref currentRun, _rephOffset, _lastMainOffset, charCount, 1 ); } } } // done reordering, reset all these counters... _movedToBeforeSubCount = 0; _movedToBeforePostCount = 0; _movedToAfterSubCount = 0; _movedToAfterPostCount = 0; _movedToBeforeEndCount = 0; _isMatraPresent = _isPostMatraNuktaPresent = _isPostMatraHalantPresent = false; _isReorderingNecessary = false; return true; } private ushort GetRepositionOffsetAndUpdateCluster (IndicRepositioningClass repositionClass, bool isReph, ushort charsMovingCount) { ushort offsetFromEnd = 0; ushort toPosition = 0; bool useOffsetFromEnd = false; switch (repositionClass) { case IndicRepositioningClass.AtStart: toPosition = _halfOffset <= _mainOffset ? _halfOffset : _mainOffset; goto MovingToBeforeMain; case IndicRepositioningClass.BeforeMain: toPosition = _mainOffset; MovingToBeforeMain: if (_lastMainOffset > toPosition) { // note this position (we're still not sure if the main // consonants will be forming a conjunct form). If // last main > main consonant ix, then we'll need to // check if matra needs reordering after features are // applied. (if Malayam, check for reordering if // last main > half offset) if (toPosition == _mainOffset || _scriptIx == IndicScriptIndices.Malayalam) { _preMainMatraOffset = toPosition; _isReorderingNecessary = true; _isMatraReorderPending = true; } } // adjust the premain and "main" consonants' offsets // so that they are now after this newly inserted matra/sign/prebase ra if (_halfOffset == toPosition) { _halfOffset += charsMovingCount; } _mainOffset += charsMovingCount; _lastMainOffset += charsMovingCount; break; case IndicRepositioningClass.AfterMain: if (_subOffset > 0) { toPosition = (ushort)(_subOffset - _movedToBeforeSubCount); } else if (_postOffset > 0) { toPosition = (ushort)(_postOffset - _movedToBeforePostCount); } else { useOffsetFromEnd = true; offsetFromEnd = (ushort) ( _movedToBeforeSubCount + _movedToAfterSubCount + _movedToBeforePostCount + _movedToAfterPostCount + _movedToBeforeEndCount ); } break; case IndicRepositioningClass.BeforeSub: if (_subOffset > 0) { toPosition = _subOffset; } else if (_postOffset > 0) { toPosition = (ushort)(_postOffset - _movedToBeforePostCount); } else { useOffsetFromEnd = true; offsetFromEnd = (ushort) ( _movedToAfterSubCount + _movedToBeforePostCount + _movedToAfterPostCount + _movedToBeforeEndCount ); } _movedToBeforeSubCount += charsMovingCount; break; case IndicRepositioningClass.AfterSub: if ( _postOffset > 0) { toPosition = (ushort)(_postOffset - _movedToBeforePostCount); // put before post offset } else { useOffsetFromEnd = true; offsetFromEnd = (ushort) ( _movedToBeforePostCount + _movedToAfterPostCount + _movedToBeforeEndCount ); } _movedToAfterSubCount += charsMovingCount; break; case IndicRepositioningClass.BeforePost: if ( _postOffset > 0) { toPosition = _postOffset; // put before post offset } else { useOffsetFromEnd = true; if (isReph) { // special case for Gurmukhi - when there's no post consonant but // there is an after-post matra, then move the reph after the // matra (unless its one of the two matras, 0xa3e or 0xa40) if (_gurmukhiRephGoesAfterPostMatra) { _gurmukhiRephGoesAfterPostMatra = false; offsetFromEnd = (ushort)( _movedToBeforeEndCount ); _movedToAfterPostCount += charsMovingCount; break; } } offsetFromEnd = (ushort) ( _movedToAfterPostCount + _movedToBeforeEndCount ); } _movedToBeforePostCount += charsMovingCount; break; case IndicRepositioningClass.AfterPost: useOffsetFromEnd = true; offsetFromEnd = (ushort)( _movedToBeforeEndCount ); _movedToAfterPostCount += charsMovingCount; break; case IndicRepositioningClass.BeforeEnd: useOffsetFromEnd = true; offsetFromEnd = 0; _movedToBeforeEndCount += charsMovingCount; break; default: throw new NotSupportedException(); } // default return if (useOffsetFromEnd) { toPosition = (ushort)( _clusterSize + _unmappedGlyphsCount - offsetFromEnd ); } if (charsMovingCount > 0) { if (isReph) { // adjust all the cluster offsets for a reph being positioned before feature // application _halfOffset -= charsMovingCount; _mainOffset -= charsMovingCount; _lastMainOffset -= charsMovingCount; if (_postbaseRaOffset > 0 && _postbaseRaOffset <= toPosition) { if (_postbaseRaOffset == toPosition) { toPosition += charsMovingCount; // if positioning reph in front of prebase ra, move past it } else if ((ushort)(_postbaseRaOffset + 1) == toPosition) { toPosition += 1; // if positioning reph in front of prebase ra, move past it } _postbaseRaOffset -= charsMovingCount; } if (_subOffset > 0 && _subOffset < toPosition) _subOffset -= charsMovingCount; if (_postOffset > 0 && _postOffset < toPosition) _postOffset -= charsMovingCount; if (_preMainMatraOffset > 0 && _preMainMatraOffset < toPosition) _preMainMatraOffset -= charsMovingCount; if (_isPostMatraNuktaPresent && _lastNuktaOffset < toPosition) _lastNuktaOffset -= charsMovingCount; _rephOffset = (ushort)(toPosition - charsMovingCount); } else { // make adjustments for any matra, sign if (_subOffset > 0 && _subOffset >= toPosition) _subOffset += charsMovingCount; if (_postOffset > 0 && _postOffset >= toPosition) _postOffset = (ushort)((int)_postOffset + charsMovingCount); if (_postbaseRaOffset > 0 && _postbaseRaOffset >= toPosition) _postbaseRaOffset = (ushort)((int)_postbaseRaOffset + charsMovingCount); if (_rephOffset > 0 && _rephOffset >= toPosition) _rephOffset = (ushort)((int)_rephOffset + charsMovingCount); if (_isPostMatraNuktaPresent && _lastNuktaOffset >= toPosition) _rephOffset = (ushort)((int)_rephOffset + charsMovingCount); } } return toPosition; } ////// Critical - calls critical code /// [SecurityCritical] private void RepositionGlyphs( ref ShapingWorkspace currentRun, ushort fromPosition, ushort toPosition, ushort charsToMove, ushort glyphsToMove) { // do the glyph move GlyphInfoList glyphs = currentRun.GlyphInfoList; UshortList charMap = currentRun.CharMap; ushort nextGlyphIx, previousGlyphIx; ushort nextCharIx, previousCharIx; // set up the "glyphs to move" variables... fromPosition += _firstCharIx; toPosition += _firstCharIx; ushort firstGlyphToMove = currentRun.GetGlyphIx(fromPosition); if (toPosition > fromPosition) { // moving glyphs backwards (toward the end of the cluster) // This must be a before main matra that needs to be moved // back onto the last component in a non-conjunct forming // multi-consonant sequence. ushort charMoveDistance = (ushort)(toPosition - fromPosition); ushort glyphMoveToPosition = (ushort) (( toPosition == currentRun.CharsCount ? currentRun.GlyphsCount : currentRun.GetGlyphIx( toPosition ) ) - 1 ); ushort glyphMoveDistance = (ushort)(glyphMoveToPosition - firstGlyphToMove); if (glyphMoveDistance == 0) { // Debug.Assert (glyphMoveDistance > 0,"glyph move distance is zero!"); return; } // go through all the glyphs, removing one at a time from the front // of the block and reinserting it at the back of the block for (int i = 0; i < glyphsToMove; ++i) { ushort movingGlyph = glyphs.Glyphs[firstGlyphToMove]; ushort movingLigCount = glyphs.LigatureCounts[firstGlyphToMove]; ushort movingFlags = glyphs.GlyphFlags[firstGlyphToMove]; for ( nextGlyphIx = firstGlyphToMove; nextGlyphIx < glyphMoveToPosition; ++nextGlyphIx) { previousGlyphIx = (ushort)(nextGlyphIx + 1); glyphs.Glyphs[nextGlyphIx] = glyphs.Glyphs[previousGlyphIx]; glyphs.LigatureCounts[nextGlyphIx] = glyphs.LigatureCounts[previousGlyphIx]; glyphs.GlyphFlags[nextGlyphIx] = glyphs.GlyphFlags[previousGlyphIx]; } glyphs.Glyphs[glyphMoveToPosition] = movingGlyph; glyphs.LigatureCounts[glyphMoveToPosition] = movingLigCount; glyphs.GlyphFlags[glyphMoveToPosition] = movingFlags; } // go through the first chars now and fix 'em up. The intent is to // have the effect of removing "charsToMove" characters from the front // of the block of characters and add them to the back of the character // block. Of course, we aren't really moving the characters, but just // setting the glyphs' "first char" pointer and the charmap values as // if we had moved the characters. Similarly, we have removed "glyphsToMove" // glyphs from the front of the glyphs and added it to the back. // We didn't do this in the loop above because we might do the loop several // times (if glyphsToMove is greater than one). nextCharIx = (ushort)(toPosition - charsToMove); nextGlyphIx = firstGlyphToMove; for (int i = 0; i < glyphMoveDistance; ++i, ++nextGlyphIx) { previousCharIx = glyphs.FirstChars[ nextGlyphIx + glyphsToMove ]; if (previousCharIx >= fromPosition) { glyphs.FirstChars[ nextGlyphIx ] = (ushort)(previousCharIx - charsToMove); } else { glyphs.FirstChars[ nextGlyphIx ] = previousCharIx; } } // finish up the inserted (moved) glyphs. There's always one character // per glyph while (nextGlyphIx <= glyphMoveToPosition) { glyphs.FirstChars[nextGlyphIx++] = nextCharIx++; } // do the same thing for the charmap nextCharIx = fromPosition; nextGlyphIx = (ushort)(glyphMoveToPosition - glyphsToMove); for (int i = 0; i < charMoveDistance; ++i, ++nextCharIx) { previousGlyphIx = charMap[nextCharIx + charsToMove]; if (previousGlyphIx >= firstGlyphToMove) { charMap[nextCharIx] = (ushort)(previousGlyphIx - glyphsToMove); } else { charMap[nextCharIx] = previousGlyphIx; } } while (nextCharIx < toPosition) { charMap [ nextCharIx++ ] = ++nextGlyphIx; } } else if (toPosition < fromPosition) { // moving glyphs forward (toward the beginning of the cluster) // These are ra's and prebase ra's... ushort glyphMoveToPosition = currentRun.GetGlyphIx( toPosition ); ushort charMoveDistance = (ushort)(fromPosition - toPosition); ushort glyphMoveDistance = (ushort)(firstGlyphToMove - glyphMoveToPosition); if (glyphMoveDistance == 0) { // Debug.Assert (glyphMoveDistance > 0,"glyph move distance is zero!"); return; } for (int i = 0; i < glyphsToMove; ++i) { nextGlyphIx = (ushort)(firstGlyphToMove + i); previousGlyphIx = (ushort)(nextGlyphIx - 1); ushort insertedGlyphPosition = (ushort)(glyphMoveToPosition + i); ushort movingGlyph = glyphs.Glyphs[nextGlyphIx]; ushort movingLigCount = glyphs.LigatureCounts[nextGlyphIx]; ushort movingFlags = glyphs.GlyphFlags[nextGlyphIx]; while (nextGlyphIx > insertedGlyphPosition) { glyphs.Glyphs[nextGlyphIx] = glyphs.Glyphs[previousGlyphIx]; glyphs.LigatureCounts[nextGlyphIx] = glyphs.LigatureCounts[previousGlyphIx]; glyphs.GlyphFlags[nextGlyphIx--] = glyphs.GlyphFlags[previousGlyphIx--]; } glyphs.Glyphs[nextGlyphIx] = movingGlyph; glyphs.LigatureCounts[nextGlyphIx] = movingLigCount; glyphs.GlyphFlags[nextGlyphIx] = movingFlags; } // go through the first chars now and fix 'em up. The intent is to // have the effect of adding "charsToMove" characters to the front // of the block of characters and removing them from the back of the character // block. Of course, we aren't really moving the characters, but just // setting the glyphs' "first char" pointer and the charmap values as // if we had moved the characters. Similarly, we have removed "glyphsToMove" // glyphs from the back of the glyphs and added it to the front. // We didn't do this in the loop above because we might do the loop several // times (if glyphsToMove is greater than one). nextGlyphIx = (ushort)(firstGlyphToMove + glyphsToMove - 1); for (int i = 0; i < glyphMoveDistance; ++i, --nextGlyphIx) { previousCharIx = glyphs.FirstChars[ nextGlyphIx - glyphsToMove ]; if (previousCharIx >= toPosition ) { glyphs.FirstChars[ nextGlyphIx ] = (ushort)(previousCharIx + charsToMove); } else { glyphs.FirstChars[ nextGlyphIx ] = previousCharIx; } } // finish up the inserted (moved) glyphs. We either have one glyph for // multiple characters (like for reph's) or one glyph per character (as // for normal matras), or zero characters for one glyph (for split matra // components) nextCharIx = (ushort)(toPosition + charsToMove - 1); while (nextGlyphIx > glyphMoveToPosition) { glyphs.FirstChars[nextGlyphIx--] = nextCharIx--; } glyphs.FirstChars[glyphMoveToPosition] = nextCharIx; // do the same thing for the charmap nextCharIx = (ushort)(fromPosition + charsToMove - 1); nextGlyphIx = (ushort)(glyphMoveToPosition + glyphsToMove - 1); for (int i = 0; i < charMoveDistance; ++i, --nextCharIx) { previousGlyphIx = charMap[nextCharIx - charsToMove]; if (previousGlyphIx >= glyphMoveToPosition) { charMap[nextCharIx] = (ushort)(previousGlyphIx + glyphsToMove); } else { charMap[nextCharIx] = previousGlyphIx; } } while (nextCharIx > toPosition) { charMap [ nextCharIx-- ] = nextGlyphIx; if (glyphsToMove > 1) --nextGlyphIx; } charMap [ toPosition ] = glyphMoveToPosition; } } ////// IndicClusterCop.RepositionCharacters - /// . /// /// shaping currentRun /// actual character ix (relative to start of cluster) to be moved /// "virtual" character ix for glyph to move to /// number of glyphs that are to be moved ///If there're no split matra components in the cluster then the /// "virtual" character ix corresponds exactly to the the /// real character ix in the charmap. If there are split matra /// components the "virtual" ix is the actual glyph ix (relative to /// first glyph in the cluster), but not correspond to the correct /// character ix. ////// Critical - calls critical code /// [SecurityCritical] private void RepositionCharacters (ref ShapingWorkspace currentRun, ushort fromPosition, ushort toPosition, ushort moveCount ) { if (fromPosition != toPosition) { ushort firstGlyphIx = currentRun.GetGlyphIx(_firstCharIx); ushort glyphsInCluster = (ushort)(_clusterSize + _unmappedGlyphsCount); fromPosition = (ushort)(firstGlyphIx + fromPosition); toPosition = (ushort)(firstGlyphIx + toPosition); if ( toPosition > fromPosition ) // if moving the reph (or halant for old style fonts) { --toPosition; // move to in front of to position Debug.Assert(toPosition < (ushort)(firstGlyphIx + _clusterSize + _unmappedGlyphsCount), "Invalid reph/halant reposition"); ushort moveDistance = (ushort)( toPosition - fromPosition ); if (moveDistance > 0) { if (_isZWJPresent) { ushort zwjGlyphIx = (ushort)(firstGlyphIx + _ZWJOffset); if(zwjGlyphIx > fromPosition && zwjGlyphIx <= toPosition) { currentRun.GlyphInfoList.GlyphFlags[zwjGlyphIx] = ShapingWorkspace.GlyphFlagsNone; if (_ZWJOffset >= moveCount) { currentRun.GlyphInfoList.GlyphFlags[zwjGlyphIx - moveCount] = ShapingWorkspace.GlyphFlagsZeroWidth; _ZWJOffset -= moveCount; } } } for (ushort i = 0; i < moveCount; ++i) { ushort movingGlyph = currentRun.GetGlyph(fromPosition); currentRun.MoveGlyphs( fromPosition, (ushort)(fromPosition + 1), moveDistance ); // now update the repositioned glyph currentRun.SetGlyph(toPosition, movingGlyph); } } } else if (fromPosition > toPosition) // else if moving matra/sign { Debug.Assert((ushort)(fromPosition + moveCount) <= currentRun.GlyphsCount && (ushort)(fromPosition + moveCount) <= (ushort)(firstGlyphIx + _clusterSize + _unmappedGlyphsCount + 1), "Invalid matra reposition"); ushort moveDistance = (ushort)( fromPosition - toPosition ); if (moveDistance > 0) { if (_isZWJPresent) { ushort zwjGlyphIx = (ushort)(firstGlyphIx + _ZWJOffset); if(zwjGlyphIx >= toPosition && zwjGlyphIx < fromPosition) { currentRun.GlyphInfoList.GlyphFlags[zwjGlyphIx] = ShapingWorkspace.GlyphFlagsNone; if (_ZWJOffset + moveCount < currentRun.GlyphsCount) { currentRun.GlyphInfoList.GlyphFlags[zwjGlyphIx + moveCount] = ShapingWorkspace.GlyphFlagsZeroWidth; _ZWJOffset += moveCount; } } } for (ushort i = 0; i < moveCount; ++i) { ushort matraGlyph = currentRun.GetGlyph(fromPosition); currentRun.MoveGlyphs( (ushort)(toPosition + 1), toPosition, moveDistance ); // now update the repositioned glyph currentRun.SetGlyph( toPosition, matraGlyph ); ++fromPosition; ++toPosition; } } } } } ////// Critical - calls critical code /// [SecurityCritical] private void RepositionSplitMatra(ref ShapingWorkspace currentRun, ushort matraOffset) { // this is a split matra. Get its components and move them // to the appropriate positions // decompose the matra and move its glyphs to the right spots char [] matraComponents = _charConverter.ToDecompositionList(currentRun.GetChar((ushort)(_firstCharIx + matraOffset))); if (matraComponents != null) { ushort componentIx = 0; // save the positioning info from this new char shape... IndicRepositioningClass nextComponentRepositioningClass = _charConverter.ToRepositioningClass(matraComponents[componentIx]); while (componentIx < matraComponents.Length) { // go through the rest of the components till we find the next one that // repositions to a different place... IndicRepositioningClass componentRepositioningClass = nextComponentRepositioningClass; ushort componentsToMove = 1; ushort toPosition = GetRepositionOffsetAndUpdateCluster(componentRepositioningClass, false, // not reph 0); while (++componentIx < matraComponents.Length) { nextComponentRepositioningClass = _charConverter.ToRepositioningClass(matraComponents[componentIx]); _unmappedGlyphsCount += 1; // keep track of number of added component glyphs if (nextComponentRepositioningClass == componentRepositioningClass) { ++componentsToMove; continue; } break; } RepositionCharacters(ref currentRun, matraOffset, toPosition,componentsToMove); // re-call with char count to update the cluster. GetRepositionOffsetAndUpdateCluster(componentRepositioningClass, false, // not reph componentsToMove); matraOffset += componentsToMove; // this will be our next starting "virtual" character offset } _lastMatraRepositioningClass = nextComponentRepositioningClass; _isSplitMatraPresent = true; } Debug.Assert(matraComponents != null, "ERROR - no split matra components array."); } ////// Critical - calls critical code /// [SecurityCritical] public bool AddCharToCluster (ref ShapingWorkspace currentRun) { CharShapeInfo charShapeFlags = currentRun.CurrentShape; bool isClusterOk = true; // before processing the current character, check if its time to process the // preceding cluster... if ((charShapeFlags & CharShapeInfo.IsStartOfCluster) != 0 && _clusterSize > 0) { ApplyClusterFeatures( ref currentRun ); ResetClusterInfo(currentRun.CurrentCharIx); } if ((charShapeFlags & CharShapeInfo.RequiresInsertedBase) != CharShapeInfo.RequiresInsertedBase) { ushort clusterCharOffset = _clusterSize; IndicCharClass charClass = (IndicCharClass)(charShapeFlags & IndicShape.IndicCharClassMask); switch (charClass) { case IndicCharClass.Vowel: if (_isVowelPresent) { // can't have two vowels per cluster isClusterOk = false; } else { // got a vowel this cluster _isVowelPresent = true; _vowelOffset = clusterCharOffset; isClusterOk = AppendConsonant(ref currentRun, clusterCharOffset ); }; break; case IndicCharClass.NuktaConsonant: isClusterOk = AppendConsonant(ref currentRun, clusterCharOffset ); if (isClusterOk) { _isNuktaPresent = true; _lastNuktaOffset = clusterCharOffset; } break; case IndicCharClass.Ra: case IndicCharClass.Consonant: isClusterOk = AppendConsonant(ref currentRun, clusterCharOffset ); break; case IndicCharClass.ZWJ: isClusterOk = AppendZWJ(ref currentRun, clusterCharOffset ); break; case IndicCharClass.ZWNJ: isClusterOk = AppendZWNJ(ref currentRun, clusterCharOffset ); break; case IndicCharClass.Halant: isClusterOk = AppendHalant( ); if (isClusterOk) { _lastHalantOffset = clusterCharOffset; } break; case IndicCharClass.Nukta: isClusterOk = AppendNukta( ref currentRun, clusterCharOffset ); if (isClusterOk) { _lastNuktaOffset = clusterCharOffset; // keep track of latest nukta } break; case IndicCharClass.Vedic: case IndicCharClass.VowelSign: isClusterOk = AppendVowelSign( ref currentRun, clusterCharOffset ); break; case IndicCharClass.Matra: isClusterOk = AppendMatra( ref currentRun, clusterCharOffset ); break; case IndicCharClass.NBSP: break; default: break; } if (!isClusterOk) { // process the cluster up till now Debug.Assert(_clusterSize > 0,"Zero length cluster is illegal"); ApplyClusterFeatures( ref currentRun ); ResetClusterInfo ( currentRun.CurrentCharIx ); } } ++_clusterSize; // this character is part of the cluster return isClusterOk; } ////// Critical - calls critical code /// [SecurityCritical] private bool AppendConsonant ( ref ShapingWorkspace currentRun, ushort consonantCharOffset ) { _isHalantActive = false; // if ( _consonantState == ConsonantState.EndOfClusterReached ) // { // return false; // } IndicFormFlags consonantFormFlags = _charConverter.ToFormInfo(currentRun.CurrentChar); // Check if we've already discovered a prebase ra (a rare bird!), and // if so... if (!_useV2FontRules) { if (_postbaseRaOffset > 0 && ((consonantFormFlags & IndicFormFlags.PostForm) == 0 || (consonantFormFlags & IndicFormFlags.PostbaseReph) != 0)) { // The old-style Kartika font expects that post-base reordering ra to // have no non-post-base forms after it. Also, a second post-base ra will // supercede the first (which means the first is not a post-base form). // If either of these are true, then set our state to consAfterMain and // clear the prebase ra offset. // <-- _lastMainOffset = _lastConsonantOffset; _consonantState = ConsonantState.AfterMain; _postbaseRaOffset = 0; } } switch (_consonantState) { case ConsonantState.AfterReph: _isRephReorderPending = true; goto FirstMainConsonant; case ConsonantState.AfterZWJ: goto FirstMainConsonant; case ConsonantState.Start: if ((consonantFormFlags & IndicFormFlags.RephForm) != 0) { _consonantState = ConsonantState.AfterReph; return true; } FirstMainConsonant: // normal handling of the first main consonant (ie, not // a reph). Also come through here for ZWJ (which is // why we check whether _halfOffset has already been // set) if ((consonantFormFlags & IndicFormFlags.HalfForm) != 0 && _halfOffset > consonantCharOffset) { _halfOffset = consonantCharOffset; } _consonantState = (consonantFormFlags & IndicFormFlags.DravidianBase) != 0 ? ConsonantState.AfterDravidianBase : ConsonantState.AfterMain; _mainOffset = consonantCharOffset; break; case ConsonantState.AfterDravidianBase: // for dravidian base chars (malayalam script only) on old behavior // fonts, don't shape second consonant consonantFormFlags = 0; // consonant may not be sub/post after dravidian base goto AfterMain; case ConsonantState.AfterMain: AfterMain: if ((consonantFormFlags & (IndicFormFlags.PostForm | IndicFormFlags.SubForm | IndicFormFlags.VattuForm)) != 0) { // if this is Kannada text and there's a zero width joiner since the // halant, swap the two... if ( _isZWJPresent && _scriptIx == IndicScriptIndices.Kannada ) { if ( _ZWJOffset == (ushort)(_lastHalantOffset + 1) ) { // For compatibility with legacy useage in Kannada, // Ra+h+ZWJ must behave like Ra+ZWJ+h. So always // order these as Ra+ZWJ+h (and, of course, the ZWJ+h // belong with the following sub.. ushort firstGlyphIx = currentRun.GetGlyphIx(_firstCharIx); ushort halantGlyphIx = (ushort)(firstGlyphIx + _lastHalantOffset); ushort zwGlyphIx = (ushort)(firstGlyphIx + _ZWJOffset); ushort halantFlags = currentRun.GlyphInfoList.GlyphFlags[halantGlyphIx]; currentRun.GlyphInfoList.Glyphs[halantGlyphIx] = currentRun.GlyphInfoList.Glyphs[zwGlyphIx]; currentRun.GlyphInfoList.GlyphFlags[halantGlyphIx] = currentRun.GlyphInfoList.GlyphFlags[zwGlyphIx]; currentRun.GlyphInfoList.Glyphs[zwGlyphIx] = _fontClient.HalantGlyph; currentRun.GlyphInfoList.GlyphFlags[zwGlyphIx] = halantFlags; _ZWJOffset = _lastHalantOffset; } else if ( (ushort)(_ZWJOffset + 1) == _lastHalantOffset) { _lastHalantOffset = _ZWJOffset; } } if ((consonantFormFlags & IndicFormFlags.PostForm) != 0) { _postOffset = _lastHalantOffset; _consonantState = ConsonantState.AfterPost; } else { _subOffset = _lastHalantOffset; _consonantState = ConsonantState.AfterSub; } } else { if (_halfOffset == _mainOffset && _mainOffset >= _lastMainOffset) { // this is the second consonant (or maybe third, if // there's a reph) and the first was a half form // so set this one as our main. _mainOffset = consonantCharOffset; } // main is now HERE //* check for NoConjuncts? _lastMainOffset = consonantCharOffset; } break; case ConsonantState.AfterSub: if ((consonantFormFlags & IndicFormFlags.PostForm) != 0) { _postOffset = _lastHalantOffset; _consonantState = ConsonantState.AfterPost; } else if ((consonantFormFlags & (IndicFormFlags.SubForm | IndicFormFlags.VattuForm)) == 0) { // a consonant with no shape class following a sub-base consonant. // Hmmm! Check a little further... // The rules are that a prebase ra can come after a sub-form. // So if this is a prebase ra AND we haven't already marked an // earlier ra as a prebase form, then we'll let this one past. // However, in all other cases, roll back our state... if ((consonantFormFlags & IndicFormFlags.PostbaseReph) == 0 || _postbaseRaOffset > 0) { // roll everything back to main and set this as the last main, // and hope font forms a conjunct. _lastMainOffset = consonantCharOffset; _subOffset = _postbaseRaOffset = 0; _consonantState = ConsonantState.AfterMain; } } break; case ConsonantState.AfterPost: if ((consonantFormFlags & IndicFormFlags.PostForm) == 0) { // hmmm! This isn't a post-form consonant. Better check further // (have to do something about this!) // The rules are that a prebase ra can come after a post-form. // So if this is a prebase ra AND we haven't already marked an // earlier ra as a prebase form, then we'll let this one past. // However, in all other cases, roll back our state... if ((consonantFormFlags & IndicFormFlags.PostbaseReph) == 0 || _postbaseRaOffset > 0) { if ((consonantFormFlags & (IndicFormFlags.SubForm | IndicFormFlags.VattuForm)) != 0) { // roll everything back to subs // and hope font forms a conjunct. _lastMainOffset = _lastConsonantOffset; _subOffset = _lastHalantOffset; _consonantState = ConsonantState.AfterSub; } else { // a consonant with no shape class (maybe font'll do something // with it?); so roll everything back to main and set this // as the last main, and hope for conjunct formations _lastMainOffset = consonantCharOffset; _consonantState = ConsonantState.AfterMain; } _postOffset = _postbaseRaOffset = 0; // clear. If appropriate, raoffset'll be reset below } } break; } if (_mainOffset != consonantCharOffset) { // take care of vattus. If this consonant is not the reph or 1st // main consonant, check for vattu form if ((consonantFormFlags & IndicFormFlags.VattuForm) != 0) { _isVattuPresent = true; } // --> // Special reordering ra handling... if ((consonantFormFlags & IndicFormFlags.PostbaseReph) != 0 && _postbaseRaOffset == 0) { // post-base RA glyph will get reordered to before the // last main consonant. So set the offset if this is a potential post main RA _isReorderingNecessary = true; _postbaseRaOffset = _lastHalantOffset; } // <-- // End special reordering ra handling... } _lastConsonantOffset = consonantCharOffset; return true; } private bool AppendHalant () { bool succeeded = false; if ( _isMatraPresent ) { // This is a post-matra halant. Check that there's not // already one. if (!_isPostMatraHalantPresent) { succeeded = _isPostMatraHalantPresent = true; } } else if (!_isHalantActive) { // this is the first (only) halant since the last consonant (good!) succeeded = _isHalantActive = true; } return succeeded; } ////// Critical - calls critical code /// [SecurityCritical] private bool AppendNukta ( ref ShapingWorkspace currentRun, ushort nuktaCharOffset ) { bool succeeded = false; if ( _isMatraPresent ) { // this nukta follows a matra, so add the matra's // positioning info to our shape info if (!_isPostMatraNuktaPresent) { // move this nukta to after the preceding (reordered) matra // reposition this matra IndicRepositioningClass nuktaReposClass = _lastMatraRepositioningClass; ushort toPosition = GetRepositionOffsetAndUpdateCluster(nuktaReposClass, false, // not reph 1); RepositionCharacters(ref currentRun, (ushort)(nuktaCharOffset + _unmappedGlyphsCount), toPosition, 1); succeeded = _isPostMatraNuktaPresent = true; _lastNuktaOffset = toPosition; } } // won't allow multiple nuktas for one consonant (ok, no more than two) else if (_lastNuktaOffset <= _lastConsonantOffset + 1) { // we may have to adjust the consonant state.. if (_consonantState == ConsonantState.AfterReph) { // if this nukta is modifying what we thought was our // reph, better change that _isRephReorderPending = false; _mainOffset = _lastConsonantOffset; _consonantState = ConsonantState.AfterMain; } else if (_consonantState == ConsonantState.AfterSub || _consonantState == ConsonantState.AfterPost) { // nukta not allowed on subs/posts // roll back to main _lastMainOffset = _lastConsonantOffset; _consonantState = ConsonantState.AfterMain; } succeeded = _isNuktaPresent = true; } return succeeded; } private bool AppendZWJ ( ref ShapingWorkspace currentRun, ushort zwjCharOffset ) { if (_isHalantActive) { if (_consonantState == ConsonantState.AfterReph && _scriptIx == IndicScriptIndices.Kannada) { // For compatibility with legacy useage in Kannada, // Ra+h+ZWJ must behave like Ra+ZWJ+h... _mainOffset = _lastConsonantOffset; _consonantState = ConsonantState.AfterMain; } else { // this ZWJ follows a halant. We expect that the following // consonant will be the main _isHalantActive = false; _isVattuPresent = false; // treat this "C + H + ZWJ" as a half-form. // (Malayalam has no half forms, but it does use this sequence // to specify the chillaksaram forms and we may want any pre-main // matras to go behind the chillaksaram (in ReorderGlyphs()) if (_halfOffset > _mainOffset) { _halfOffset = _mainOffset; } _mainOffset = (ushort)(zwjCharOffset + 1); _consonantState = ConsonantState.AfterZWJ; } // if this ZWJ is followin a RA+h that we thought might be // a prebase form, now we know it isn't! if (_postbaseRaOffset == _lastConsonantOffset) { _postbaseRaOffset = 0; } } else if ( _consonantState == ConsonantState.AfterReph) { _mainOffset = _lastConsonantOffset; _consonantState = ConsonantState.AfterMain; } if (!_isZWJPresent) { _isZWJPresent = true; _ZWJOffset = zwjCharOffset; } return true; } private bool AppendZWNJ ( ref ShapingWorkspace currentRun, ushort zwnjCharOffset ) { if (_isHalantActive) { // ZWNJ following a C+H. _isHalantActive = false; if (_postbaseRaOffset == 0 || _postbaseRaOffset < _lastConsonantOffset) { _mainOffset = _lastConsonantOffset; _postbaseRaOffset = 0; } else { // last consonant is our pre-base ra. Mark it as "last main", too. _lastMainOffset = _lastConsonantOffset; } } else if (_scriptIx == IndicScriptIndices.Bengali) { // ZWNJ following a ra (only for Bengali) _mainOffset = _lastConsonantOffset; _consonantState = ConsonantState.AfterMain; } _isZWJPresent = true; _ZWJOffset = zwnjCharOffset; return true; } ////// Critical - calls critical code /// [SecurityCritical] private bool AppendMatra ( ref ShapingWorkspace currentRun, ushort matraCharOffset ) { bool succeeded = false; // assume the worst! char matraChar = currentRun.CurrentChar; IndicFormFlags matraPositionFlags = _charConverter.ToFormInfo(matraChar); // check that this matra has no positions already claimed by preceding matras if (matraPositionFlags > _matraPositionFlags) { // keep track of first matra if (_matraPositionFlags == 0) { FinalizeOffsets(ref currentRun, matraCharOffset, false); } // if this is a post-vowel matra, validate the matra if (_isVowelPresent && _vowelOffset == _lastConsonantOffset) { char[] invalidVowelList = _charConverter.GetMatraInvalidVowelList(matraChar); if (invalidVowelList != null) { char vowelChar = currentRun.GetChar((ushort)(_firstCharIx + _vowelOffset)); for (int i = 0; i < invalidVowelList.Length; ++i) { if (invalidVowelList[i] == vowelChar) { return false; // this matra is not allowed with this vowel base } } } } _matraPositionFlags |= matraPositionFlags; succeeded = true; } else { // Seems this matra wants to position in the same place as a previous matra. // This is not allowed, except for a couple of Kannada exceptions // (0xCCA+0xCD5 or 0xCC6+0xCC2+0xCD5) if (matraChar == '\u0CD5') { // for Kannada script, there are three ways to type the oo vowel sign // if this is one of those, we're happy (this is the only acceptable // case of two matras with the same reordering position) char prevChar = currentRun.PreviousChar; if (prevChar == '\u0CCA') { succeeded = true; } else if (prevChar == '\u0CC2') { ushort matraCharIx = currentRun.CurrentCharIx; if (matraCharIx >= _firstCharIx + 3 && currentRun.GetChar((ushort)(matraCharIx - 2)) == '\u0CC6') { succeeded = true; } } } } if (succeeded) { CharShapeInfo matraShapeInfo = _charConverter.ToShapeInfo(matraChar); bool isSplitMatra = ((matraShapeInfo & IndicShape.ShapeIncludesPositioningInfoFlag) == 0); if (isSplitMatra) { // This is a split matra...so move the glyphs where they // belong RepositionSplitMatra(ref currentRun, matraCharOffset ); } else { // reposition this matra _lastMatraRepositioningClass = GetRepositioningClass(matraShapeInfo); ushort toPosition = GetRepositionOffsetAndUpdateCluster(_lastMatraRepositioningClass, false, // not reph 1); RepositionCharacters(ref currentRun, (ushort)(matraCharOffset + _unmappedGlyphsCount), toPosition, 1); // special case for Gurmukhi - when there's no post consonant but // there is an after-post matra, then move the reph after the // matra (unless its one of the two matras, 0xa3e or 0xa40) if (_isRephReorderPending && _scriptIx == IndicScriptIndices.Gurmukhi) { if (_lastMatraRepositioningClass == IndicRepositioningClass.AfterPost) { if (matraChar != '\u0a3e' && matraChar != '\u0a40') { _gurmukhiRephGoesAfterPostMatra = true; } } } } _isMatraPresent = true; } return succeeded; } ////// Critical - calls critical code /// [SecurityCritical] private bool AppendVowelSign ( ref ShapingWorkspace currentRun, ushort vowelSignCharOffset ) { bool succeeded = true; FinalizeOffsets( ref currentRun, vowelSignCharOffset, false ); // a vedic is a vowel sign and is the only way we'd see multiple // vowel signs. We can only have one of each vedic and only // two vedics, so check if preceding character is same as this one // (that would be bad) char vowelSignChar = currentRun.CurrentChar; succeeded = (currentRun.PreviousChar != vowelSignChar); if (succeeded) { // for matras and vowel signs, reposition them now IndicRepositioningClass signReposClass = _charConverter.ToRepositioningClass(vowelSignChar); ushort toPosition = GetRepositionOffsetAndUpdateCluster(signReposClass, false, // not reph 1); RepositionCharacters(ref currentRun, (ushort)(vowelSignCharOffset + _unmappedGlyphsCount), toPosition, 1); } return succeeded || vowelSignCharOffset == 0; } ////// Critical - calls critical code /// [SecurityCritical] public void FinalizeOffsets ( ref ShapingWorkspace currentRun, ushort currentCharOffset, bool isFinalCall ) { if (_lastMainOffset < _mainOffset) { _lastMainOffset = _mainOffset; } if (currentCharOffset > 0) { if (_consonantState != ConsonantState.EndOfClusterReached && _consonantState != ConsonantState.Start) { // if there's no half forms, set the half offset now to the first // consonant (in case any matra wants to reposition to After Start) if (_halfOffset > _mainOffset) { _halfOffset = _mainOffset; } if ( !_useV2FontRules ) { if ( _lastMainOffset + 2 < currentCharOffset) { // for non-V2 script fonts (ie, pre-Vista Indic fonts) we // need to effectively swap the post-main consonants and their // halants before applying the font features because these // fonts use C+h lookups rather than h+C lookups. // find first halant ushort halantOffset = (ushort)(_lastMainOffset + 1); // start here ushort halantGlyphIx = (ushort)(currentRun.GetGlyphIx(_firstCharIx) + halantOffset); bool foundHalant = (_fontClient.HalantGlyph == currentRun.GetGlyph(halantGlyphIx)); if (!foundHalant && (_lastMainOffset + 3 < _clusterSize)) { // didn't find a halant. Maybe this is a ZWJ? Try the next glyph foundHalant = (_fontClient.HalantGlyph == currentRun.GetGlyph(++halantGlyphIx)); ++halantOffset; } if (foundHalant) { // so, we have our halant. If there's a trailing halant, // move the first halant based on its repositioning class, // otherwise if there's a sub- or post- consonant, move // past it ushort toPosition = halantOffset; if (_isHalantActive) { toPosition = GetRepositionOffsetAndUpdateCluster(_charConverter.HalantRepositioningClass, false, 0); } else if (_postOffset > 0 || _subOffset > 0) { toPosition = currentCharOffset; } RepositionCharacters(ref currentRun, halantOffset, toPosition, 1 ); } } } _consonantState = ConsonantState.EndOfClusterReached; } if (isFinalCall) { if (_isRephReorderPending) { // move the reph to where it belong... IndicRepositioningClass rephPosition = _charConverter.ToRepositioningClass( currentRun.GetChar(_firstCharIx) ); ushort toPosition = GetRepositionOffsetAndUpdateCluster(rephPosition, true, // is reph 2); RepositionCharacters(ref currentRun,0,toPosition,2); if (_lastMainOffset == _mainOffset || (_postOffset > 0 && toPosition > _postOffset)) { _isRephReorderPending = false; } else { _isReorderingNecessary = true; } } if (_unmappedGlyphsCount > 0) { UpdateSplitMatraClusterOffsets(ref currentRun); } } } } ////// Critical - calls critical code /// [SecurityCritical] private void UpdateSplitMatraClusterOffsets(ref ShapingWorkspace currentRun) { // Split matra components mean that the char:glyph offset relationship // is not 1:1 for this cluster. We have already moved all the matras // to their places and need to make some adjustments before we try // to have OTLS apply the font features. Our strategy will be to // try to map 2 glyphs to 1 character till we've compensated for // all the matra component glyphs added by... // first, if there's a reph map it to one character // second, if there's a prebase ra, map it to one character // third, if there's a sub form consonant, map the halant-consonant // pair to one glyph // fourth, do the same if there's a post form consonant // fifth, if more than one matra are adjacent to each other, map // them to one glyph // sixth, if we've still not brought our "virtual" character // count back to par and we have a prebase matra, map // the following glyph to the same character as the // matra // finally, if all else fails map the remaining "extra" glyphs // to the same character as the last glyph with a valid // FirstChars value. GlyphInfoList glyphs = currentRun.GlyphInfoList; UshortList charMap = currentRun.CharMap; ushort firstGlyphInCluster = currentRun.GetGlyphIx(_firstCharIx); ushort lastGlyphInCluster = (ushort)(firstGlyphInCluster + _clusterSize + _unmappedGlyphsCount - 1); ushort lastCharInCluster = (ushort)(_firstCharIx + _clusterSize - 1); // first reset the first chars and charmap entries to 1:1 glyphIx to charIx // when this is done, the extra glyphs will all refer to character indices that // are invalid (beyond the actual end of the character range). Not to worry! // We're gonna fix it up... ushort nextGlyphIx = firstGlyphInCluster; ushort nextCharIx = _firstCharIx; while (nextCharIx <= lastCharInCluster) { glyphs.FirstChars[nextGlyphIx] = nextCharIx; charMap[nextCharIx++] = nextGlyphIx++; } while (nextGlyphIx <= lastGlyphInCluster) { glyphs.FirstChars[nextGlyphIx++] = nextCharIx++; } // now look for possible chars to "squeeze out" so that our glyph to character // mapping gets in sync ushort passId = 0; while (_unmappedGlyphsCount > 0) { bool isSqueezeTarget = false; ushort clusterCharOffset = 0; switch(passId++) { case 0: if (_isPostMatraNuktaPresent && _lastNuktaOffset > 0 && _lastNuktaOffset < _mainOffset) { // compress the matra+nukta to one character clusterCharOffset = _preMainMatraOffset; _lastNuktaOffset = _preMainMatraOffset; isSqueezeTarget = true; } break; case 1: if (_postbaseRaOffset > 0) { // compress the prebase ra to one character clusterCharOffset = _postbaseRaOffset; isSqueezeTarget = true; if (_lastMainOffset == (ushort)(_postbaseRaOffset + 1)) { _lastMainOffset = _postbaseRaOffset; } } break; case 2: if (_rephOffset > 0) { // compress the reph to one character clusterCharOffset = _rephOffset; isSqueezeTarget = true; } break; case 3: if (_subOffset > 0 && _postbaseRaOffset != _subOffset) { // compress the (first) sub-base to one character clusterCharOffset = _subOffset; isSqueezeTarget = true; } break; case 4: if (_postOffset > 0) { // compress the (first) post-base to one character clusterCharOffset = _postOffset; isSqueezeTarget = true; } break; case 5: if (_isPostMatraNuktaPresent && _lastNuktaOffset > _mainOffset) { // compress the matra+nukta to one character clusterCharOffset = (ushort)(_lastNuktaOffset - 1); isSqueezeTarget = true; } break; case 6: if ( _lastMainOffset > _mainOffset ) { // compress the first base with the first character that follows it // (could be nukta, halant, ZWJ) to one character clusterCharOffset = _mainOffset; isSqueezeTarget = true; } break; default: // If we're here none (or not enough) of the possible 2 glyphs to 1 character // mapping assignments has been enough. So just map all of the extra glyphs // to the end of the cluster. Be careful to check if the reph is at the end; // if so, adjust the reph to be the last char bool isRephAtEnd = _rephOffset >= lastCharInCluster; nextGlyphIx = lastGlyphInCluster; while (nextGlyphIx > firstGlyphInCluster && glyphs.FirstChars[nextGlyphIx] >= lastCharInCluster) { glyphs.FirstChars[nextGlyphIx--] = lastCharInCluster; if (isRephAtEnd) { _rephOffset = lastCharInCluster--; isRephAtEnd = false; } } _unmappedGlyphsCount = 0; break; } if (isSqueezeTarget) { if (_lastMainOffset > 0 && _lastMainOffset > clusterCharOffset) --_lastMainOffset; if (_subOffset > 0 && _subOffset > clusterCharOffset) --_subOffset; if (_postOffset > 0 && _postOffset > clusterCharOffset) --_postOffset; if (_rephOffset > 0 && _rephOffset > clusterCharOffset) --_rephOffset; if (_postbaseRaOffset > 0 && _postbaseRaOffset > clusterCharOffset) --_postbaseRaOffset; // now update the glyph firstchars. The second glyph must now point to the // same character as the first glyph, so update all the remaining entries // (subtract one from the enntry) nextGlyphIx = (ushort)(firstGlyphInCluster + clusterCharOffset); while (++nextGlyphIx <= lastGlyphInCluster) { glyphs.FirstChars[nextGlyphIx] -= 1; } _unmappedGlyphsCount -= 1; } } // Now we need to fix up the charmap. Some of the characters may map to two // glyphs. Find them, starting from the beginning of the cluster. For each // pair of glyphs mapped to one character, adjust all the following charmap // entries (add one to charmap entry) nextCharIx = _firstCharIx; nextGlyphIx = firstGlyphInCluster; ushort charsInRun = currentRun.CharsCount; while (nextCharIx <= lastCharInCluster) { if (nextGlyphIx < lastGlyphInCluster && glyphs.FirstChars[nextGlyphIx + 1] == glyphs.FirstChars[nextGlyphIx]) { ushort charMapIx = nextCharIx; while (++charMapIx <= lastCharInCluster) { charMap[charMapIx] += 1; } ++nextGlyphIx; } ++nextCharIx; ++nextGlyphIx; } } ////// Critical - calls critical code /// [SecurityCritical] private bool FindLateRepositionAnchor(ref ShapingWorkspace currentRun) { bool foundAnchor = false; ushort newLastMainOffset = _lastMainOffset; if (_mainOffset < _lastMainOffset) { // check if conjunct formed ushort mainGlyph = currentRun.GetGlyphIx((ushort)(_firstCharIx + _mainOffset)); ushort nextGlyphToTest = currentRun.GetGlyphIx((ushort)(_firstCharIx + _lastMainOffset)); if (mainGlyph < nextGlyphToTest) { // conjunct didn't form. If looking for matra anchor, break on first explicit // halant. Otherwise, keep looking for an earlier halant (main might be // C+h+C+h+C). ushort halantGlyphIx = nextGlyphToTest; ushort halantGlyph = _fontClient.HalantGlyph; while (--nextGlyphToTest > mainGlyph) { if (halantGlyph == currentRun.GetGlyph(nextGlyphToTest)) { // found it. Set main offset to point to the consonant just past here. ushort halantCharIx = currentRun.GlyphInfoList.FirstChars[nextGlyphToTest]; halantGlyphIx = nextGlyphToTest; newLastMainOffset = (ushort)(halantCharIx + 1 - _firstCharIx); if (_isSplitMatraPresent) { // it may be that we mapped the main+halant glyphs to one character // because of added split matra component glyphs. We need to check // and if this is the case we need to increment newLastMainOffset if (currentRun.GlyphInfoList.FirstChars[nextGlyphToTest - 1] == halantCharIx) { ++newLastMainOffset; } } foundAnchor = true; // if(_isMatraReorderPending || _postbaseRaOffset > 0) { break; } } } // not quite done yet. If there's a ZWJ/NJ right after (either of) the halant // we need to move past this if (foundAnchor) { if (_isZWJPresent) { if((currentRun.GlyphInfoList.GlyphFlags[halantGlyphIx] & (ushort)GlyphFlags.ZeroWidth) != 0) { ++newLastMainOffset; } } // Also check if font has set the proposed new anchor points as a mark // (some font mark sub-base consonants this way). If this is so, we // don't want to move anything here.... if ((currentRun.GlyphInfoList.GlyphFlags[currentRun.GetGlyphIx(newLastMainOffset)] & (ushort)GlyphFlags.GlyphTypeMask) == (ushort)GlyphFlags.Mark) { foundAnchor = false; // } } } } if (!foundAnchor && _scriptIx == IndicScriptIndices.Malayalam && _isMatraReorderPending && _isZWJPresent) { // special Mayalayam case - if we have a chillaksaram that has // not become part of a composite glyph, we want to set our // anchor to after the chillaksaram //ushort mainCharOffset = _postbaseRaOffset > 0 ? (ushort)(_postbaseRaOffset - 1) : _lastMainOffset; if (mainCharOffset < _clusterSize) { ushort halfFormGlyphIx = currentRun.GetGlyphIx((ushort)(_firstCharIx + _halfOffset)); ushort nextGlyphToTest = currentRun.GetGlyphIx((ushort)(_firstCharIx + _mainOffset)); ushort mainGlyph = currentRun.GetGlyphIx((ushort)(_firstCharIx + mainCharOffset)); while (nextGlyphToTest <= mainGlyph) { if (nextGlyphToTest != halfFormGlyphIx && (currentRun.GlyphInfoList.GlyphFlags[nextGlyphToTest] & (ushort)GlyphFlags.GlyphTypeMask) != (ushort)GlyphFlags.Mark) { foundAnchor = true; // newLastMainOffset = (ushort)(currentRun.GlyphInfoList.FirstChars[nextGlyphToTest] - _firstCharIx); break; } ++nextGlyphToTest; } } } _lastMainOffset = foundAnchor ? newLastMainOffset : _mainOffset; return foundAnchor; } private static IndicRepositioningClass GetRepositioningClass(CharShapeInfo charShape) { Debug.Assert((charShape & (CharShapeInfo) IndicCharClass.IncludesPositioningInfo) != 0,"\"Includes Positioning Info\" flag not set."); charShape = (CharShapeInfo)((ushort)charShape >> IndicShape.RepositioningInfoShift); return (IndicRepositioningClass)charShape; } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- Bidi.cs
- AsymmetricSignatureDeformatter.cs
- XmlCharType.cs
- RelationshipEntry.cs
- EncodingTable.cs
- SrgsElementFactoryCompiler.cs
- CodeExporter.cs
- SQLResource.cs
- ReturnType.cs
- FunctionImportElement.cs
- MappingException.cs
- RoutedPropertyChangedEventArgs.cs
- IndentedWriter.cs
- CodeThrowExceptionStatement.cs
- WpfWebRequestHelper.cs
- ResourceExpressionBuilder.cs
- MediaContext.cs
- SHA1Cng.cs
- LinearQuaternionKeyFrame.cs
- ThreadStartException.cs
- CultureSpecificStringDictionary.cs
- SmiEventSink_Default.cs
- CqlParserHelpers.cs
- InternalCache.cs
- PerformanceCounter.cs
- ProxyWebPartManager.cs
- NamespaceCollection.cs
- RtfToken.cs
- MetadataPropertyCollection.cs
- ListItemViewControl.cs
- ContentValidator.cs
- ColorContext.cs
- AssertUtility.cs
- FontSourceCollection.cs
- HwndSource.cs
- CodeChecksumPragma.cs
- StoreAnnotationsMap.cs
- MimeParameter.cs
- HtmlListAdapter.cs
- ToolStripRenderer.cs
- CookieProtection.cs
- TextBoxView.cs
- XPathBinder.cs
- IntPtr.cs
- LicenseProviderAttribute.cs
- UnaryNode.cs
- Quaternion.cs
- ClientRuntimeConfig.cs
- ExceptionRoutedEventArgs.cs
- UnsafeNativeMethods.cs
- FontClient.cs
- DataException.cs
- GenerateDerivedKeyRequest.cs
- InsufficientMemoryException.cs
- ErrorRuntimeConfig.cs
- UnknownWrapper.cs
- sitestring.cs
- GenericsInstances.cs
- TextLineResult.cs
- MaterializeFromAtom.cs
- _NestedMultipleAsyncResult.cs
- CallbackException.cs
- ToolStripSplitButton.cs
- DataShape.cs
- PropertyValueUIItem.cs
- ResourcePool.cs
- TaskHelper.cs
- ProxyHelper.cs
- DbConnectionPoolCounters.cs
- RankException.cs
- TypeDescriptorContext.cs
- SoapInteropTypes.cs
- EventLog.cs
- VersionPair.cs
- TimeSpanMinutesOrInfiniteConverter.cs
- BamlStream.cs
- SQLDateTimeStorage.cs
- GACIdentityPermission.cs
- NodeFunctions.cs
- RegexInterpreter.cs
- EncodingTable.cs
- WpfWebRequestHelper.cs
- ZipIOExtraField.cs
- ConfigurationValue.cs
- BamlLocalizabilityResolver.cs
- SessionEndedEventArgs.cs
- AutomationPeer.cs
- _BasicClient.cs
- StringArrayConverter.cs
- SQLBytes.cs
- ConfigurationErrorsException.cs
- DecoderFallbackWithFailureFlag.cs
- DbParameterHelper.cs
- MatrixValueSerializer.cs
- ReferenceConverter.cs
- TdsValueSetter.cs
- securitycriticaldataformultiplegetandset.cs
- FormsAuthenticationModule.cs
- SqlProfileProvider.cs
- RefType.cs