public override bool TryUpdatePosition(
                FontMetrics fontMetrics,
                GPosTable table,
                GlyphPositioningCollection collection,
                Tag feature,
                ushort index,
                int count)
            {
                // Mark-to-Base Attachment Positioning Subtable.
                // Implements: https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-4-mark-to-base-attachment-positioning-subtable
                ushort glyphId = collection[index][0];

                if (glyphId == 0)
                {
                    return(false);
                }

                int markIndex = this.markCoverage.CoverageIndexOf(glyphId);

                if (markIndex == -1)
                {
                    return(false);
                }

                // Search backward for a base glyph.
                int baseGlyphIndex = index;

                while (--baseGlyphIndex >= 0)
                {
                    GlyphShapingData data = collection.GetGlyphShapingData(baseGlyphIndex);
                    if (!AdvancedTypographicUtils.IsMarkGlyph(fontMetrics, data.GlyphIds[0], data) && !(data.LigatureComponent > 0))
                    {
                        break;
                    }
                }

                if (baseGlyphIndex < 0)
                {
                    return(false);
                }

                ushort baseGlyphId = collection[baseGlyphIndex][0];
                int    baseIndex   = this.baseCoverage.CoverageIndexOf(baseGlyphId);

                if (baseIndex < 0)
                {
                    return(false);
                }

                MarkRecord  markRecord = this.markArrayTable.MarkRecords[markIndex];
                AnchorTable baseAnchor = this.baseArrayTable.BaseRecords[baseIndex].BaseAnchorTables[markRecord.MarkClass];

                AdvancedTypographicUtils.ApplyAnchor(collection, index, baseAnchor, markRecord, baseGlyphIndex);

                return(true);
            }
        private bool ShouldIgnore(int index)
        {
            GlyphShapingData  data         = this.Collection.GetGlyphShapingData(index);
            GlyphShapingClass shapingClass = AdvancedTypographicUtils.GetGlyphShapingClass(this.fontMetrics, data.GlyphIds[0], data);

            return((this.ignoreMarks && shapingClass.IsMark) ||
                   (this.ignoreBaseGlypghs && shapingClass.IsBase) ||
                   (this.ignoreLigatures && shapingClass.IsLigature) ||
                   (this.markAttachmentType > 0 && shapingClass.IsMark && shapingClass.MarkAttachmentType != this.markAttachmentType));
        }
Exemplo n.º 3
0
        public static void ApplyPosition(
            GlyphPositioningCollection collection,
            ushort index,
            ValueRecord record)
        {
            GlyphShapingData current = collection.GetGlyphShapingData(index);

            current.Bounds.Width  += record.XAdvance;
            current.Bounds.Height += record.YAdvance;
            current.Bounds.X      += record.XPlacement;
            current.Bounds.Y      += record.YPlacement;
        }
Exemplo n.º 4
0
        public static bool IsMarkGlyph(FontMetrics fontMetrics, ushort glyphId, GlyphShapingData shapingData)
        {
            if (!fontMetrics.TryGetGlyphClass(glyphId, out GlyphClassDef? glyphClass) &&
                !CodePoint.IsMark(shapingData.CodePoint))
            {
                return(false);
            }

            if (glyphClass != GlyphClassDef.MarkGlyph)
            {
                return(false);
            }

            return(true);
        }
Exemplo n.º 5
0
        public static void ApplyAnchor(
            GlyphPositioningCollection collection,
            ushort index,
            AnchorTable baseAnchor,
            MarkRecord markRecord,
            int baseGlyphIndex)
        {
            short baseX = baseAnchor.XCoordinate;
            short baseY = baseAnchor.YCoordinate;
            short markX = markRecord.MarkAnchorTable.XCoordinate;
            short markY = markRecord.MarkAnchorTable.YCoordinate;

            GlyphShapingData data = collection.GetGlyphShapingData(index);

            data.Bounds.X       = baseX - markX;
            data.Bounds.Y       = baseY - markY;
            data.MarkAttachment = baseGlyphIndex;
        }
Exemplo n.º 6
0
            private static void ReverseCursiveMinorOffset(
                GlyphPositioningCollection collection,
                int position,
                int i,
                bool horizontal,
                int parent)
            {
                GlyphShapingData c = collection.GetGlyphShapingData(i);
                int chain          = c.CursiveAttachment;

                if (chain <= 0)
                {
                    return;
                }

                c.CursiveAttachment = 0;

                int j = i + chain;

                // Stop if we see new parent in the chain.
                if (j == parent)
                {
                    return;
                }

                ReverseCursiveMinorOffset(collection, position, j, horizontal, parent);

                GlyphShapingData p = collection.GetGlyphShapingData(j);

                if (horizontal)
                {
                    p.Bounds.Y = -c.Bounds.Y;
                }
                else
                {
                    p.Bounds.X = -c.Bounds.X;
                }

                p.CursiveAttachment = -chain;
            }
        public bool TryUpdatePositions(FontMetrics fontMetrics, GlyphPositioningCollection collection, KerningMode kerningMode, out bool kerned)
        {
            kerned = false;
            bool updated = false;

            for (ushort i = 0; i < collection.Count; i++)
            {
                if (!collection.ShouldProcess(fontMetrics, i))
                {
                    continue;
                }

                ScriptClass current = CodePoint.GetScriptClass(collection.GetGlyphShapingData(i).CodePoint);
                BaseShaper  shaper  = ShaperFactory.Create(current, kerningMode);

                ushort index = i;
                ushort count = 1;
                while (i < collection.Count - 1)
                {
                    // We want to assign the same feature lookups to individual sections of the text rather
                    // than the text as a whole to ensure that different language shapers do not interfere
                    // with each other when the text contains multiple languages.
                    GlyphShapingData nextData = collection.GetGlyphShapingData(i + 1);
                    ScriptClass      next     = CodePoint.GetScriptClass(nextData.CodePoint);
                    if (next is not ScriptClass.Common and not ScriptClass.Unknown and not ScriptClass.Inherited && next != current)
                    {
                        break;
                    }

                    i++;
                    count++;
                }

                if (shaper.MarkZeroingMode == MarkZeroingMode.PreGPos)
                {
                    ZeroMarkAdvances(fontMetrics, collection, index, count);
                }

                // Assign Substitution features to each glyph.
                shaper.AssignFeatures(collection, index, count);
                IEnumerable <Tag>     stageFeatures = shaper.GetShapingStageFeatures();
                SkippingGlyphIterator iterator      = new(fontMetrics, collection, index, default);
                foreach (Tag stageFeature in stageFeatures)
                {
                    List <(Tag Feature, ushort Index, LookupTable LookupTable)> lookups = this.GetFeatureLookups(in stageFeature, current);
                    if (lookups.Count == 0)
                    {
                        continue;
                    }

                    // Apply features in order.
                    foreach ((Tag Feature, ushort Index, LookupTable LookupTable)featureLookup in lookups)
                    {
                        Tag feature = featureLookup.Feature;
                        iterator.Reset(index, featureLookup.LookupTable.LookupFlags);

                        while (iterator.Index < index + count)
                        {
                            List <TagEntry> glyphFeatures = collection.GetGlyphShapingData(iterator.Index).Features;
                            if (!HasFeature(glyphFeatures, in feature))
                            {
                                iterator.Next();
                                continue;
                            }

                            bool success = featureLookup.LookupTable.TryUpdatePosition(fontMetrics, this, collection, featureLookup.Feature, iterator.Index, count - (iterator.Index - index));
                            kerned  |= success && (feature == KernTag || feature == VKernTag);
                            updated |= success;
                            iterator.Next();
                        }
                    }
                }

                if (shaper.MarkZeroingMode == MarkZeroingMode.PostGpos)
                {
                    ZeroMarkAdvances(fontMetrics, collection, index, count);
                }

                FixCursiveAttachment(collection, index, count);
                FixMarkAttachment(collection, index, count);
                if (updated)
                {
                    UpdatePositions(fontMetrics, collection, index, count);
                }
            }

            return(updated);
        }
        public void ApplySubstitution(FontMetrics fontMetrics, GlyphSubstitutionCollection collection, KerningMode kerningMode)
        {
            for (ushort i = 0; i < collection.Count; i++)
            {
                // Choose a shaper based on the script.
                // This determines which features to apply to which glyphs.
                ScriptClass current = CodePoint.GetScriptClass(collection.GetGlyphShapingData(i).CodePoint);
                BaseShaper  shaper  = ShaperFactory.Create(current, kerningMode);

                ushort index = i;
                ushort count = 1;
                while (i < collection.Count - 1)
                {
                    // We want to assign the same feature lookups to individual sections of the text rather
                    // than the text as a whole to ensure that different language shapers do not interfere
                    // with each other when the text contains multiple languages.
                    GlyphShapingData nextData = collection.GetGlyphShapingData(i + 1);
                    ScriptClass      next     = CodePoint.GetScriptClass(nextData.CodePoint);
                    if (next is not ScriptClass.Common and not ScriptClass.Unknown and not ScriptClass.Inherited && next != current)
                    {
                        break;
                    }

                    i++;
                    count++;
                }

                // Assign Substitution features to each glyph.
                shaper.AssignFeatures(collection, index, count);
                IEnumerable <Tag> stageFeatures = shaper.GetShapingStageFeatures();

                int currentCount = collection.Count;
                SkippingGlyphIterator iterator = new(fontMetrics, collection, index, default);
                foreach (Tag stageFeature in stageFeatures)
                {
                    List <(Tag Feature, ushort Index, LookupTable LookupTable)> lookups = this.GetFeatureLookups(in stageFeature, current);
                    if (lookups.Count == 0)
                    {
                        continue;
                    }

                    // Apply features in order.
                    foreach ((Tag Feature, ushort Index, LookupTable LookupTable)featureLookup in lookups)
                    {
                        Tag feature = featureLookup.Feature;
                        iterator.Reset(index, featureLookup.LookupTable.LookupFlags);

                        while (iterator.Index < index + count)
                        {
                            List <TagEntry> glyphFeatures = collection.GetGlyphShapingData(iterator.Index).Features;
                            if (!HasFeature(glyphFeatures, in feature))
                            {
                                iterator.Next();
                                continue;
                            }

                            featureLookup.LookupTable.TrySubstitution(fontMetrics, this, collection, featureLookup.Feature, iterator.Index, count - (iterator.Index - index));
                            iterator.Next();

                            // Account for substitutions changing the length of the collection.
                            if (collection.Count != currentCount)
                            {
                                count        = (ushort)(count - (currentCount - collection.Count));
                                currentCount = collection.Count;
                            }
                        }
                    }
                }
            }
        }
        /// <inheritdoc/>
        public override void AssignFeatures(IGlyphShapingCollection collection, int index, int count)
        {
            this.AddFeature(collection, index, count, CcmpTag);
            this.AddFeature(collection, index, count, LoclTag);
            this.AddFeature(collection, index, count, IsolTag, false);
            this.AddFeature(collection, index, count, FinaTag, false);
            this.AddFeature(collection, index, count, Fin2Tag, false);
            this.AddFeature(collection, index, count, Fin3Tag, false);
            this.AddFeature(collection, index, count, MediTag, false);
            this.AddFeature(collection, index, count, Med2Tag, false);
            this.AddFeature(collection, index, count, InitTag, false);
            this.AddFeature(collection, index, count, MsetTag);

            base.AssignFeatures(collection, index, count);

            int prev  = -1;
            int state = 0;

            byte[] actions = new byte[count];

            // Apply the state machine to map glyphs to features.
            for (int i = 0; i < count; i++)
            {
                GlyphShapingData data         = collection.GetGlyphShapingData(i + index);
                JoiningClass     joiningClass = CodePoint.GetJoiningClass(data.CodePoint);
                JoiningType      joiningType  = joiningClass.JoiningType;
                if (joiningType == JoiningType.Transparent)
                {
                    actions[i] = None;
                    continue;
                }

                int    shapingClassIndex = GetShapingClassIndex(joiningType);
                byte[] actionsWithState  = StateTable[state, shapingClassIndex];
                byte   prevAction        = actionsWithState[0];
                byte   curAction         = actionsWithState[1];
                state = actionsWithState[2];

                if (prevAction != None && prev != -1)
                {
                    actions[prev] = prevAction;
                }

                actions[i] = curAction;
                prev       = i;
            }

            // Apply the chosen features to their respective glyphs.
            for (int i = 0; i < actions.Length; i++)
            {
                switch (actions[i])
                {
                case Fina:
                    collection.EnableShapingFeature(i + index, FinaTag);
                    break;

                case Fin2:
                    collection.EnableShapingFeature(i + index, Fin2Tag);
                    break;

                case Fin3:
                    collection.EnableShapingFeature(i + index, Fin3Tag);
                    break;

                case Isol:
                    collection.EnableShapingFeature(i + index, IsolTag);
                    break;

                case Init:
                    collection.EnableShapingFeature(i + index, InitTag);
                    break;

                case Medi:
                    collection.EnableShapingFeature(i + index, MediTag);
                    break;

                case Med2:
                    collection.EnableShapingFeature(i + index, Med2Tag);
                    break;
                }
            }
        }
Exemplo n.º 10
0
        public static GlyphShapingClass GetGlyphShapingClass(FontMetrics fontMetrics, ushort glyphId, GlyphShapingData shapingData)
        {
            bool   isMark;
            bool   isBase;
            bool   isLigature;
            ushort markAttachmentType = 0;

            if (fontMetrics.TryGetGlyphClass(glyphId, out GlyphClassDef? glyphClass))
            {
                isMark     = glyphClass == GlyphClassDef.MarkGlyph;
                isBase     = glyphClass == GlyphClassDef.BaseGlyph;
                isLigature = glyphClass == GlyphClassDef.LigatureGlyph;
                if (fontMetrics.TryGetMarkAttachmentClass(glyphId, out GlyphClassDef? markAttachmentClass))
                {
                    markAttachmentType = (ushort)markAttachmentClass;
                }
            }
            else
            {
                // TODO: We may have to store each codepoint. FontKit checks all.
                isMark     = CodePoint.IsMark(shapingData.CodePoint);
                isBase     = !isMark;
                isLigature = shapingData.CodePointCount > 1;
            }

            return(new GlyphShapingClass(isMark, isBase, isLigature, markAttachmentType));
        }
Exemplo n.º 11
0
            public override bool TryUpdatePosition(
                FontMetrics fontMetrics,
                GPosTable table,
                GlyphPositioningCollection collection,
                Tag feature,
                ushort index,
                int count)
            {
                // Mark-to-Ligature Attachment Positioning.
                // Implements: https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-5-mark-to-ligature-attachment-positioning-subtable
                ushort glyphId = collection[index][0];

                if (glyphId == 0)
                {
                    return(false);
                }

                int markIndex = this.markCoverage.CoverageIndexOf(glyphId);

                if (markIndex < 0)
                {
                    return(false);
                }

                // Search backward for a base glyph.
                int baseGlyphIndex = index;

                while (--baseGlyphIndex >= 0)
                {
                    GlyphShapingData data = collection.GetGlyphShapingData(baseGlyphIndex);
                    if (!AdvancedTypographicUtils.IsMarkGlyph(fontMetrics, data.GlyphIds[0], data))
                    {
                        break;
                    }
                }

                if (baseGlyphIndex < 0)
                {
                    return(false);
                }

                ushort baseGlyphId   = collection[baseGlyphIndex][0];
                int    ligatureIndex = this.ligatureCoverage.CoverageIndexOf(baseGlyphId);

                if (ligatureIndex < 0)
                {
                    return(false);
                }

                // We must now check whether the ligature ID of the current mark glyph
                // is identical to the ligature ID of the found ligature.
                // If yes, we can directly use the component index. If not, we attach the mark
                // glyph to the last component of the ligature.
                LigatureAttachTable ligatureAttach = this.ligatureArrayTable.LigatureAttachTables[ligatureIndex];
                GlyphShapingData    markGlyph      = collection.GetGlyphShapingData(index);
                GlyphShapingData    ligGlyph       = collection.GetGlyphShapingData(baseGlyphIndex);
                int compIndex = ligGlyph.LigatureId > 0 && ligGlyph.LigatureId == markGlyph.LigatureId && markGlyph.LigatureComponent > 0
                    ? Math.Min(markGlyph.LigatureComponent, ligGlyph.CodePointCount) - 1
                    : ligGlyph.CodePointCount - 1;

                MarkRecord  markRecord = this.markArrayTable.MarkRecords[markIndex];
                AnchorTable baseAnchor = ligatureAttach.ComponentRecords[compIndex].LigatureAnchorTables[markRecord.MarkClass];

                AdvancedTypographicUtils.ApplyAnchor(collection, index, baseAnchor, markRecord, baseGlyphIndex);

                return(true);
            }
Exemplo n.º 12
0
            public override bool TryUpdatePosition(
                FontMetrics fontMetrics,
                GPosTable table,
                GlyphPositioningCollection collection,
                Tag feature,
                ushort index,
                int count)
            {
                if (count <= 1)
                {
                    return(false);
                }

                // Implements Cursive Attachment Positioning Subtable:
                // https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-3-cursive-attachment-positioning-subtable
                ushort glyphId = collection[index][0];

                if (glyphId == 0)
                {
                    return(false);
                }

                ushort nextIndex   = (ushort)(index + 1);
                ushort nextGlyphId = collection[nextIndex][0];

                if (nextGlyphId == 0)
                {
                    return(false);
                }

                int coverageNext = this.coverageTable.CoverageIndexOf(nextGlyphId);

                if (coverageNext < 0)
                {
                    return(false);
                }

                EntryExitAnchors nextRecord = this.entryExitAnchors[coverageNext];
                AnchorTable?     entry      = nextRecord.EntryAnchor;

                if (entry is null)
                {
                    return(false);
                }

                int coverage = this.coverageTable.CoverageIndexOf(glyphId);

                if (coverage < 0)
                {
                    return(false);
                }

                EntryExitAnchors curRecord = this.entryExitAnchors[coverage];
                AnchorTable?     exit      = curRecord.ExitAnchor;

                if (exit is null)
                {
                    return(false);
                }

                GlyphShapingData current = collection.GetGlyphShapingData(index);
                GlyphShapingData next    = collection.GetGlyphShapingData(nextIndex);

                // TODO: Vertical.
                if (current.Direction == TextDirection.LeftToRight)
                {
                    current.Bounds.Width = exit.XCoordinate + current.Bounds.X;

                    int delta = entry.XCoordinate + next.Bounds.X;
                    next.Bounds.Width -= delta;
                    next.Bounds.X     -= delta;
                }
                else
                {
                    int delta = exit.XCoordinate + current.Bounds.X;
                    current.Bounds.Width -= delta;
                    current.Bounds.X     -= delta;

                    next.Bounds.Width = entry.XCoordinate + next.Bounds.X;
                }

                int child   = index;
                int parent  = nextIndex;
                int xOffset = entry.XCoordinate - exit.XCoordinate;
                int yOffset = entry.YCoordinate - exit.YCoordinate;

                if (this.LookupFlags.HasFlag(LookupFlags.RightToLeft))
                {
                    int temp = child;
                    child  = parent;
                    parent = temp;

                    xOffset = -xOffset;
                    yOffset = -yOffset;
                }

                // If child was already connected to someone else, walk through its old
                // chain and reverse the link direction, such that the whole tree of its
                // previous connection now attaches to new parent.Watch out for case
                // where new parent is on the path from old chain...
                bool horizontal = !collection.IsVerticalLayoutMode;

                ReverseCursiveMinorOffset(collection, index, child, horizontal, parent);

                GlyphShapingData c = collection.GetGlyphShapingData(child);

                c.CursiveAttachment = parent - child;
                if (horizontal)
                {
                    c.Bounds.Y = yOffset;
                }
                else
                {
                    c.Bounds.X = xOffset;
                }

                // If parent was attached to child, separate them.
                GlyphShapingData p = collection.GetGlyphShapingData(parent);

                if (p.CursiveAttachment == -c.CursiveAttachment)
                {
                    p.CursiveAttachment = 0;
                }

                return(true);
            }
            public override bool TryUpdatePosition(
                FontMetrics fontMetrics,
                GPosTable table,
                GlyphPositioningCollection collection,
                Tag feature,
                ushort index,
                int count)
            {
                // Mark to mark positioning.
                // Implements: https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-6-mark-to-mark-attachment-positioning-subtable
                ushort glyphId = collection[index][0];

                if (glyphId == 0)
                {
                    return(false);
                }

                int mark1Index = this.mark1Coverage.CoverageIndexOf(glyphId);

                if (mark1Index == -1)
                {
                    return(false);
                }

                // Get the previous mark to attach to.
                if (index < 1)
                {
                    return(false);
                }

                int              prevIdx     = index - 1;
                ushort           prevGlyphId = collection[prevIdx][0];
                GlyphShapingData prevGlyph   = collection.GetGlyphShapingData(prevIdx);

                if (!AdvancedTypographicUtils.IsMarkGlyph(fontMetrics, prevGlyphId, prevGlyph))
                {
                    return(false);
                }

                // The following logic was borrowed from Harfbuzz,
                // see: https://github.com/harfbuzz/harfbuzz/blob/3e635cf5e26e33d6210d3092256a49291752deec/src/hb-ot-layout-gpos-table.hh#L2525
                bool             good     = false;
                GlyphShapingData curGlyph = collection.GetGlyphShapingData(index);

                if (curGlyph.LigatureId == prevGlyph.LigatureId)
                {
                    if (curGlyph.LigatureId > 0)
                    {
                        // Marks belonging to the same base.
                        good = true;
                    }
                    else if (curGlyph.LigatureComponent == prevGlyph.LigatureComponent)
                    {
                        // Marks belonging to the same ligature component.
                        good = true;
                    }
                }
                else
                {
                    // If ligature ids don't match, it may be the case that one of the marks
                    // itself is a ligature, in which case match.
                    if ((curGlyph.LigatureId > 0 && curGlyph.LigatureComponent <= 0) ||
                        (prevGlyph.LigatureId > 0 && prevGlyph.LigatureComponent <= 0))
                    {
                        good = true;
                    }
                }

                if (!good)
                {
                    return(false);
                }

                int mark2Index = this.mark2Coverage.CoverageIndexOf(prevGlyphId);

                if (mark2Index == -1)
                {
                    return(false);
                }

                MarkRecord  markRecord = this.mark1ArrayTable.MarkRecords[mark1Index];
                AnchorTable baseAnchor = this.mark2ArrayTable.Mark2Records[mark2Index].MarkAnchorTable[markRecord.MarkClass];

                AdvancedTypographicUtils.ApplyAnchor(collection, index, baseAnchor, markRecord, prevIdx);

                return(true);
            }