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