/// <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 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); } } }
/// <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); } }