Example #1
0
        /// <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>
        /// Tests that <see cref="PositioningCompiler.CompileTransformation" /> calls builder correctly on given transformaton.
        /// </summary>
        /// <param name="table">The table.</param>
        /// <param name="expected">The expected.</param>
        public void TestCompileTransformation(IGlyphTransformationTable table, IEnumerable <ITransition>[] expected)
        {
            var builder = new StateMachineBuilderStub();

            var compiler = new PositioningCompiler();

            compiler.CompileTransformation(table, builder);

            Assert.IsTrue(expected.ValuesEqual(builder.Paths, new PathEqualityComparer()));
        }
Example #3
0
        /// <summary>
        /// Tests that <see cref="SubstitutionCompiler.CompileTransformation" /> calls builder correctly on given transformaton.
        /// </summary>
        /// <param name="table">The table.</param>
        /// <param name="expected">The expected.</param>
        /// <param name="expectedProcessingDirection">The expected processing direction.</param>
        public void TestCompileTransformation(IGlyphTransformationTable table, IEnumerable <ITransition>[] expected, ProcessingDirection expectedProcessingDirection = ProcessingDirection.StartToEnd)
        {
            var builder = new StateMachineBuilderStub();

            var compiler = new SubstitutionCompiler();

            compiler.CompileTransformation(table, builder);

            Assert.IsTrue(expected.ValuesEqual(builder.Paths, new PathEqualityComparer()));
            Assert.AreEqual(expectedProcessingDirection, builder.ProcessingDirection);
        }
Example #4
0
        /// <inheritdoc />
        public override void CompileTransformation(IGlyphTransformationTable transformation, IStateMachineBuilder builder)
        {
            if (transformation is ConstantPositioningTable)
            {
                var table = transformation as ConstantPositioningTable;

                var paths = table.Coverage.CoveredGlyphIds.Values.Select(glyphId => new[]
                {
                    new SimpleTransition {
                        GlyphId     = glyphId,
                        HeadShift   = 1,
                        LookupFlags = table.LookupFlags,
                        Action      = new PositioningAdjustmentAction
                        {
                            PositionChanges = new[] { table.PositionChange }
                        }
                    }
                });

                foreach (var path in paths)
                {
                    builder.AddPath(path);
                }
            }
            else if (transformation is IndividualChangePositioningTable)
            {
                var table = transformation as IndividualChangePositioningTable;

                var paths = table.Coverage.CoveredGlyphIds.Zip(
                    table.PositionChanges,
                    (coveragePair, positioningChange) => new[]
                {
                    new SimpleTransition {
                        GlyphId     = coveragePair.Value,
                        HeadShift   = 1,
                        LookupFlags = table.LookupFlags,
                        Action      = new PositioningAdjustmentAction
                        {
                            PositionChanges = new[] { positioningChange },
                        }
                    }
                });

                foreach (var path in paths)
                {
                    builder.AddPath(path);
                }
            }
            else if (transformation is GlyphPairPositioningTable)
            {
                var table = transformation as GlyphPairPositioningTable;

                var coverageDictionary = table.Coverage.CoveredGlyphIds;

                var paths =
                    table.PairSets.SelectMany(
                        (pairSet, coverageIndex) => pairSet.Select(
                            pair => new[]
                {
                    // First state just checks first glyph of the pair.
                    new SimpleTransition {
                        GlyphId     = coverageDictionary[(ushort)coverageIndex],
                        HeadShift   = 1,
                        LookupFlags = table.LookupFlags
                    },

                    // Seconds state checks the second glyph and does the positioning.
                    new SimpleTransition {
                        GlyphId     = pair.SecondGlyphID,
                        LookupFlags = table.LookupFlags,
                        HeadShift   = 0,           // TODO: 0?
                        Action      = new PositioningAdjustmentAction
                        {
                            PositionChanges = new[] { pair.FirstGlyphPositionChange, pair.SecondGlyphPositionChange },
                        }
                    }
                }));

                foreach (var path in paths)
                {
                    builder.AddPath(path);
                }
            }
            else if (transformation is ClassPairPositioningTable)
            {
                var table = transformation as ClassPairPositioningTable;

                var paths =
                    table
                    .PairSets
                    .Zip(table.FirstClassDef.ClassAssignments)
                    .SelectMany(
                        pairSetTuple => pairSetTuple
                        .Item1
                        .Zip(table.SecondClassDef.ClassAssignments)
                        .Select(pairTuple => new[]
                {
                    new SetTransition {
                        GlyphIdSet  = new HashSet <ushort>(pairSetTuple.Item2),
                        HeadShift   = 1,
                        LookupFlags = table.LookupFlags
                    },
                    new SetTransition
                    {
                        GlyphIdSet  = new HashSet <ushort>(pairTuple.Item2),
                        HeadShift   = 0,
                        LookupFlags = table.LookupFlags,
                        Action      = new PositioningAdjustmentAction
                        {
                            PositionChanges = new[] { pairTuple.Item1.Item1, pairTuple.Item1.Item2 }
                        }
                    }
                }));

                foreach (var path in paths)
                {
                    builder.AddPath(path);
                }
            }
            else if (transformation is CursivePositioningTable)
            {
                var table = transformation as CursivePositioningTable;

                var glyphsWithEntryExits = table.Coverage.CoveredGlyphIds.Zip(table.EntryExitRecords, (coveragePair, entryExit) => new
                {
                    GlyphId          = coveragePair.Value,
                    EntryAnchorPoint = entryExit.Item1,
                    ExitAnchorPoint  = entryExit.Item2
                }).ToList();

                // Only pairs where the first glyph has exit anchor and the second glyph has entry anchor are valid
                var paths =
                    from firstGlyph in glyphsWithEntryExits
                    where firstGlyph.ExitAnchorPoint != null
                    from secondGlyph in glyphsWithEntryExits
                    where secondGlyph.EntryAnchorPoint != null
                    select new []
                {
                    // First state just checks first glyph of the pair.
                    new SimpleTransition {
                        GlyphId     = firstGlyph.GlyphId,
                        HeadShift   = 1,
                        LookupFlags = table.LookupFlags
                    },

                    // Seconds state checks the second glyph and does the positioning.
                    new SimpleTransition {
                        GlyphId     = secondGlyph.GlyphId,
                        LookupFlags = table.LookupFlags,
                        HeadShift   = 0,
                        Action      = new AnchorPointToAnchorPointAction
                        {
                            PreviousGlyphAnchorPoint = firstGlyph.ExitAnchorPoint,
                            CurrentGlyphAnchorPoint  = secondGlyph.EntryAnchorPoint
                        }
                    }
                };

                foreach (var path in paths)
                {
                    builder.AddPath(path);
                }
            }
            else if (transformation is MarkToBasePositioningTable)
            {
                var table = transformation as MarkToBasePositioningTable;

                /*var glyphIdWithMarkEntry = table.MarkCoverage.CoveredGlyphIds.Zip(
                 *  table.MarkAnchorPoints,
                 *  (coveragePair, markArrayEntry) => new KeyValuePair<ushort, Tuple<uushort, AnchorPoint>>(coveragePair.Value, markArrayEntry)).ToDictionary();
                 *
                 * var glyphIdWithBaseEntry = table.MarkCoverage.CoveredGlyphIds.Zip(
                 *  table.BaseAnchorPoints,
                 *  (coveragePair, baseArrayEntry) => new KeyValuePair<ushort, IEnumerable<AnchorPoint>>(coveragePair.Value, baseArrayEntry)).ToDictionary();
                 */

                var glyphIdWithMarkEntry = table.MarkCoverage.CoveredGlyphIds.Zip(
                    table.MarkAnchorPoints,
                    (coveragePair, markArrayEntry) => new {
                    GlyphId     = coveragePair.Value,
                    ClassId     = markArrayEntry.Item1,
                    AnchorPoint = markArrayEntry.Item2
                });

                var glyphIdWithBaseEntry = table.BaseCoverage.CoveredGlyphIds.Zip(
                    table.BaseAnchorPoints,
                    (coveragePair, baseArrayEntry) => new
                {
                    GlyphId = coveragePair.Value,
                    AnchorPointByClassId = baseArrayEntry.ToList()
                });

                var paths =
                    from baseGlyph in glyphIdWithBaseEntry
                    from markGlyph in glyphIdWithMarkEntry
                    select new []
                {
                    new SimpleTransition
                    {
                        GlyphId     = baseGlyph.GlyphId,
                        HeadShift   = 1,
                        LookupFlags = table.LookupFlags
                    },
                    new SimpleTransition
                    {
                        GlyphId     = markGlyph.GlyphId,
                        HeadShift   = 1,
                        LookupFlags = table.LookupFlags,
                        Action      = new AnchorPointToAnchorPointAction
                        {
                            PreviousGlyphAnchorPoint = baseGlyph.AnchorPointByClassId[markGlyph.ClassId],
                            CurrentGlyphAnchorPoint  = markGlyph.AnchorPoint
                        }
                    }
                };

                foreach (var path in paths)
                {
                    builder.AddPath(path);
                }
            }
        }
Example #5
0
        /// <summary>
        /// Compiles a single transformation.
        /// </summary>
        /// <param name="transformation">The transformation.</param>
        /// <param name="builder">The builder.</param>
        public virtual void CompileTransformation(IGlyphTransformationTable transformation, IStateMachineBuilder builder)
        {
            if (transformation is GlyphContextTransformationTable)
            {
                this.CompileRuleContextTransformation(
                    transformation,
                    builder,
                    glyphId => glyphId,
                    (glyphId, headShift, lookupFlags, action, zone) => new SimpleTransition {
                    GlyphId     = glyphId,
                    HeadShift   = headShift,
                    LookupFlags = lookupFlags,
                    Action      = action
                });
            }
            else if (transformation is ClassContextTransformationTable)
            {
                var table            = transformation as ClassContextTransformationTable;
                var classAssignments = table.ClassDefinitions.ClassAssignments;

                /* Here is slight difference from spec - first transition of each "rule" state chain used to recognize the context
                 * will alway be a set transition where glyph set will be defined by appropriate class definition. Instead, only glyps
                 * which are mentioned in the coverage table should be recognized by the first transition. */
                this.CompileRuleContextTransformation(
                    transformation,
                    builder,
                    glyphId => table.ClassDefinitions.ClassAssignments.Single(classAssignment => classAssignment.Contains(glyphId)).Key,
                    (classId, headShift, lookupFlags, action, zone) => new SetTransition {
                    GlyphIdSet  = new HashSet <ushort>(classAssignments[classId]),
                    HeadShift   = headShift,
                    LookupFlags = lookupFlags,
                    Action      = action
                });
            }
            else if (transformation is CoverageContextTransformationTable)
            {
                var table = transformation as CoverageContextTransformationTable;

                foreach (var transformationSet in table.TransformationSets)
                {
                    this.CompileSingleContextTransformation(
                        table,
                        builder,
                        transformationSet,
                        (coverageTable, headShift, lookupFlags, action, zone) => new SetTransition {
                        GlyphIdSet  = new HashSet <ushort>(coverageTable.CoveredGlyphIds.Values),
                        HeadShift   = headShift,
                        LookupFlags = lookupFlags,
                        Action      = action
                    },
                        table.Coverages);
                }
            }
            else if (transformation is ChainingGlyphContextTransformationTable)
            {
                this.CompileChainingRuleContextTransformation(
                    transformation,
                    builder,
                    glyphId => glyphId,
                    (glyphId, headShift, lookupFlags, action, zone) => new SimpleTransition
                {
                    GlyphId     = glyphId,
                    HeadShift   = headShift,
                    LookupFlags = lookupFlags,
                    Action      = action
                });
            }
            else if (transformation is ChainingClassContextTransformationTable)
            {
                var table = transformation as ChainingClassContextTransformationTable;
                var contextClassAssignments   = table.ContextClassDefinitions.ClassAssignments;
                var lookbackClassAssignments  = table.LookbackClassDefinition.ClassAssignments;
                var lookaheadClassAssignments = table.LookaheadClassDefinition.ClassAssignments;

                var zoneToClassesMap = new Dictionary <ContextZone, ILookup <ushort, ushort> >
                {
                    { ContextZone.Context, contextClassAssignments },
                    { ContextZone.Lookback, lookbackClassAssignments },
                    { ContextZone.Lookahead, lookaheadClassAssignments }
                };

                this.CompileChainingRuleContextTransformation(
                    transformation,
                    builder,
                    glyphId => contextClassAssignments.Single(classAssignment => classAssignment.Contains(glyphId)).Key,
                    (classId, headShift, lookupFlags, action, zone) => new SetTransition {
                    GlyphIdSet  = new HashSet <ushort>((IEnumerable <ushort>)zoneToClassesMap[zone].SingleOrDefault(p => p.Key == classId) ?? new List <ushort>()),
                    HeadShift   = headShift,
                    LookupFlags = lookupFlags,
                    Action      = action
                });
            }
            else if (transformation is ChainingCoverageContextSubstitutionTable)
            {
                var table = transformation as ChainingCoverageContextSubstitutionTable;

                foreach (var transformationSet in table.TransformationSets)
                {
                    this.CompileSingleContextTransformation(
                        table,
                        builder,
                        transformationSet,
                        (coverageTable, headShift, lookupFlags, action, zone) => new SetTransition {
                        GlyphIdSet  = new HashSet <ushort>(coverageTable.CoveredGlyphIds.Values),
                        HeadShift   = headShift,
                        LookupFlags = lookupFlags,
                        Action      = action
                    },
                        table.ContextCoverages,
                        table.LookbackCoverages,
                        table.LookaheadCoverages);
                }
            }
            else
            {
                throw new ArgumentOutOfRangeException("transformation");
            }
        }
Example #6
0
        /// <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);
            }
        }
        /// <inheritdoc />
        public override void CompileTransformation(IGlyphTransformationTable transformation, IStateMachineBuilder builder)
        {
            if (transformation is SimpleReplacementSubstitutionTable)
            {
                var table = transformation as SimpleReplacementSubstitutionTable;

                var paths = table.Coverage.CoveredGlyphIds.Zip(
                    table.ReplacementGlyphIds,
                    (coveragePair, replacementGlyphId) => new[]
                {
                    new SimpleTransition {
                        GlyphId     = coveragePair.Value,
                        HeadShift   = 1,
                        LookupFlags = table.LookupFlags,
                        Action      = new SubstitutionAction
                        {
                            ReplacementGlyphIds = new[] { replacementGlyphId },
                            ReplacedGlyphCount  = 1
                        }
                    }
                });

                foreach (var path in paths)
                {
                    builder.AddPath(path);
                }
            }
            else if (transformation is DeltaSubstitutionTable)
            {
                var table = transformation as DeltaSubstitutionTable;

                var paths =
                    from coveragePair in table.Coverage.CoveredGlyphIds
                    select
                    new[]
                {
                    new SimpleTransition
                    {
                        GlyphId     = coveragePair.Value,
                        HeadShift   = 1,
                        LookupFlags = table.LookupFlags,
                        Action      = new SubstitutionAction
                        {
                            ReplacementGlyphIds = new[] { (ushort)(coveragePair.Value + table.GlyphIdDelta) },
                            ReplacedGlyphCount  = 1
                        }
                    }
                };

                foreach (var path in paths)
                {
                    builder.AddPath(path);
                }
            }
            else if (transformation is LigatureSubstitutionTable)
            {
                var table = transformation as LigatureSubstitutionTable;

                var ligatureSetInfos = table.Coverage.CoveredGlyphIds.Zip(
                    table.LigatureSets,
                    (coveragePair, ligatureSet) => new { CoveredGlyphId = coveragePair.Value, LigatureSet = ligatureSet });

                var paths =
                    from ligatureSetInfo in ligatureSetInfos
                    from ligature in ligatureSetInfo.LigatureSet
                    select this.ConvertLigatureToPath(ligatureSetInfo.CoveredGlyphId, ligature, table.LookupFlags).ToList();

                foreach (var path in paths)
                {
                    builder.AddPath(path);
                }
            }
            else if (transformation is MultipleSubstitutionTable)
            {
                var table = transformation as MultipleSubstitutionTable;

                var paths = table.Coverage.CoveredGlyphIds.Zip(
                    table.ReplacementSequences,
                    (coveragePair, replacementSequence) =>
                {
                    var list = replacementSequence.ToList();
                    return(new[]
                    {
                        new SimpleTransition
                        {
                            GlyphId = coveragePair.Value,
                            HeadShift = 1,
                            LookupFlags = table.LookupFlags,
                            Action = new SubstitutionAction
                            {
                                ReplacementGlyphIds = list,
                                ReplacedGlyphCount = 1
                            }
                        }
                    });
                });

                foreach (var path in paths)
                {
                    builder.AddPath(path);
                }
            }
            else if (transformation is ReverseChainingContextSubstitutionTable)
            {
                var table = transformation as ReverseChainingContextSubstitutionTable;

                builder.SetProcessingDirection(ProcessingDirection.EndToStart);

                // The entire machine is executed from the end of the string towards its start
                var lookbackCoveragesList = table.LookbackCoverages.ToList();
                var completeContext       = (((IEnumerable <ICoverageTable>)lookbackCoveragesList)
                                             .Reverse()
                                             .Append(table.Coverage)
                                             .Append(table.LookaheadCoverages)
                                             ).Reverse().ToList();

                var contextCheckPathSeqment = completeContext
                                              .Take(completeContext.Count - 1)
                                              .Select(coverage => (ITransition) new SetTransition
                {
                    GlyphIdSet  = new HashSet <ushort>(coverage.CoveredGlyphIds.Select(p => p.Value)),
                    HeadShift   = -1,
                    LookupFlags = table.LookupFlags
                })
                                              .Append(new SetTransition
                {
                    GlyphIdSet  = new HashSet <ushort>(completeContext.Last().CoveredGlyphIds.Select(p => p.Value)),
                    HeadShift   = lookbackCoveragesList.Count,
                    LookupFlags = table.LookupFlags
                }).ToList();

                int i = 0;
                var replacementGlyphIds = table.SubstituteGlyphIds.ToList();
                foreach (var coveredGlyphId in table.Coverage.CoveredGlyphIds.Values)
                {
                    var transition = new SimpleTransition
                    {
                        GlyphId     = coveredGlyphId,
                        HeadShift   = lookbackCoveragesList.Count,
                        LookupFlags = table.LookupFlags,
                        Action      = new SubstitutionAction
                        {
                            ReplacedGlyphCount  = 1,
                            ReplacementGlyphIds = new [] { replacementGlyphIds[i] }
                        }
                    };

                    builder.AddPath(contextCheckPathSeqment.Append(transition));

                    i++;
                }

                builder.AddPath(contextCheckPathSeqment.Append(new AlwaysTransition
                {
                    HeadShift   = lookbackCoveragesList.Count,
                    LookupFlags = table.LookupFlags
                }));
            }
            else
            {
                base.CompileTransformation(transformation, builder);
            }
        }