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