/// <summary> /// Compiles a rule context transformation. /// </summary> /// <param name="transformation">The transformation.</param> /// <param name="builder">The builder.</param> /// <param name="glyphIdToRuleIndexCallback">The callback used to resolve rule index from glyph IDs.</param> /// <param name="createTransitionCallback">The callback used to create transitions.</param> /// <exception cref="System.InvalidOperationException">Transformations in transformation set don't share LookupFlags.</exception> private void CompileChainingRuleContextTransformation( IGlyphTransformationTable transformation, IStateMachineBuilder builder, Func <ushort, ushort> glyphIdToRuleIndexCallback, CreateContextTransformationTransitionDelegate <ushort> createTransitionCallback) { var table = (ChainingRuleContextTransformationTableBase)transformation; // Flatten the collection of rules and associate each rule with glyph received from the coverage table var rulesWithFirstGlyph = table .TransformationRules .Zip(table.Coverage.CoveredGlyphIds.Values) .SelectMany(ruleSetWithFirstGlyph => ruleSetWithFirstGlyph.Item1.Select(rule => Tuple.Create(rule, glyphIdToRuleIndexCallback(ruleSetWithFirstGlyph.Item2)))); foreach (var ruleTuple in rulesWithFirstGlyph) { foreach (var transformationSet in ruleTuple.Item1.TransformationSets) { var completeInputContext = ruleTuple.Item1.Context .Prepend(ruleTuple.Item2) .ToList(); this.CompileSingleContextTransformation( table, builder, transformationSet, createTransitionCallback, completeInputContext, ruleTuple.Item1.Lookback, ruleTuple.Item1.Lookahead); } } }
/// <summary> /// Compiles a context transformation for a single context. /// </summary> /// <typeparam name="TContextItem">The type of items in the context. This is arbitrary - it is only used to construct individual transitions in the context recognition section of the machine.</typeparam> /// <param name="transformation">The transformation.</param> /// <param name="builder">The builder.</param> /// <param name="subTransformationSet">The set of tranformation to execute on the matched context.</param> /// <param name="createTransitionCallback">The callback used to create transitions from individual items of the context. </param> /// <param name="context">The context.</param> /// <param name="lookbackContext">The lookback context (NULL if the transition is not chaining).</param> /// <param name="lookaheadContext">The lookahead context (NULL if the transition is not chaining). If lookaheadContext is defined, lookbackContext must be defined as well.</param> /// <exception cref="System.InvalidOperationException">Transformations in transformation set don't share LookupFlags.</exception> private void CompileSingleContextTransformation <TContextItem>( IGlyphTransformationTable transformation, IStateMachineBuilder builder, ContextTransformationSet subTransformationSet, CreateContextTransformationTransitionDelegate <TContextItem> createTransitionCallback, IEnumerable <TContextItem> context, IEnumerable <TContextItem> lookbackContext = null, IEnumerable <TContextItem> lookaheadContext = null) { if (!subTransformationSet.Transformations.Any()) { return; } var contextList = context.ToList(); var lookbackContextList = lookbackContext == null ? new List <TContextItem>() : lookbackContext.Reverse().ToList(); var lookaheadContextList = lookaheadContext == null ? new List <TContextItem>() : lookaheadContext.ToList(); var completeContext = lookbackContextList.Append(contextList).Append(lookaheadContextList).ToList(); var subMachineLookupFlags = subTransformationSet.Transformations.First().LookupFlags; // All the transformations in one transformation set must have the same lookup flags (they are supposed to come from // a single lookup). if (subTransformationSet.Transformations.Any(p => p.LookupFlags != subMachineLookupFlags)) { throw new InvalidOperationException("Transformations in transformation set don't share LookupFlags."); } // The head must end pointing to the first glyph after end of the context var subBuilder = new SubMachineBuilder(subMachineLookupFlags); foreach (var subTransformation in subTransformationSet.Transformations) { this.CompileTransformation(subTransformation, subBuilder); } /* The path for each rule is constructed as follows: * 1) A chain of states which match the context (including lookback and lookahead context). * 2) State which positions the head to adhere to first glyph index of the transformation set * and creates the context terminator glyph. The transition checking the last glyph of the context * leads to this state. Additonal state is inserted before this one in case the table has defined a * lookahead * 3) The sub-machine to be applied on the context. This state machine is modified to jump to 4 whenever * it encounters context terminator. * 4) State which positions the head to end of the context and removes the context terminator. */ // Part 1. Doesn't include the transition and state checking the last glyph (see part 2). var contextMatchPathSegment = completeContext .Take(completeContext.Count - 1) .Select( (contextMember, index) => createTransitionCallback( contextMember, 1, transformation.LookupFlags, null, this.GetContextZoneForGlyphIndex(index, contextList, lookbackContextList, lookaheadContextList))).ToList(); List <ITransition> subMachineInitializationPathSegment; if (lookaheadContextList.Count > 0) { // Part 2. Sub-machine initialization state. subMachineInitializationPathSegment = new List <ITransition> { createTransitionCallback( completeContext.Last(), -lookaheadContextList.Count, transformation.LookupFlags, null, this.GetContextZoneForGlyphIndex(completeContext.Count - 1, contextList, lookbackContextList, lookaheadContextList)), new AlwaysTransition { HeadShift = -contextList.Count, LookupFlags = transformation.LookupFlags, Action = new SubstitutionAction { ReplacedGlyphCount = 0, ReplacementGlyphIds = new[] { SubMachineBuilder.ContextTerminatorGlyphId } } } }; } else { // Part 2. Sub-machine initialization state. subMachineInitializationPathSegment = new List <ITransition> { createTransitionCallback( completeContext.Last(), -contextList.Count, transformation.LookupFlags, new SubstitutionAction { ReplacementGlyphIds = new[] { SubMachineBuilder.ContextTerminatorGlyphId } }, this.GetContextZoneForGlyphIndex(completeContext.Count - 1, contextList, lookbackContextList, lookaheadContextList)) }; } /* Part 3 a 4. Sub-machine iself + deinitialization state. * Including its entry state (to which lead all the internal transitions). */ var subMachinePaths = subBuilder.GetPaths(); // Assemble the paths and add them into the outer machine builder. foreach (var subMachinePath in subMachinePaths) { var assembledPath = contextMatchPathSegment.Append(subMachineInitializationPathSegment).Append(subMachinePath).ToList(); builder.AddPath(assembledPath); } }