public void Clone_State_ReturnsClone() { var a = new SubstitutionAction { ReplacedGlyphCount = 2, SkippedGlyphCount = 2, ReplacementGlyphIds = new ushort[] { 1, 2, 3 } }; var b = a.Clone(); var comparer = new TransitionActionEqualityComparer(); Assert.IsTrue(comparer.Equals(a, b)); }
public void Clone_Action_ReturnsClone() { var a = new AnchorPointToAnchorPointAction { CurrentGlyphAnchorPoint = new AnchorPoint { X = 1, Y = 2 }, PreviousGlyphAnchorPoint = new AnchorPoint { X = 2, Y = 3 }, }; var b = a.Clone(); var comparer = new TransitionActionEqualityComparer(); Assert.IsTrue(comparer.Equals(a, b)); }
public void Clone_State_ReturnsClone() { var a = new PositioningAdjustmentAction { PositionChanges = new[] { new GlyphPositionChange { AdvanceX = 1, AdvanceY = 2, OffsetX = 3, OffsetY = 4 }, new GlyphPositionChange { AdvanceX = 2, AdvanceY = 3, OffsetX = 4, OffsetY = 5 } } }; var b = a.Clone(); var comparer = new TransitionActionEqualityComparer(); Assert.IsTrue(comparer.Equals(a, b)); }
/// <summary> /// Normalizes the specified input machine. /// </summary> /// <param name="inputMachine">The input machine. This state machine will not be changed (a copy of all involved objects is constructed).</param> /// <param name="allowedGlyphIds">The set of glyph IDs, which are allowed to have transitions in the machine. If null, all glyphs are allowed.</param> /// <returns> /// The normalized machine. /// </returns> public StateMachine Normalize(StateMachine inputMachine, IEnumerable <ushort> allowedGlyphIds) { var allowedGlyphIdsSet = allowedGlyphIds != null ? new HashSet <ushort>(allowedGlyphIds) : new HashSet <ushort>(Enumerable.Range(ushort.MinValue, ushort.MaxValue - ushort.MinValue + 1).Select(p => (ushort)p)); var queue = new Queue <State>(); queue.Enqueue(inputMachine.EntryState); var visitedOrQueuedStates = new HashSet <State> { inputMachine.EntryState }; var oldToNewMap = new Dictionary <State, State>(); var transitionsByTargetState = new Dictionary <State, HashSet <ITransition> >(); var transitionComparer = new TransitionNonrecursiveEqualityComparer(); var actionComparer = new TransitionActionEqualityComparer(); while (queue.Any()) { var currentState = queue.Dequeue(); State newState; if (oldToNewMap.ContainsKey(currentState)) { newState = oldToNewMap[currentState]; } else { newState = new State(); oldToNewMap.Add(currentState, newState); } var groupedTransitions = currentState.Transitions.GroupBy(p => p.TargetState); foreach (var transitionGroup in groupedTransitions) { var glyphIdsToMerge = new List <ushort>(); // Transitions that can't be merged (fallback and always transitions, or transitions that don't match others) // have to be cloned as they are. var transitionsToClone = new List <ITransition>(); var oldTargetState = transitionGroup.First().TargetState; State newTargetState; if (oldToNewMap.ContainsKey(oldTargetState)) { newTargetState = oldToNewMap[oldTargetState]; } else { newTargetState = new State(); oldToNewMap[oldTargetState] = newTargetState; } ITransition lastMergedTransition = null; foreach (var transition in transitionGroup) { if (transition is SimpleTransition || transition is SetTransition) { // Only transitions which share all attributes but target and transition number can be merged. if ( lastMergedTransition == null || ( transition.IsFallback == lastMergedTransition.IsFallback && transition.HeadShift == lastMergedTransition.HeadShift && transition.LookupFlags == lastMergedTransition.LookupFlags && actionComparer.Equals(transition.Action, lastMergedTransition.Action) ) ) { if (transition is SimpleTransition) { glyphIdsToMerge.Add((transition as SimpleTransition).GlyphId); } else { glyphIdsToMerge.AddRange((transition as SetTransition).GlyphIdSet); } lastMergedTransition = transition; } else { transitionsToClone.Add(transition); } } else { transitionsToClone.Add(transition); } } var filteredGlyphIdsToMerge = new HashSet <ushort>(glyphIdsToMerge); filteredGlyphIdsToMerge.IntersectWith(allowedGlyphIdsSet); var newTransitions = new List <ITransition>(); foreach (var transition in transitionsToClone) { var newTransition = transition.Clone(); newTransition.TargetState = newTargetState; newTransition = this.AddTransitionOrGetExisting(transitionsByTargetState, newTargetState, newTransition, transitionComparer); if (transition is SimpleTransition) { if (allowedGlyphIdsSet.Contains(((SimpleTransition)transition).GlyphId)) { newTransitions.Add(newTransition); } } else if (transition is SetTransition) { ((SetTransition)transition).GlyphIdSet.IntersectWith(allowedGlyphIdsSet); if (((SetTransition)transition).GlyphIdSet.Count > 0) { newTransitions.Add(newTransition); } } else { newTransitions.Add(newTransition); } } // If there are no glyphs remaining in the set, no transition will be added. if (filteredGlyphIdsToMerge.Count == 1) { var newTransition = (ITransition) new SimpleTransition { GlyphId = filteredGlyphIdsToMerge.Single(), HeadShift = lastMergedTransition.HeadShift, LookupFlags = lastMergedTransition.LookupFlags, Action = lastMergedTransition.Action == null ? null : lastMergedTransition.Action.Clone(), TargetState = newTargetState }; newTransition = this.AddTransitionOrGetExisting(transitionsByTargetState, newTargetState, newTransition, transitionComparer); newTransitions.Add(newTransition); } else if (filteredGlyphIdsToMerge.Count > 1) { var newTransition = (ITransition) new SetTransition { GlyphIdSet = filteredGlyphIdsToMerge, HeadShift = lastMergedTransition.HeadShift, LookupFlags = lastMergedTransition.LookupFlags, Action = lastMergedTransition.Action == null ? null : lastMergedTransition.Action.Clone(), TargetState = newTargetState }; newTransition = this.AddTransitionOrGetExisting(transitionsByTargetState, newTargetState, newTransition, transitionComparer); newTransitions.Add(newTransition); } foreach (var newTransition in newTransitions) { newState.Transitions.Add(newTransition); } if (!visitedOrQueuedStates.Contains(oldTargetState)) { queue.Enqueue(oldTargetState); visitedOrQueuedStates.Add(oldTargetState); } } } var unsortedMachine = new StateMachine(oldToNewMap[inputMachine.EntryState]); var sorter = new StateMachineTransitionSorter(); return(sorter.SortTransitions(unsortedMachine)); }